1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the tools applications of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <QtCore/qcoreapplication.h>
30#include <QtCore/qfile.h>
31#include <QtCore/qfileinfo.h>
32#include <QtCore/qlist.h>
33#include <QtCore/qxmlstream.h>
34
35class VkSpecParser
36{
37public:
38 bool parse();
39
40 struct TypedName {
41 QString name;
42 QString type;
43 QString typeSuffix;
44 };
45
46 struct Command {
47 TypedName cmd;
48 QList<TypedName> args;
49 bool deviceLevel;
50 };
51
52 QList<Command> commands() const { return m_commands; }
53
54 void setFileName(const QString &fn) { m_fn = fn; }
55
56private:
57 void skip();
58 void parseCommands();
59 Command parseCommand();
60 TypedName parseParamOrProto(const QString &tag);
61 QString parseName();
62
63 QFile m_file;
64 QXmlStreamReader m_reader;
65 QList<Command> m_commands;
66 QString m_fn;
67};
68
69bool VkSpecParser::parse()
70{
71 m_file.setFileName(m_fn);
72 if (!m_file.open(QIODevice::ReadOnly | QIODevice::Text)) {
73 qWarning("Failed to open %s", qPrintable(m_file.fileName()));
74 return false;
75 }
76
77 m_reader.setDevice(&m_file);
78 while (!m_reader.atEnd()) {
79 m_reader.readNext();
80 if (m_reader.isStartElement()) {
81 if (m_reader.name() == QStringLiteral("commands"))
82 parseCommands();
83 }
84 }
85
86 return true;
87}
88
89void VkSpecParser::skip()
90{
91 QString tag = m_reader.name().toString();
92 while (!m_reader.atEnd()) {
93 m_reader.readNext();
94 if (m_reader.isEndElement() && m_reader.name() == tag)
95 break;
96 }
97}
98
99void VkSpecParser::parseCommands()
100{
101 m_commands.clear();
102
103 while (!m_reader.atEnd()) {
104 m_reader.readNext();
105 if (m_reader.isEndElement() && m_reader.name() == QStringLiteral("commands"))
106 return;
107 if (m_reader.isStartElement() && m_reader.name() == u"command")
108 m_commands.append(parseCommand());
109 }
110}
111
112VkSpecParser::Command VkSpecParser::parseCommand()
113{
114 Command c;
115
116 while (!m_reader.atEnd()) {
117 m_reader.readNext();
118 if (m_reader.isEndElement() && m_reader.name() == QStringLiteral("command"))
119 break;
120 if (m_reader.isStartElement()) {
121 const QString protoStr = QStringLiteral("proto");
122 const QString paramStr = QStringLiteral("param");
123 if (m_reader.name() == protoStr) {
124 c.cmd = parseParamOrProto(protoStr);
125 } else if (m_reader.name() == paramStr) {
126 c.args.append(parseParamOrProto(paramStr));
127 } else {
128 skip();
129 }
130 }
131 }
132
133 c.deviceLevel = false;
134 if (!c.args.isEmpty()) {
135 QStringList dispatchableDeviceAndChildTypes {
136 QStringLiteral("VkDevice"),
137 QStringLiteral("VkQueue"),
138 QStringLiteral("VkCommandBuffer")
139 };
140 if (dispatchableDeviceAndChildTypes.contains(c.args[0].type)
141 && c.cmd.name != QStringLiteral("vkGetDeviceProcAddr"))
142 {
143 c.deviceLevel = true;
144 }
145 }
146
147 return c;
148}
149
150VkSpecParser::TypedName VkSpecParser::parseParamOrProto(const QString &tag)
151{
152 TypedName t;
153
154 while (!m_reader.atEnd()) {
155 m_reader.readNext();
156 if (m_reader.isEndElement() && m_reader.name() == tag)
157 break;
158 if (m_reader.isStartElement()) {
159 if (m_reader.name() == QStringLiteral("name")) {
160 t.name = parseName();
161 } else if (m_reader.name() != QStringLiteral("type")) {
162 skip();
163 }
164 } else {
165 auto text = m_reader.text().trimmed();
166 if (!text.isEmpty()) {
167 if (text.startsWith(QLatin1Char('['))) {
168 t.typeSuffix += text;
169 } else {
170 if (!t.type.isEmpty())
171 t.type += QLatin1Char(' ');
172 t.type += text;
173 }
174 }
175 }
176 }
177
178 return t;
179}
180
181QString VkSpecParser::parseName()
182{
183 QString name;
184 while (!m_reader.atEnd()) {
185 m_reader.readNext();
186 if (m_reader.isEndElement() && m_reader.name() == QStringLiteral("name"))
187 break;
188 name += m_reader.text();
189 }
190 return name.trimmed();
191}
192
193QString funcSig(const VkSpecParser::Command &c, const char *className = nullptr)
194{
195 QString s(QString::asprintf("%s %s%s%s", qPrintable(c.cmd.type),
196 (className ? className : ""), (className ? "::" : ""),
197 qPrintable(c.cmd.name)));
198 if (!c.args.isEmpty()) {
199 s += QLatin1Char('(');
200 bool first = true;
201 for (const VkSpecParser::TypedName &a : c.args) {
202 if (!first)
203 s += QStringLiteral(", ");
204 else
205 first = false;
206 s += QString::asprintf("%s%s%s%s", qPrintable(a.type),
207 (a.type.endsWith(QLatin1Char('*')) ? "" : " "),
208 qPrintable(a.name), qPrintable(a.typeSuffix));
209 }
210 s += QLatin1Char(')');
211 }
212 return s;
213}
214
215QString funcCall(const VkSpecParser::Command &c, int idx)
216{
217 // template:
218 // [return] reinterpret_cast<PFN_vkEnumeratePhysicalDevices>(d_ptr->m_funcs[0])(instance, pPhysicalDeviceCount, pPhysicalDevices);
219 QString s = QString::asprintf("%sreinterpret_cast<PFN_%s>(d_ptr->m_funcs[%d])",
220 (c.cmd.type == QStringLiteral("void") ? "" : "return "),
221 qPrintable(c.cmd.name),
222 idx);
223 if (!c.args.isEmpty()) {
224 s += QLatin1Char('(');
225 bool first = true;
226 for (const VkSpecParser::TypedName &a : c.args) {
227 if (!first)
228 s += QStringLiteral(", ");
229 else
230 first = false;
231 s += a.name;
232 }
233 s += QLatin1Char(')');
234 }
235 return s;
236}
237
238class Preamble
239{
240public:
241 QByteArray get(const QString &fn);
242
243private:
244 QByteArray m_str;
245} preamble;
246
247QByteArray Preamble::get(const QString &fn)
248{
249 if (!m_str.isEmpty())
250 return m_str;
251
252 QFile f(fn);
253 if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
254 qWarning("Failed to open %s", qPrintable(fn));
255 return m_str;
256 }
257
258 m_str = f.readAll();
259 m_str.replace("FOO", "QtGui");
260 m_str += "\n// This file is automatically generated by qvkgen. Do not edit.\n";
261
262 return m_str;
263}
264
265bool genVulkanFunctionsH(const QList<VkSpecParser::Command> &commands, const QString &licHeaderFn,
266 const QString &outputBase)
267{
268 QFile f(outputBase + QStringLiteral(".h"));
269 if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) {
270 qWarning("Failed to write %s", qPrintable(f.fileName()));
271 return false;
272 }
273
274 static const char *s =
275"%s\n"
276"#ifndef QVULKANFUNCTIONS_H\n"
277"#define QVULKANFUNCTIONS_H\n"
278"\n"
279"#include <QtGui/qtguiglobal.h>\n"
280"\n"
281"#if QT_CONFIG(vulkan) || defined(Q_CLANG_QDOC)\n"
282"\n"
283"#ifndef VK_NO_PROTOTYPES\n"
284"#define VK_NO_PROTOTYPES\n"
285"#endif\n"
286"#include <vulkan/vulkan.h>\n"
287"\n"
288"#include <QtCore/qscopedpointer.h>\n"
289"\n"
290"QT_BEGIN_NAMESPACE\n"
291"\n"
292"class QVulkanInstance;\n"
293"class QVulkanFunctionsPrivate;\n"
294"class QVulkanDeviceFunctionsPrivate;\n"
295"\n"
296"class Q_GUI_EXPORT QVulkanFunctions\n"
297"{\n"
298"public:\n"
299" ~QVulkanFunctions();\n"
300"\n"
301"%s\n"
302"private:\n"
303" Q_DISABLE_COPY(QVulkanFunctions)\n"
304" QVulkanFunctions(QVulkanInstance *inst);\n"
305"\n"
306" QScopedPointer<QVulkanFunctionsPrivate> d_ptr;\n"
307" friend class QVulkanInstance;\n"
308"};\n"
309"\n"
310"class Q_GUI_EXPORT QVulkanDeviceFunctions\n"
311"{\n"
312"public:\n"
313" ~QVulkanDeviceFunctions();\n"
314"\n"
315"%s\n"
316"private:\n"
317" Q_DISABLE_COPY(QVulkanDeviceFunctions)\n"
318" QVulkanDeviceFunctions(QVulkanInstance *inst, VkDevice device);\n"
319"\n"
320" QScopedPointer<QVulkanDeviceFunctionsPrivate> d_ptr;\n"
321" friend class QVulkanInstance;\n"
322"};\n"
323"\n"
324"QT_END_NAMESPACE\n"
325"\n"
326"#endif // QT_CONFIG(vulkan) || defined(Q_CLANG_QDOC)\n"
327"\n"
328"#endif // QVULKANFUNCTIONS_H\n";
329
330 QString instCmdStr;
331 QString devCmdStr;
332 for (const VkSpecParser::Command &c : commands) {
333 QString *dst = c.deviceLevel ? &devCmdStr : &instCmdStr;
334 *dst += QStringLiteral(" ");
335 *dst += funcSig(c);
336 *dst += QStringLiteral(";\n");
337 }
338
339 f.write(QString::asprintf(s, preamble.get(licHeaderFn).constData(),
340 instCmdStr.toUtf8().constData(),
341 devCmdStr.toUtf8().constData()).toUtf8());
342
343 return true;
344}
345
346bool genVulkanFunctionsPH(const QList<VkSpecParser::Command> &commands, const QString &licHeaderFn,
347 const QString &outputBase)
348{
349 QFile f(outputBase + QStringLiteral("_p.h"));
350 if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) {
351 qWarning("Failed to write %s", qPrintable(f.fileName()));
352 return false;
353 }
354
355 static const char *s =
356"%s\n"
357"#ifndef QVULKANFUNCTIONS_P_H\n"
358"#define QVULKANFUNCTIONS_P_H\n"
359"\n"
360"//\n"
361"// W A R N I N G\n"
362"// -------------\n"
363"//\n"
364"// This file is not part of the Qt API. It exists purely as an\n"
365"// implementation detail. This header file may change from version to\n"
366"// version without notice, or even be removed.\n"
367"//\n"
368"// We mean it.\n"
369"//\n"
370"\n"
371"#include \"qvulkanfunctions.h\"\n"
372"\n"
373"QT_BEGIN_NAMESPACE\n"
374"\n"
375"class QVulkanInstance;\n"
376"\n"
377"class QVulkanFunctionsPrivate\n"
378"{\n"
379"public:\n"
380" QVulkanFunctionsPrivate(QVulkanInstance *inst);\n"
381"\n"
382" PFN_vkVoidFunction m_funcs[%d];\n"
383"};\n"
384"\n"
385"class QVulkanDeviceFunctionsPrivate\n"
386"{\n"
387"public:\n"
388" QVulkanDeviceFunctionsPrivate(QVulkanInstance *inst, VkDevice device);\n"
389"\n"
390" PFN_vkVoidFunction m_funcs[%d];\n"
391"};\n"
392"\n"
393"QT_END_NAMESPACE\n"
394"\n"
395"#endif // QVULKANFUNCTIONS_P_H\n";
396
397 const int devLevelCount = std::count_if(commands.cbegin(), commands.cend(),
398 [](const VkSpecParser::Command &c) { return c.deviceLevel; });
399 const int instLevelCount = commands.count() - devLevelCount;
400
401 f.write(QString::asprintf(s, preamble.get(licHeaderFn).constData(), instLevelCount, devLevelCount).toUtf8());
402
403 return true;
404}
405
406bool genVulkanFunctionsPC(const QList<VkSpecParser::Command> &commands, const QString &licHeaderFn,
407 const QString &outputBase)
408{
409 QFile f(outputBase + QStringLiteral("_p.cpp"));
410 if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) {
411 qWarning("Failed to write %s", qPrintable(f.fileName()));
412 return false;
413 }
414
415 static const char *s =
416"%s\n"
417"#include \"qvulkanfunctions_p.h\"\n"
418"#include \"qvulkaninstance.h\"\n"
419"\n"
420"QT_BEGIN_NAMESPACE\n"
421"\n%s"
422"QVulkanFunctionsPrivate::QVulkanFunctionsPrivate(QVulkanInstance *inst)\n"
423"{\n"
424" static const char *funcNames[] = {\n"
425"%s\n"
426" };\n"
427" for (int i = 0; i < %d; ++i) {\n"
428" m_funcs[i] = inst->getInstanceProcAddr(funcNames[i]);\n"
429" if (!m_funcs[i])\n"
430" qWarning(\"QVulkanFunctions: Failed to resolve %%s\", funcNames[i]);\n"
431" }\n"
432"}\n"
433"\n%s"
434"QVulkanDeviceFunctionsPrivate::QVulkanDeviceFunctionsPrivate(QVulkanInstance *inst, VkDevice device)\n"
435"{\n"
436" QVulkanFunctions *f = inst->functions();\n"
437" Q_ASSERT(f);\n\n"
438" static const char *funcNames[] = {\n"
439"%s\n"
440" };\n"
441" for (int i = 0; i < %d; ++i) {\n"
442" m_funcs[i] = f->vkGetDeviceProcAddr(device, funcNames[i]);\n"
443" if (!m_funcs[i])\n"
444" qWarning(\"QVulkanDeviceFunctions: Failed to resolve %%s\", funcNames[i]);\n"
445" }\n"
446"}\n"
447"\n"
448"QT_END_NAMESPACE\n";
449
450 QString devCmdWrapperStr;
451 QString instCmdWrapperStr;
452 int devIdx = 0;
453 int instIdx = 0;
454 QString devCmdNamesStr;
455 QString instCmdNamesStr;
456
457 for (int i = 0; i < commands.count(); ++i) {
458 QString *dst = commands[i].deviceLevel ? &devCmdWrapperStr : &instCmdWrapperStr;
459 int *idx = commands[i].deviceLevel ? &devIdx : &instIdx;
460 *dst += funcSig(commands[i], commands[i].deviceLevel ? "QVulkanDeviceFunctions" : "QVulkanFunctions");
461 *dst += QString(QStringLiteral("\n{\n Q_ASSERT(d_ptr->m_funcs[%1]);\n ")).arg(*idx);
462 *dst += funcCall(commands[i], *idx);
463 *dst += QStringLiteral(";\n}\n\n");
464 ++*idx;
465
466 dst = commands[i].deviceLevel ? &devCmdNamesStr : &instCmdNamesStr;
467 *dst += QStringLiteral(" \"");
468 *dst += commands[i].cmd.name;
469 *dst += QStringLiteral("\",\n");
470 }
471
472 if (devCmdNamesStr.count() > 2)
473 devCmdNamesStr = devCmdNamesStr.left(devCmdNamesStr.count() - 2);
474 if (instCmdNamesStr.count() > 2)
475 instCmdNamesStr = instCmdNamesStr.left(instCmdNamesStr.count() - 2);
476
477 const QString str =
478 QString::asprintf(s, preamble.get(licHeaderFn).constData(),
479 instCmdWrapperStr.toUtf8().constData(),
480 instCmdNamesStr.toUtf8().constData(), instIdx,
481 devCmdWrapperStr.toUtf8().constData(),
482 devCmdNamesStr.toUtf8().constData(), commands.count() - instIdx);
483
484 f.write(str.toUtf8());
485
486 return true;
487}
488
489int main(int argc, char **argv)
490{
491 QCoreApplication app(argc, argv);
492 VkSpecParser parser;
493
494 if (argc < 4) {
495 qWarning("Usage: qvkgen input_vk_xml input_license_header output_base\n"
496 " For example: qvkgen vulkan/vk.xml vulkan/qvulkanfunctions.header vulkan/qvulkanfunctions");
497 return 1;
498 }
499
500 parser.setFileName(QString::fromUtf8(argv[1]));
501
502 if (!parser.parse())
503 return 1;
504
505 QList<VkSpecParser::Command> commands = parser.commands();
506 QStringList ignoredFuncs {
507 QStringLiteral("vkCreateInstance"),
508 QStringLiteral("vkDestroyInstance"),
509 QStringLiteral("vkGetInstanceProcAddr")
510 };
511
512 // Filter out extensions and unwanted functions.
513 // The check for the former is rather simplistic for now: skip if the last letter is uppercase...
514 for (int i = 0; i < commands.count(); ++i) {
515 QString name = commands[i].cmd.name;
516 QChar c = name[name.count() - 1];
517 if (c.isUpper() || ignoredFuncs.contains(name))
518 commands.remove(i--);
519 }
520
521 QString licenseHeaderFileName = QString::fromUtf8(argv[2]);
522 QString outputBase = QString::fromUtf8(argv[3]);
523 genVulkanFunctionsH(commands, licenseHeaderFileName, outputBase);
524 genVulkanFunctionsPH(commands, licenseHeaderFileName, outputBase);
525 genVulkanFunctionsPC(commands, licenseHeaderFileName, outputBase);
526
527 return 0;
528}
529