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