• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3 -i
2#
3# Copyright 2020-2024 The Khronos Group Inc.
4#
5# SPDX-License-Identifier: Apache-2.0
6
7# Description:
8# -----------
9# This script generates a full schema definition from the vk.xml.
10
11import os
12import re
13from generator import (GeneratorOptions, OutputGenerator, noneStr,
14                       regSortFeatures, write)
15
16
17headerString = "\
18{\n\
19\"$schema\": \"http://json-schema.org/draft-04/schema#\", \n\
20\"id\": \"https://schema.khronos.org/vulkan/vk.json#\",\n\
21\"title\": \"JSON schema for Vulkan SC\",\n\
22\"description\": \"Schema for representing entire vk.xml as a schema.\",\n\
23\"type\": \"object\",\n\
24\"additionalProperties\": true,\n\
25\"definitions\": {\
26"
27
28basetypeString = "\
29    \"$schema\": {\"type\": \"string\", \"format\": \"uri\"},\n\
30    \"uint8_t\": {\"type\": \"integer\", \"minimum\": 0, \"maximum\": 255},\n\
31    \"int32_t\": {\"type\": \"integer\", \"minimum\": -2147483648, \"maximum\": 2147483647},\n\
32    \"uint32_t\": {\"type\": \"integer\", \"minimum\": 0, \"maximum\": 4294967295},\n\
33    \"uint64_t\": {\"oneOf\": [{\"enum\": [\"\"]},{\"type\": \"integer\"}]},\n\
34    \"char\": {\"type\": \"string\"},\n\
35    \"float\": {\"type\": \"number\"},\n\
36    \"size_t\": {\"$ref\": \"#/definitions/uint32_t\"},\n\
37    \"enum\": {\"type\": \"string\"},\n\
38    \"void\": {\"enum\": [\"NULL\", \"\"]},\
39"
40
41class SchemaGeneratorOptions(GeneratorOptions):
42    """SchemaGeneratorOptions - subclass of GeneratorOptions.
43
44    Adds options used by SchemaOutputGenerator objects during C language header
45    generation."""
46
47    def __init__(self,
48                 prefixText="",
49                 genFuncPointers=True,
50                 protectFile=True,
51                 protectFeature=True,
52                 protectProto=None,
53                 protectProtoStr=None,
54                 apicall='',
55                 apientry='',
56                 apientryp='',
57                 indentFuncProto=True,
58                 indentFuncPointer=False,
59                 alignFuncParam=0,
60                 genEnumBeginEndRange=False,
61                 genAliasMacro=False,
62                 aliasMacro='',
63                 **kwargs
64                 ):
65
66        GeneratorOptions.__init__(self, **kwargs)
67
68        self.prefixText = prefixText
69        """list of strings to prefix generated header with (usually a copyright statement + calling convention macros)."""
70
71        self.genFuncPointers = genFuncPointers
72        """True if function pointer typedefs should be generated"""
73
74        self.protectFile = protectFile
75        """True if multiple inclusion protection should be generated (based on the filename) around the entire header."""
76
77        self.protectFeature = protectFeature
78        """True if #ifndef..#endif protection should be generated around a feature interface in the header file."""
79
80        self.protectProto = protectProto
81        """If conditional protection should be generated around prototype declarations, set to either '#ifdef' to require opt-in (#ifdef protectProtoStr) or '#ifndef' to require opt-out (#ifndef protectProtoStr). Otherwise set to None."""
82
83        self.protectProtoStr = protectProtoStr
84        """#ifdef/#ifndef symbol to use around prototype declarations, if protectProto is set"""
85
86        self.apicall = apicall
87        """string to use for the function declaration prefix, such as APICALL on Windows."""
88
89        self.apientry = apientry
90        """string to use for the calling convention macro, in typedefs, such as APIENTRY."""
91
92        self.apientryp = apientryp
93        """string to use for the calling convention macro in function pointer typedefs, such as APIENTRYP."""
94
95        self.indentFuncProto = indentFuncProto
96        """True if prototype declarations should put each parameter on a separate line"""
97
98        self.indentFuncPointer = indentFuncPointer
99        """True if typedefed function pointers should put each parameter on a separate line"""
100
101        self.alignFuncParam = alignFuncParam
102        """if nonzero and parameters are being put on a separate line, align parameter names at the specified column"""
103
104        self.genEnumBeginEndRange = genEnumBeginEndRange
105        """True if BEGIN_RANGE / END_RANGE macros should be generated for enumerated types"""
106
107        self.genAliasMacro = genAliasMacro
108        """True if the OpenXR alias macro should be generated for aliased types (unclear what other circumstances this is useful)"""
109
110        self.aliasMacro = aliasMacro
111        """alias macro to inject when genAliasMacro is True"""
112
113        self.codeGenerator = True
114        """True if this generator makes compilable code"""
115
116
117class SchemaOutputGenerator(OutputGenerator):
118    # This is an ordered list of sections in the header file.
119    TYPE_SECTIONS = ['basetype', 'handle', 'enum', 'group', 'bitmask', 'struct']
120    ALL_SECTIONS = TYPE_SECTIONS
121
122    def __init__(self, *args, **kwargs):
123        super().__init__(*args, **kwargs)
124        # Internal state - accumulators for different inner block text
125        self.sections = {section: [] for section in self.ALL_SECTIONS}
126        self.feature_not_empty = False
127        self.may_alias = None
128
129    def beginFile(self, genOpts):
130        OutputGenerator.beginFile(self, genOpts)
131
132        # Write schema header
133        write(headerString, file=self.outFile)
134        write(basetypeString, file=self.outFile)
135
136    def endFile(self):
137        write("    \"VkLastStructure\": {", file=self.outFile)
138        write("    }", file=self.outFile)
139        write("  }", file=self.outFile)
140        write("}", file=self.outFile)
141
142        # Finish processing in superclass
143        OutputGenerator.endFile(self)
144
145    def beginFeature(self, interface, emit):
146        OutputGenerator.beginFeature(self, interface, emit)
147        self.sections = {section: [] for section in self.ALL_SECTIONS}
148        self.feature_not_empty = False
149
150    def endFeature(self):
151        if self.emit:
152            if self.feature_not_empty:
153                if self.genOpts.conventions.writeFeature(self.featureExtraProtect, self.genOpts.filename):
154
155                    for section in self.TYPE_SECTIONS:
156                        contents = self.sections[section]
157                        if contents:
158                            write('\n'.join(contents), file=self.outFile)
159
160        # Finish processing in superclass
161        OutputGenerator.endFeature(self)
162
163    def appendSection(self, section, text):
164        self.sections[section].append(text)
165        self.feature_not_empty = True
166
167    def genType(self, typeinfo, name, alias):
168        OutputGenerator.genType(self, typeinfo, name, alias)
169        typeElem = typeinfo.elem
170        body = ""
171
172        category = typeElem.get('category')
173        if category == 'funcpointer':
174            section = 'struct'
175        else:
176            section = category
177
178        if category in ('struct', 'union'):
179            self.genStruct(typeinfo, name, alias)
180
181        elif category == 'handle':
182            for elem in typeElem:
183                if elem.tag == 'name':
184                    body += "    \"" + elem.text + "\": {\"$ref\": \"#/definitions/uint64_t" + "\"},"
185
186        elif category in ('bitmask','basetype'):
187            storeType = ""
188            section = 'bitmask'
189
190            for elem in typeElem:
191                if elem.tag == 'type':
192                    storeType = elem.text
193
194                if elem.tag == 'name':
195                    if elem.text == "VkBool32":
196                        body += "    \"" + elem.text + "\": {\"oneOf\": [{\"$ref\": \"#/definitions/" + storeType + "\"},{\"enum\": [\"VK_TRUE\", \"VK_FALSE\"]}]},"
197                    elif elem.text == "VkFlags":
198                        body += "    \"" + elem.text + "\": {\"oneOf\": [{\"$ref\": \"#/definitions/" + storeType + "\"},{\"$ref\": \"#/definitions/enum\"}]},"
199                    else:
200                        body += "    \"" + elem.text + "\": {\"$ref\": \"#/definitions/" + storeType + "\"},"
201
202        if body:
203            self.appendSection(section, body)
204
205    def genMemberSchema(self, structure, param):
206        paramdecl = ""
207        storeType = ""
208        isArr = param.get('len') not in (None, "null-terminated")
209        isPtr = False
210
211        for elem in param:
212            text = noneStr(elem.text)
213            tail = noneStr(elem.tail)
214
215            #TODO: In the actual json data, we inline the pNext structs by checking what they point to, at runtime.
216            #      This is however not possible to be represented in the schema. So, the plan is to not represent the
217            #      pNext structs altogether or to indicate that these would be represented at runtime.
218            #if elem.text != 'pNext' and elem.text != 'sType' and elem.text != 'VkStructureType' and elem.text != 'void' and elem.text != 'const':
219            if 1:
220                if elem.tag == 'type':
221                    storeType = text
222                    if '*' in tail:
223                        isPtr = True
224
225                if elem.tag == 'name':
226                    paramdecl += "            \"" + text + "\": "
227                    if isPtr and text != "pNext":
228                        paramdecl += "{\"oneOf\": [{\"$ref\": \"#/definitions/void\"},"
229
230                    if isArr or tail.startswith('['):
231                        # TODO: How to get maxCount here?
232                        paramdecl += " {\"type\": \"array\", \"minItems\": 0, \"maxItems\": 255, \"items\": {\"$ref\": \"#/definitions/"
233                        if (structure == "VkPipelineLayoutCreateInfo" and text == "pSetLayouts") or \
234                           (structure == "VkDescriptorSetLayoutBinding" and text == "pImmutableSamplers") or \
235                           (structure == "VkSamplerYcbcrConversionInfo" and text == "conversion"):
236                            paramdecl += "char\"}}"
237                        elif (storeType == "void"):
238                            # void* data can be NULL, an array of uint8_t data, or a Base64-encoded string
239                            paramdecl += "uint8_t\"}}, {\"type\": \"string\"}"
240                        else:
241                            paramdecl += storeType + "\"}}"
242                    else:
243                        paramdecl += "{\"$ref\": \"#/definitions/"
244                        paramdecl += storeType + "\"}"
245
246                    if isPtr and text != "pNext":
247                        paramdecl += "]}"
248                    isPtr = False
249            else:
250                return ""
251
252        return paramdecl
253
254    def genStruct(self, typeinfo, typeName, alias):
255        OutputGenerator.genStruct(self, typeinfo, typeName, alias)
256        body = ""
257        typeElem = typeinfo.elem
258
259        if alias:
260            return
261        else:
262            body = ''
263            body += "    \"" + typeName + "\": {\n"
264            body += "        \"type\": \"object\",\n"
265            body += "        \"additionalProperties\": false,\n"
266            body += "        \"properties\": {\n"
267
268            count = 0
269            numMembers = len(typeElem.findall('.//member'))
270
271            for member in typeElem.findall('.//member'):
272                count = count + 1
273
274                genText = self.genMemberSchema(typeName, member)
275                body += genText
276
277                if count < numMembers and genText != "":
278                    body += ','
279                    body += '\n'
280            body += "\n        }\n"
281        body += "    },\n"
282
283        self.appendSection('struct', body)
284
285    def genGroup(self, groupinfo, groupName, alias=None):
286        OutputGenerator.genGroup(self, groupinfo, groupName, alias)
287        groupElem = groupinfo.elem
288        body = ""
289
290        section = 'enum'
291        body += "    \"" + groupName + "\": {\"$ref\": \"#/definitions/enum"+ "\"},"
292
293        self.appendSection(section, body)
294
295