1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include <QtCore/qcoreapplication.h>
5#include <QtCore/qfile.h>
6#include <QtCore/qfileinfo.h>
7#include <QtCore/qlist.h>
8#include <QtCore/qmap.h>
9#include <QtCore/qxmlstream.h>
10
11// generate wrappers for core functions from the following versions
12static const QStringList VERSIONS = {
13 QStringLiteral("VK_VERSION_1_0"), // must be the first and always present
14 QStringLiteral("VK_VERSION_1_1"),
15 QStringLiteral("VK_VERSION_1_2"),
16 QStringLiteral("VK_VERSION_1_3")
17};
18
19class VkSpecParser
20{
21public:
22 bool parse();
23
24 struct TypedName {
25 QString name;
26 QString type;
27 QString typeSuffix;
28 };
29
30 struct Command {
31 TypedName cmd;
32 QList<TypedName> args;
33 bool deviceLevel;
34 };
35
36 QList<Command> commands() const { return m_commands; }
37 QMap<QString, QStringList> versionCommandMapping() const { return m_versionCommandMapping; }
38
39 void setFileName(const QString &fn) { m_fn = fn; }
40
41private:
42 void skip();
43 void parseFeature();
44 void parseFeatureRequire(const QString &versionDefine);
45 void parseCommands();
46 Command parseCommand();
47 TypedName parseParamOrProto(const QString &tag);
48 QString parseName();
49
50 QFile m_file;
51 QXmlStreamReader m_reader;
52 QList<Command> m_commands;
53 QMap<QString, QStringList> m_versionCommandMapping; // "1.0" -> ["vkGetPhysicalDeviceProperties", ...]
54 QString m_fn;
55};
56
57bool VkSpecParser::parse()
58{
59 m_file.setFileName(m_fn);
60 if (!m_file.open(flags: QIODevice::ReadOnly | QIODevice::Text)) {
61 qWarning(msg: "Failed to open %s", qPrintable(m_file.fileName()));
62 return false;
63 }
64 m_reader.setDevice(&m_file);
65
66 m_commands.clear();
67 m_versionCommandMapping.clear();
68
69 while (!m_reader.atEnd()) {
70 m_reader.readNext();
71 if (m_reader.isStartElement()) {
72 if (m_reader.name() == QStringLiteral("commands"))
73 parseCommands();
74 else if (m_reader.name() == QStringLiteral("feature"))
75 parseFeature();
76 }
77 }
78
79 return true;
80}
81
82void VkSpecParser::skip()
83{
84 QString tag = m_reader.name().toString();
85 while (!m_reader.atEnd()) {
86 m_reader.readNext();
87 if (m_reader.isEndElement() && m_reader.name() == tag)
88 break;
89 }
90}
91
92void VkSpecParser::parseFeature()
93{
94 // <feature api="vulkan" name="VK_VERSION_1_0" number="1.0" comment="Vulkan core API interface definitions">
95 // <require comment="Device initialization">
96
97 QString api;
98 QString versionName;
99 for (const QXmlStreamAttribute &attr : m_reader.attributes()) {
100 if (attr.name() == QStringLiteral("api"))
101 api = attr.value().toString().trimmed();
102 else if (attr.name() == QStringLiteral("name"))
103 versionName = attr.value().toString().trimmed();
104 }
105 const bool isVulkan = api == QStringLiteral("vulkan");
106
107 while (!m_reader.atEnd()) {
108 m_reader.readNext();
109 if (m_reader.isEndElement() && m_reader.name() == QStringLiteral("feature"))
110 return;
111 if (m_reader.isStartElement() && m_reader.name() == QStringLiteral("require")) {
112 if (isVulkan)
113 parseFeatureRequire(versionDefine: versionName);
114 }
115 }
116}
117
118void VkSpecParser::parseFeatureRequire(const QString &versionDefine)
119{
120 // <require comment="Device initialization">
121 // <command name="vkCreateInstance"/>
122
123 while (!m_reader.atEnd()) {
124 m_reader.readNext();
125 if (m_reader.isEndElement() && m_reader.name() == QStringLiteral("require"))
126 return;
127 if (m_reader.isStartElement() && m_reader.name() == QStringLiteral("command")) {
128 for (const QXmlStreamAttribute &attr : m_reader.attributes()) {
129 if (attr.name() == QStringLiteral("name"))
130 m_versionCommandMapping[versionDefine].append(t: attr.value().toString().trimmed());
131 }
132 }
133 }
134}
135
136void VkSpecParser::parseCommands()
137{
138 // <commands comment="Vulkan command definitions">
139 // <command successcodes="VK_SUCCESS" ...>
140
141 while (!m_reader.atEnd()) {
142 m_reader.readNext();
143 if (m_reader.isEndElement() && m_reader.name() == QStringLiteral("commands"))
144 return;
145 if (m_reader.isStartElement() && m_reader.name() == QStringLiteral("command")) {
146 const Command c = parseCommand();
147 if (!c.cmd.name.isEmpty()) // skip aliases
148 m_commands.append(t: c);
149 }
150 }
151}
152
153VkSpecParser::Command VkSpecParser::parseCommand()
154{
155 Command c;
156
157 // <command successcodes="VK_SUCCESS" ...>
158 // <proto><type>VkResult</type> <name>vkCreateInstance</name></proto>
159 // <param>const <type>VkInstanceCreateInfo</type>* <name>pCreateInfo</name></param>
160 // <param optional="true">const <type>VkAllocationCallbacks</type>* <name>pAllocator</name></param>
161 // <param><type>VkInstance</type>* <name>pInstance</name></param>
162
163 while (!m_reader.atEnd()) {
164 m_reader.readNext();
165 if (m_reader.isEndElement() && m_reader.name() == QStringLiteral("command"))
166 break;
167 if (m_reader.isStartElement()) {
168 const QString protoStr = QStringLiteral("proto");
169 const QString paramStr = QStringLiteral("param");
170 if (m_reader.name() == protoStr) {
171 c.cmd = parseParamOrProto(tag: protoStr);
172 } else if (m_reader.name() == paramStr) {
173 c.args.append(t: parseParamOrProto(tag: paramStr));
174 } else {
175 skip();
176 }
177 }
178 }
179
180 c.deviceLevel = false;
181 if (!c.args.isEmpty()) {
182 QStringList dispatchableDeviceAndChildTypes {
183 QStringLiteral("VkDevice"),
184 QStringLiteral("VkQueue"),
185 QStringLiteral("VkCommandBuffer")
186 };
187 if (dispatchableDeviceAndChildTypes.contains(str: c.args[0].type)
188 && c.cmd.name != QStringLiteral("vkGetDeviceProcAddr"))
189 {
190 c.deviceLevel = true;
191 }
192 }
193
194 return c;
195}
196
197VkSpecParser::TypedName VkSpecParser::parseParamOrProto(const QString &tag)
198{
199 TypedName t;
200
201 while (!m_reader.atEnd()) {
202 m_reader.readNext();
203 if (m_reader.isEndElement() && m_reader.name() == tag)
204 break;
205 if (m_reader.isStartElement()) {
206 if (m_reader.name() == QStringLiteral("name")) {
207 t.name = parseName();
208 } else if (m_reader.name() != QStringLiteral("type")) {
209 skip();
210 }
211 } else {
212 auto text = m_reader.text().trimmed();
213 if (!text.isEmpty()) {
214 if (text.startsWith(c: u'[')) {
215 t.typeSuffix += text;
216 } else {
217 if (!t.type.isEmpty())
218 t.type += u' ';
219 t.type += text;
220 }
221 }
222 }
223 }
224
225 return t;
226}
227
228QString VkSpecParser::parseName()
229{
230 QString name;
231 while (!m_reader.atEnd()) {
232 m_reader.readNext();
233 if (m_reader.isEndElement() && m_reader.name() == QStringLiteral("name"))
234 break;
235 name += m_reader.text();
236 }
237 return name.trimmed();
238}
239
240QString funcSig(const VkSpecParser::Command &c, const char *className = nullptr)
241{
242 QString s(QString::asprintf(format: "%s %s%s%s", qPrintable(c.cmd.type),
243 (className ? className : ""), (className ? "::" : ""),
244 qPrintable(c.cmd.name)));
245 if (!c.args.isEmpty()) {
246 s += u'(';
247 bool first = true;
248 for (const VkSpecParser::TypedName &a : c.args) {
249 if (!first)
250 s += QStringLiteral(", ");
251 else
252 first = false;
253 s += QString::asprintf(format: "%s%s%s%s", qPrintable(a.type),
254 (a.type.endsWith(c: u'*') ? "" : " "),
255 qPrintable(a.name), qPrintable(a.typeSuffix));
256 }
257 s += u')';
258 }
259 return s;
260}
261
262QString funcCall(const VkSpecParser::Command &c, int idx)
263{
264 // template:
265 // [return] reinterpret_cast<PFN_vkEnumeratePhysicalDevices>(d_ptr->m_funcs[0])(instance, pPhysicalDeviceCount, pPhysicalDevices);
266 QString s = QString::asprintf(format: "%sreinterpret_cast<PFN_%s>(d_ptr->m_funcs[%d])",
267 (c.cmd.type == QStringLiteral("void") ? "" : "return "),
268 qPrintable(c.cmd.name),
269 idx);
270 if (!c.args.isEmpty()) {
271 s += u'(';
272 bool first = true;
273 for (const VkSpecParser::TypedName &a : c.args) {
274 if (!first)
275 s += QStringLiteral(", ");
276 else
277 first = false;
278 s += a.name;
279 }
280 s += u')';
281 }
282 return s;
283}
284
285class Preamble
286{
287public:
288 QByteArray get(const QString &fn);
289
290private:
291 QByteArray m_str;
292} preamble;
293
294QByteArray Preamble::get(const QString &fn)
295{
296 if (!m_str.isEmpty())
297 return m_str;
298
299 QFile f(fn);
300 if (!f.open(flags: QIODevice::ReadOnly | QIODevice::Text)) {
301 qWarning(msg: "Failed to open %s", qPrintable(fn));
302 return m_str;
303 }
304
305 m_str = f.readAll();
306 m_str.replace(before: "FOO", after: "QtGui");
307 m_str += "\n// This file is automatically generated by qvkgen. Do not edit.\n";
308
309 return m_str;
310}
311
312bool genVulkanFunctionsH(const QList<VkSpecParser::Command> &commands,
313 const QMap<QString, QStringList> &versionCommandMapping,
314 const QString &licHeaderFn,
315 const QString &outputBase)
316{
317 QFile f(outputBase + QStringLiteral(".h"));
318 if (!f.open(flags: QIODevice::WriteOnly | QIODevice::Text)) {
319 qWarning(msg: "Failed to write %s", qPrintable(f.fileName()));
320 return false;
321 }
322
323 static const char *s =
324"%s\n"
325"#ifndef QVULKANFUNCTIONS_H\n"
326"#define QVULKANFUNCTIONS_H\n"
327"\n"
328"#if 0\n"
329"#pragma qt_no_master_include\n"
330"#endif\n"
331"\n"
332"#include <QtGui/qtguiglobal.h>\n"
333"\n"
334"#if QT_CONFIG(vulkan) || defined(Q_QDOC)\n"
335"\n"
336"#ifndef VK_NO_PROTOTYPES\n"
337"#define VK_NO_PROTOTYPES\n"
338"#endif\n"
339"#include <vulkan/vulkan.h>\n"
340"\n"
341"#include <QtCore/qscopedpointer.h>\n"
342"\n"
343"QT_BEGIN_NAMESPACE\n"
344"\n"
345"class QVulkanInstance;\n"
346"class QVulkanFunctionsPrivate;\n"
347"class QVulkanDeviceFunctionsPrivate;\n"
348"\n"
349"class Q_GUI_EXPORT QVulkanFunctions\n"
350"{\n"
351"public:\n"
352" ~QVulkanFunctions();\n"
353"\n"
354"%s\n"
355"private:\n"
356" Q_DISABLE_COPY(QVulkanFunctions)\n"
357" QVulkanFunctions(QVulkanInstance *inst);\n"
358"\n"
359" QScopedPointer<QVulkanFunctionsPrivate> d_ptr;\n"
360" friend class QVulkanInstance;\n"
361"};\n"
362"\n"
363"class Q_GUI_EXPORT QVulkanDeviceFunctions\n"
364"{\n"
365"public:\n"
366" ~QVulkanDeviceFunctions();\n"
367"\n"
368"%s\n"
369"private:\n"
370" Q_DISABLE_COPY(QVulkanDeviceFunctions)\n"
371" QVulkanDeviceFunctions(QVulkanInstance *inst, VkDevice device);\n"
372"\n"
373" QScopedPointer<QVulkanDeviceFunctionsPrivate> d_ptr;\n"
374" friend class QVulkanInstance;\n"
375"};\n"
376"\n"
377"QT_END_NAMESPACE\n"
378"\n"
379"#endif // QT_CONFIG(vulkan) || defined(Q_QDOC)\n"
380"\n"
381"#endif // QVULKANFUNCTIONS_H\n";
382
383 QString instCmdStr;
384 QString devCmdStr;
385 for (const QString &version : VERSIONS) {
386 const QStringList &coreFunctionsInVersion = versionCommandMapping[version];
387 instCmdStr += "#if " + version + "\n";
388 devCmdStr += "#if " + version + "\n";
389 for (const VkSpecParser::Command &c : commands) {
390 if (!coreFunctionsInVersion.contains(str: c.cmd.name))
391 continue;
392
393 QString *dst = c.deviceLevel ? &devCmdStr : &instCmdStr;
394 *dst += QStringLiteral(" ");
395 *dst += funcSig(c);
396 *dst += QStringLiteral(";\n");
397 }
398 instCmdStr += "#endif\n";
399 devCmdStr += "#endif\n";
400 }
401
402 f.write(data: QString::asprintf(format: s, preamble.get(fn: licHeaderFn).constData(),
403 instCmdStr.toUtf8().constData(),
404 devCmdStr.toUtf8().constData()).toUtf8());
405
406 return true;
407}
408
409bool genVulkanFunctionsPH(const QList<VkSpecParser::Command> &commands,
410 const QMap<QString, QStringList> &versionCommandMapping,
411 const QString &licHeaderFn,
412 const QString &outputBase)
413{
414 QFile f(outputBase + QStringLiteral("_p.h"));
415 if (!f.open(flags: QIODevice::WriteOnly | QIODevice::Text)) {
416 qWarning(msg: "Failed to write %s", qPrintable(f.fileName()));
417 return false;
418 }
419
420 static const char *s =
421"%s\n"
422"#ifndef QVULKANFUNCTIONS_P_H\n"
423"#define QVULKANFUNCTIONS_P_H\n"
424"\n"
425"//\n"
426"// W A R N I N G\n"
427"// -------------\n"
428"//\n"
429"// This file is not part of the Qt API. It exists purely as an\n"
430"// implementation detail. This header file may change from version to\n"
431"// version without notice, or even be removed.\n"
432"//\n"
433"// We mean it.\n"
434"//\n"
435"\n"
436"#include \"qvulkanfunctions.h\"\n"
437"\n"
438"QT_BEGIN_NAMESPACE\n"
439"\n"
440"class QVulkanInstance;\n"
441"\n"
442"class QVulkanFunctionsPrivate\n"
443"{\n"
444"public:\n"
445" QVulkanFunctionsPrivate(QVulkanInstance *inst);\n"
446"\n"
447" PFN_vkVoidFunction m_funcs[%d];\n"
448"};\n"
449"\n"
450"class QVulkanDeviceFunctionsPrivate\n"
451"{\n"
452"public:\n"
453" QVulkanDeviceFunctionsPrivate(QVulkanInstance *inst, VkDevice device);\n"
454"\n"
455" PFN_vkVoidFunction m_funcs[%d];\n"
456"};\n"
457"\n"
458"QT_END_NAMESPACE\n"
459"\n"
460"#endif // QVULKANFUNCTIONS_P_H\n";
461
462 int devLevelCount = 0;
463 int instLevelCount = 0;
464 for (const QString &version : VERSIONS) {
465 const QStringList &coreFunctionsInVersion = versionCommandMapping[version];
466 for (const VkSpecParser::Command &c : commands) {
467 if (!coreFunctionsInVersion.contains(str: c.cmd.name))
468 continue;
469
470 if (c.deviceLevel)
471 devLevelCount += 1;
472 else
473 instLevelCount += 1;
474 }
475 }
476
477 f.write(data: QString::asprintf(format: s, preamble.get(fn: licHeaderFn).constData(), instLevelCount, devLevelCount).toUtf8());
478
479 return true;
480}
481
482bool genVulkanFunctionsPC(const QList<VkSpecParser::Command> &commands,
483 const QMap<QString, QStringList> &versionCommandMapping,
484 const QString &licHeaderFn,
485 const QString &outputBase)
486{
487 QFile f(outputBase + QStringLiteral("_p.cpp"));
488 if (!f.open(flags: QIODevice::WriteOnly | QIODevice::Text)) {
489 qWarning(msg: "Failed to write %s", qPrintable(f.fileName()));
490 return false;
491 }
492
493 static const char s[] =
494"%s\n"
495"#include \"qvulkanfunctions_p.h\"\n"
496"#include \"qvulkaninstance.h\"\n"
497"\n"
498"#include <QtCore/private/qoffsetstringarray_p.h>\n"
499"\n"
500"QT_BEGIN_NAMESPACE\n"
501"\n%s"
502"QVulkanFunctionsPrivate::QVulkanFunctionsPrivate(QVulkanInstance *inst)\n"
503"{\n"
504" static constexpr auto funcNames = qOffsetStringArray(\n"
505"%s\n"
506" );\n"
507" static_assert(std::extent_v<decltype(m_funcs)> == size_t(funcNames.count()));\n"
508" for (int i = 0; i < funcNames.count(); ++i) {\n"
509" m_funcs[i] = inst->getInstanceProcAddr(funcNames.at(i));\n"
510" if (i < %d && !m_funcs[i])\n"
511" qWarning(\"QVulkanFunctions: Failed to resolve %%s\", funcNames.at(i));\n"
512" }\n"
513"}\n"
514"\n%s"
515"QVulkanDeviceFunctionsPrivate::QVulkanDeviceFunctionsPrivate(QVulkanInstance *inst, VkDevice device)\n"
516"{\n"
517" QVulkanFunctions *f = inst->functions();\n"
518" Q_ASSERT(f);\n\n"
519" static constexpr auto funcNames = qOffsetStringArray(\n"
520"%s\n"
521" );\n"
522" static_assert(std::extent_v<decltype(m_funcs)> == size_t(funcNames.count()));\n"
523" for (int i = 0; i < funcNames.count(); ++i) {\n"
524" m_funcs[i] = f->vkGetDeviceProcAddr(device, funcNames.at(i));\n"
525" if (i < %d && !m_funcs[i])\n"
526" qWarning(\"QVulkanDeviceFunctions: Failed to resolve %%s\", funcNames.at(i));\n"
527" }\n"
528"}\n"
529"\n"
530"QT_END_NAMESPACE\n";
531
532 QString devCmdWrapperStr;
533 QString instCmdWrapperStr;
534 int devIdx = 0;
535 int instIdx = 0;
536 QString devCmdNamesStr;
537 QString instCmdNamesStr;
538 int vulkan10DevCount = 0;
539 int vulkan10InstCount = 0;
540
541 for (const QString &version : VERSIONS) {
542 const QStringList &coreFunctionsInVersion = versionCommandMapping[version];
543 instCmdWrapperStr += "\n#if " + version + "\n";
544 devCmdWrapperStr += "\n#if " + version + "\n";
545 for (const VkSpecParser::Command &c : commands) {
546 if (!coreFunctionsInVersion.contains(str: c.cmd.name))
547 continue;
548
549 QString *dst = c.deviceLevel ? &devCmdWrapperStr : &instCmdWrapperStr;
550 int *idx = c.deviceLevel ? &devIdx : &instIdx;
551 *dst += funcSig(c, className: c.deviceLevel ? "QVulkanDeviceFunctions" : "QVulkanFunctions");
552 *dst += QString(QStringLiteral("\n{\n Q_ASSERT(d_ptr->m_funcs[%1]);\n ")).arg(a: *idx);
553 *dst += funcCall(c, idx: *idx);
554 *dst += QStringLiteral(";\n}\n\n");
555 *idx += 1;
556
557 dst = c.deviceLevel ? &devCmdNamesStr : &instCmdNamesStr;
558 *dst += QStringLiteral(" \"");
559 *dst += c.cmd.name;
560 *dst += QStringLiteral("\",\n");
561 }
562 if (version == QStringLiteral("VK_VERSION_1_0")) {
563 vulkan10InstCount = instIdx;
564 vulkan10DevCount = devIdx;
565 }
566 instCmdWrapperStr += "#endif\n\n";
567 devCmdWrapperStr += "#endif\n\n";
568 }
569
570 if (devCmdNamesStr.size() > 2)
571 devCmdNamesStr.chop(n: 2);
572 if (instCmdNamesStr.size() > 2)
573 instCmdNamesStr.chop(n: 2);
574
575 const QString str =
576 QString::asprintf(format: s, preamble.get(fn: licHeaderFn).constData(),
577 instCmdWrapperStr.toUtf8().constData(),
578 instCmdNamesStr.toUtf8().constData(), vulkan10InstCount,
579 devCmdWrapperStr.toUtf8().constData(),
580 devCmdNamesStr.toUtf8().constData(), vulkan10DevCount);
581
582 f.write(data: str.toUtf8());
583
584 return true;
585}
586
587int main(int argc, char **argv)
588{
589 QCoreApplication app(argc, argv);
590 VkSpecParser parser;
591
592 if (argc < 4) {
593 qWarning(msg: "Usage: qvkgen input_vk_xml input_license_header output_base\n"
594 " For example: qvkgen vulkan/vk.xml vulkan/qvulkanfunctions.header vulkan/qvulkanfunctions");
595 return 1;
596 }
597
598 parser.setFileName(QString::fromUtf8(utf8: argv[1]));
599
600 if (!parser.parse())
601 return 1;
602
603 // Now we have a list of functions (commands), including extensions, and a
604 // table of Version (1.0, 1.1, 1.2) -> Core functions in that version.
605 QList<VkSpecParser::Command> commands = parser.commands();
606 QMap<QString, QStringList> versionCommandMapping = parser.versionCommandMapping();
607
608 QStringList ignoredFuncs {
609 QStringLiteral("vkCreateInstance"),
610 QStringLiteral("vkDestroyInstance"),
611 QStringLiteral("vkGetInstanceProcAddr"),
612 QStringLiteral("vkEnumerateInstanceVersion")
613 };
614 for (int i = 0; i < commands.size(); ++i) {
615 if (ignoredFuncs.contains(str: commands[i].cmd.name))
616 commands.remove(i: i--);
617 }
618
619 QString licenseHeaderFileName = QString::fromUtf8(utf8: argv[2]);
620 QString outputBase = QString::fromUtf8(utf8: argv[3]);
621 genVulkanFunctionsH(commands, versionCommandMapping, licHeaderFn: licenseHeaderFileName, outputBase);
622 genVulkanFunctionsPH(commands, versionCommandMapping, licHeaderFn: licenseHeaderFileName, outputBase);
623 genVulkanFunctionsPC(commands, versionCommandMapping, licHeaderFn: licenseHeaderFileName, outputBase);
624
625 return 0;
626}
627

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