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/qvector.h>
31#include <QtCore/qfile.h>
32#include <QtCore/qfileinfo.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 QVector<TypedName> args;
49 bool deviceLevel;
50 };
51
52 QVector<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 QVector<Command> m_commands;
66 QString m_fn;
67};
68
69bool VkSpecParser::parse()
70{
71 m_file.setFileName(m_fn);
72 if (!m_file.open(flags: QIODevice::ReadOnly | QIODevice::Text)) {
73 qWarning(msg: "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() == "command")
108 m_commands.append(t: 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(tag: protoStr);
125 } else if (m_reader.name() == paramStr) {
126 c.args.append(t: parseParamOrProto(tag: 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(str: 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 QStringRef text = m_reader.text().trimmed();
166 if (!text.isEmpty()) {
167 if (text.startsWith(c: 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(format: "%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(format: "%s%s%s%s", qPrintable(a.type),
207 (a.type.endsWith(c: 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(format: "%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(flags: QIODevice::ReadOnly | QIODevice::Text)) {
254 qWarning(msg: "Failed to open %s", qPrintable(fn));
255 return m_str;
256 }
257
258 m_str = f.readAll();
259 m_str.replace(before: "FOO", after: "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 QVector<VkSpecParser::Command> &commands, const QString &licHeaderFn, const QString &outputBase)
266{
267 QFile f(outputBase + QStringLiteral(".h"));
268 if (!f.open(flags: QIODevice::WriteOnly | QIODevice::Text)) {
269 qWarning(msg: "Failed to write %s", qPrintable(f.fileName()));
270 return false;
271 }
272
273 static const char *s =
274"%s\n"
275"#ifndef QVULKANFUNCTIONS_H\n"
276"#define QVULKANFUNCTIONS_H\n"
277"\n"
278"#include <QtGui/qtguiglobal.h>\n"
279"\n"
280"#if QT_CONFIG(vulkan) || defined(Q_CLANG_QDOC)\n"
281"\n"
282"#ifndef VK_NO_PROTOTYPES\n"
283"#define VK_NO_PROTOTYPES\n"
284"#endif\n"
285"#include <vulkan/vulkan.h>\n"
286"\n"
287"#include <QtCore/qscopedpointer.h>\n"
288"\n"
289"QT_BEGIN_NAMESPACE\n"
290"\n"
291"class QVulkanInstance;\n"
292"class QVulkanFunctionsPrivate;\n"
293"class QVulkanDeviceFunctionsPrivate;\n"
294"\n"
295"class Q_GUI_EXPORT QVulkanFunctions\n"
296"{\n"
297"public:\n"
298" ~QVulkanFunctions();\n"
299"\n"
300"%s\n"
301"private:\n"
302" Q_DISABLE_COPY(QVulkanFunctions)\n"
303" QVulkanFunctions(QVulkanInstance *inst);\n"
304"\n"
305" QScopedPointer<QVulkanFunctionsPrivate> d_ptr;\n"
306" friend class QVulkanInstance;\n"
307"};\n"
308"\n"
309"class Q_GUI_EXPORT QVulkanDeviceFunctions\n"
310"{\n"
311"public:\n"
312" ~QVulkanDeviceFunctions();\n"
313"\n"
314"%s\n"
315"private:\n"
316" Q_DISABLE_COPY(QVulkanDeviceFunctions)\n"
317" QVulkanDeviceFunctions(QVulkanInstance *inst, VkDevice device);\n"
318"\n"
319" QScopedPointer<QVulkanDeviceFunctionsPrivate> d_ptr;\n"
320" friend class QVulkanInstance;\n"
321"};\n"
322"\n"
323"QT_END_NAMESPACE\n"
324"\n"
325"#endif // QT_CONFIG(vulkan) || defined(Q_CLANG_QDOC)\n"
326"\n"
327"#endif // QVULKANFUNCTIONS_H\n";
328
329 QString instCmdStr;
330 QString devCmdStr;
331 for (const VkSpecParser::Command &c : commands) {
332 QString *dst = c.deviceLevel ? &devCmdStr : &instCmdStr;
333 *dst += QStringLiteral(" ");
334 *dst += funcSig(c);
335 *dst += QStringLiteral(";\n");
336 }
337
338 f.write(data: QString::asprintf(format: s, preamble.get(fn: licHeaderFn).constData(),
339 instCmdStr.toUtf8().constData(),
340 devCmdStr.toUtf8().constData()).toUtf8());
341
342 return true;
343}
344
345bool genVulkanFunctionsPH(const QVector<VkSpecParser::Command> &commands, const QString &licHeaderFn, const QString &outputBase)
346{
347 QFile f(outputBase + QStringLiteral("_p.h"));
348 if (!f.open(flags: QIODevice::WriteOnly | QIODevice::Text)) {
349 qWarning(msg: "Failed to write %s", qPrintable(f.fileName()));
350 return false;
351 }
352
353 static const char *s =
354"%s\n"
355"#ifndef QVULKANFUNCTIONS_P_H\n"
356"#define QVULKANFUNCTIONS_P_H\n"
357"\n"
358"//\n"
359"// W A R N I N G\n"
360"// -------------\n"
361"//\n"
362"// This file is not part of the Qt API. It exists purely as an\n"
363"// implementation detail. This header file may change from version to\n"
364"// version without notice, or even be removed.\n"
365"//\n"
366"// We mean it.\n"
367"//\n"
368"\n"
369"#include \"qvulkanfunctions.h\"\n"
370"\n"
371"QT_BEGIN_NAMESPACE\n"
372"\n"
373"class QVulkanInstance;\n"
374"\n"
375"class QVulkanFunctionsPrivate\n"
376"{\n"
377"public:\n"
378" QVulkanFunctionsPrivate(QVulkanInstance *inst);\n"
379"\n"
380" PFN_vkVoidFunction m_funcs[%d];\n"
381"};\n"
382"\n"
383"class QVulkanDeviceFunctionsPrivate\n"
384"{\n"
385"public:\n"
386" QVulkanDeviceFunctionsPrivate(QVulkanInstance *inst, VkDevice device);\n"
387"\n"
388" PFN_vkVoidFunction m_funcs[%d];\n"
389"};\n"
390"\n"
391"QT_END_NAMESPACE\n"
392"\n"
393"#endif // QVULKANFUNCTIONS_P_H\n";
394
395 const int devLevelCount = std::count_if(first: commands.cbegin(), last: commands.cend(),
396 pred: [](const VkSpecParser::Command &c) { return c.deviceLevel; });
397 const int instLevelCount = commands.count() - devLevelCount;
398
399 f.write(data: QString::asprintf(format: s, preamble.get(fn: licHeaderFn).constData(), instLevelCount, devLevelCount).toUtf8());
400
401 return true;
402}
403
404bool genVulkanFunctionsPC(const QVector<VkSpecParser::Command> &commands, const QString &licHeaderFn, const QString &outputBase)
405{
406 QFile f(outputBase + QStringLiteral("_p.cpp"));
407 if (!f.open(flags: QIODevice::WriteOnly | QIODevice::Text)) {
408 qWarning(msg: "Failed to write %s", qPrintable(f.fileName()));
409 return false;
410 }
411
412 static const char *s =
413"%s\n"
414"#include \"qvulkanfunctions_p.h\"\n"
415"#include \"qvulkaninstance.h\"\n"
416"\n"
417"QT_BEGIN_NAMESPACE\n"
418"\n%s"
419"QVulkanFunctionsPrivate::QVulkanFunctionsPrivate(QVulkanInstance *inst)\n"
420"{\n"
421" static const char *funcNames[] = {\n"
422"%s\n"
423" };\n"
424" for (int i = 0; i < %d; ++i) {\n"
425" m_funcs[i] = inst->getInstanceProcAddr(funcNames[i]);\n"
426" if (!m_funcs[i])\n"
427" qWarning(\"QVulkanFunctions: Failed to resolve %%s\", funcNames[i]);\n"
428" }\n"
429"}\n"
430"\n%s"
431"QVulkanDeviceFunctionsPrivate::QVulkanDeviceFunctionsPrivate(QVulkanInstance *inst, VkDevice device)\n"
432"{\n"
433" QVulkanFunctions *f = inst->functions();\n"
434" Q_ASSERT(f);\n\n"
435" static const char *funcNames[] = {\n"
436"%s\n"
437" };\n"
438" for (int i = 0; i < %d; ++i) {\n"
439" m_funcs[i] = f->vkGetDeviceProcAddr(device, funcNames[i]);\n"
440" if (!m_funcs[i])\n"
441" qWarning(\"QVulkanDeviceFunctions: Failed to resolve %%s\", funcNames[i]);\n"
442" }\n"
443"}\n"
444"\n"
445"QT_END_NAMESPACE\n";
446
447 QString devCmdWrapperStr;
448 QString instCmdWrapperStr;
449 int devIdx = 0;
450 int instIdx = 0;
451 QString devCmdNamesStr;
452 QString instCmdNamesStr;
453
454 for (int i = 0; i < commands.count(); ++i) {
455 QString *dst = commands[i].deviceLevel ? &devCmdWrapperStr : &instCmdWrapperStr;
456 int *idx = commands[i].deviceLevel ? &devIdx : &instIdx;
457 *dst += funcSig(c: commands[i], className: commands[i].deviceLevel ? "QVulkanDeviceFunctions" : "QVulkanFunctions");
458 *dst += QString(QStringLiteral("\n{\n Q_ASSERT(d_ptr->m_funcs[%1]);\n ")).arg(a: *idx);
459 *dst += funcCall(c: commands[i], idx: *idx);
460 *dst += QStringLiteral(";\n}\n\n");
461 ++*idx;
462
463 dst = commands[i].deviceLevel ? &devCmdNamesStr : &instCmdNamesStr;
464 *dst += QStringLiteral(" \"");
465 *dst += commands[i].cmd.name;
466 *dst += QStringLiteral("\",\n");
467 }
468
469 if (devCmdNamesStr.count() > 2)
470 devCmdNamesStr = devCmdNamesStr.left(n: devCmdNamesStr.count() - 2);
471 if (instCmdNamesStr.count() > 2)
472 instCmdNamesStr = instCmdNamesStr.left(n: instCmdNamesStr.count() - 2);
473
474 const QString str =
475 QString::asprintf(format: s, preamble.get(fn: licHeaderFn).constData(),
476 instCmdWrapperStr.toUtf8().constData(),
477 instCmdNamesStr.toUtf8().constData(), instIdx,
478 devCmdWrapperStr.toUtf8().constData(),
479 devCmdNamesStr.toUtf8().constData(), commands.count() - instIdx);
480
481 f.write(data: str.toUtf8());
482
483 return true;
484}
485
486int main(int argc, char **argv)
487{
488 QCoreApplication app(argc, argv);
489 VkSpecParser parser;
490
491 if (argc < 4) {
492 qWarning(msg: "Usage: qvkgen input_vk_xml input_license_header output_base\n"
493 " For example: qvkgen vulkan/vk.xml vulkan/qvulkanfunctions.header vulkan/qvulkanfunctions");
494 return 1;
495 }
496
497 parser.setFileName(QString::fromUtf8(str: argv[1]));
498
499 if (!parser.parse())
500 return 1;
501
502 QVector<VkSpecParser::Command> commands = parser.commands();
503 QStringList ignoredFuncs {
504 QStringLiteral("vkCreateInstance"),
505 QStringLiteral("vkDestroyInstance"),
506 QStringLiteral("vkGetInstanceProcAddr")
507 };
508
509 // Filter out extensions and unwanted functions.
510 // The check for the former is rather simplistic for now: skip if the last letter is uppercase...
511 for (int i = 0; i < commands.count(); ++i) {
512 QString name = commands[i].cmd.name;
513 QChar c = name[name.count() - 1];
514 if (c.isUpper() || ignoredFuncs.contains(str: name))
515 commands.remove(i: i--);
516 }
517
518 QString licenseHeaderFileName = QString::fromUtf8(str: argv[2]);
519 QString outputBase = QString::fromUtf8(str: argv[3]);
520 genVulkanFunctionsH(commands, licHeaderFn: licenseHeaderFileName, outputBase);
521 genVulkanFunctionsPH(commands, licHeaderFn: licenseHeaderFileName, outputBase);
522 genVulkanFunctionsPC(commands, licHeaderFn: licenseHeaderFileName, outputBase);
523
524 return 0;
525}
526

source code of qtbase/src/tools/qvkgen/qvkgen.cpp