• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright © 2020 Hoe Hao Cheng
2#
3# Permission is hereby granted, free of charge, to any person obtaining a
4# copy of this software and associated documentation files (the "Software"),
5# to deal in the Software without restriction, including without limitation
6# the rights to use, copy, modify, merge, publish, distribute, sublicense,
7# and/or sell copies of the Software, and to permit persons to whom the
8# Software is furnished to do so, subject to the following conditions:
9#
10# The above copyright notice and this permission notice (including the next
11# paragraph) shall be included in all copies or substantial portions of the
12# Software.
13#
14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
17# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20# IN THE SOFTWARE.
21#
22
23import re
24from xml.etree import ElementTree
25from typing import List,Tuple
26
27class Version:
28    device_version = (1,0,0)
29    struct_version = (1,0)
30
31    def __init__(self, version, struct=()):
32        self.device_version = version
33
34        if not struct:
35            self.struct_version = (version[0], version[1])
36        else:
37            self.struct_version = struct
38
39    # e.g. "VK_MAKE_VERSION(1,2,0)"
40    def version(self):
41        return ("VK_MAKE_VERSION("
42               + str(self.device_version[0])
43               + ","
44               + str(self.device_version[1])
45               + ","
46               + str(self.device_version[2])
47               + ")")
48
49    # e.g. "10"
50    def struct(self):
51        return (str(self.struct_version[0])+str(self.struct_version[1]))
52
53    # the sType of the extension's struct
54    # e.g. VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT
55    # for VK_EXT_transform_feedback and struct="FEATURES"
56    def stype(self, struct: str):
57        return ("VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_"
58                + str(self.struct_version[0]) + "_" + str(self.struct_version[1])
59                + '_' + struct)
60
61class Extension:
62    name           = None
63    alias          = None
64    is_required    = False
65    is_nonstandard = False
66    enable_conds   = None
67    core_since     = None
68
69    # these are specific to zink_device_info.py:
70    has_properties      = False
71    has_features        = False
72    guard               = False
73    features_promoted   = False
74    properties_promoted = False
75
76
77    # these are specific to zink_instance.py:
78    platform_guard = None
79
80    def __init__(self, name, alias="", required=False, nonstandard=False,
81                 properties=False, features=False, conditions=None, guard=False):
82        self.name = name
83        self.alias = alias
84        self.is_required = required
85        self.is_nonstandard = nonstandard
86        self.has_properties = properties
87        self.has_features = features
88        self.enable_conds = conditions
89        self.guard = guard
90
91        if alias == "" and (properties == True or features == True):
92            raise RuntimeError("alias must be available when properties and/or features are used")
93
94    # e.g.: "VK_EXT_robustness2" -> "robustness2"
95    def pure_name(self):
96        return '_'.join(self.name.split('_')[2:])
97
98    # e.g.: "VK_EXT_robustness2" -> "EXT_robustness2"
99    def name_with_vendor(self):
100        return self.name[3:]
101
102    # e.g.: "VK_EXT_robustness2" -> "Robustness2"
103    def name_in_camel_case(self):
104        return "".join([x.title() for x in self.name.split('_')[2:]])
105
106    # e.g.: "VK_EXT_robustness2" -> "VK_EXT_ROBUSTNESS_2"
107    def name_in_snake_uppercase(self):
108        def replace(original):
109            # we do not split the types into two, e.g. INT_32
110            match_types = re.match(".*(int|float)(8|16|32|64)$", original)
111
112            # do not match win32
113            match_os = re.match(".*win32$", original)
114
115            # try to match extensions with alphanumeric names, like robustness2
116            match_alphanumeric = re.match("([a-z]+)(\d+)", original)
117
118            if match_types is not None or match_os is not None:
119                return original.upper()
120
121            if match_alphanumeric is not None:
122                return (match_alphanumeric[1].upper()
123                        + '_'
124                        + match_alphanumeric[2])
125
126            return original.upper()
127
128        replaced = list(map(replace, self.name.split('_')))
129        return '_'.join(replaced)
130
131    # e.g.: "VK_EXT_robustness2" -> "ROBUSTNESS_2"
132    def pure_name_in_snake_uppercase(self):
133        return '_'.join(self.name_in_snake_uppercase().split('_')[2:])
134
135    # e.g.: "VK_EXT_robustness2" -> "VK_EXT_ROBUSTNESS_2_EXTENSION_NAME"
136    def extension_name(self):
137        return self.name_in_snake_uppercase() + "_EXTENSION_NAME"
138
139    # generate a C string literal for the extension
140    def extension_name_literal(self):
141        return '"' + self.name + '"'
142
143    # get the field in zink_device_info that refers to the extension's
144    # feature/properties struct
145    # e.g. rb2_<suffix> for VK_EXT_robustness2
146    def field(self, suffix: str):
147        return self.alias + '_' + suffix
148
149    def physical_device_struct(self, struct: str):
150        if self.name_in_camel_case().endswith(struct):
151            struct = ""
152
153        return ("VkPhysicalDevice"
154                + self.name_in_camel_case()
155                + struct
156                + self.vendor())
157
158    # the sType of the extension's struct
159    # e.g. VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT
160    # for VK_EXT_transform_feedback and struct="FEATURES"
161    def stype(self, struct: str):
162        return ("VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_"
163                + self.pure_name_in_snake_uppercase()
164                + '_' + struct + '_'
165                + self.vendor())
166
167    # e.g. EXT in VK_EXT_robustness2
168    def vendor(self):
169        return self.name.split('_')[1]
170
171# Type aliases
172Layer = Extension
173
174class ExtensionRegistryEntry:
175    # type of extension - right now it's either "instance" or "device"
176    ext_type          = ""
177    # the version in which the extension is promoted to core VK
178    promoted_in       = None
179    # functions added by the extension are referred to as "commands" in the registry
180    device_commands   = None
181    pdevice_commands  = None
182    instance_commands = None
183    constants         = None
184    features_struct   = None
185    features_fields   = None
186    features_promoted = False
187    properties_struct = None
188    properties_fields = None
189    properties_promoted = False
190    # some instance extensions are locked behind certain platforms
191    platform_guard    = ""
192
193class ExtensionRegistry:
194    # key = extension name, value = registry entry
195    registry = dict()
196
197    def __init__(self, vkxml_path: str):
198        vkxml = ElementTree.parse(vkxml_path)
199
200        commands_type = dict()
201        command_aliases = dict()
202        platform_guards = dict()
203        struct_aliases = dict()
204
205        for cmd in vkxml.findall("commands/command"):
206            name = cmd.find("./proto/name")
207
208            if name is not None and name.text:
209                commands_type[name.text] = cmd.find("./param/type").text
210            elif cmd.get("name") is not None:
211                command_aliases[cmd.get("name")] = cmd.get("alias")
212
213        for typ in vkxml.findall("types/type"):
214            if typ.get("category") != "struct":
215                continue
216
217            name = typ.get("name")
218            alias = typ.get("alias")
219
220            if name and alias:
221                struct_aliases[name] = alias
222
223        for (cmd, alias) in command_aliases.items():
224            commands_type[cmd] = commands_type[alias]
225
226        for platform in vkxml.findall("platforms/platform"):
227            name = platform.get("name")
228            guard = platform.get("protect")
229            platform_guards[name] = guard
230
231        for ext in vkxml.findall("extensions/extension"):
232            # Reserved extensions are marked with `supported="disabled"`
233            if ext.get("supported") == "disabled":
234                continue
235
236            name = ext.attrib["name"]
237
238            entry = ExtensionRegistryEntry()
239            entry.ext_type = ext.attrib["type"]
240            entry.promoted_in = self.parse_promotedto(ext.get("promotedto"))
241
242            entry.device_commands = []
243            entry.pdevice_commands = []
244            entry.instance_commands = []
245            entry.features_fields = []
246            entry.properties_fields = []
247
248            for cmd in ext.findall("require/command"):
249                cmd_name = cmd.get("name")
250                if cmd_name:
251                    if commands_type[cmd_name] in ("VkDevice", "VkCommandBuffer", "VkQueue"):
252                        entry.device_commands.append(cmd_name)
253                    elif commands_type[cmd_name] in ("VkPhysicalDevice"):
254                        entry.pdevice_commands.append(cmd_name)
255                    else:
256                        entry.instance_commands.append(cmd_name)
257
258            entry.constants = []
259            for enum in ext.findall("require/enum"):
260                enum_name = enum.get("name")
261                enum_extends = enum.get("extends")
262                # we are only interested in VK_*_EXTENSION_NAME, which does not
263                # have an "extends" attribute
264                if not enum_extends:
265                    entry.constants.append(enum_name)
266
267            for ty in ext.findall("require/type"):
268                ty_name = ty.get("name")
269                if (self.is_features_struct(ty_name) and
270                    entry.features_struct is None):
271                    entry.features_struct = ty_name
272
273                elif (self.is_properties_struct(ty_name) and
274                      entry.properties_struct is None):
275                    entry.properties_struct = ty_name
276
277            if entry.features_struct:
278                struct_name = entry.features_struct
279                if entry.features_struct in struct_aliases:
280                    struct_name = struct_aliases[entry.features_struct]
281                    entry.features_promoted = True
282
283                elif entry.promoted_in is not None:
284                    # if the extension is promoted but a core-Vulkan alias is not
285                    # available for the features, then consider the features struct
286                    # non-core-promoted
287                    entry.features_promoted = False
288
289                for field in vkxml.findall("./types/type[@name='{}']/member".format(struct_name)):
290                    field_name = field.find("name").text
291
292                    # we ignore sType and pNext since they are irrelevant
293                    if field_name not in ["sType", "pNext"]:
294                        entry.features_fields.append(field_name)
295
296            if entry.properties_struct:
297                struct_name = entry.properties_struct
298                if entry.properties_struct in struct_aliases:
299                    struct_name = struct_aliases[entry.properties_struct]
300                    entry.properties_promoted = True
301
302                elif entry.promoted_in is not None:
303                    # if the extension is promoted but a core-Vulkan alias is not
304                    # available for the properties, then it is not promoted to core
305                    entry.properties_promoted = False
306
307                for field in vkxml.findall("./types/type[@name='{}']/member".format(struct_name)):
308                    field_name = field.find("name").text
309
310                    # we ignore sType and pNext since they are irrelevant
311                    if field_name not in ["sType", "pNext"]:
312                        entry.properties_fields.append(field_name)
313
314            if ext.get("platform") is not None:
315                entry.platform_guard = platform_guards[ext.get("platform")]
316
317            self.registry[name] = entry
318
319    def in_registry(self, ext_name: str):
320        return ext_name in self.registry
321
322    def get_registry_entry(self, ext_name: str):
323        if self.in_registry(ext_name):
324            return self.registry[ext_name]
325
326    # Parses e.g. "VK_VERSION_x_y" to integer tuple (x, y)
327    # For any erroneous inputs, None is returned
328    def parse_promotedto(self, promotedto: str):
329        result = None
330
331        if promotedto and promotedto.startswith("VK_VERSION_"):
332            (major, minor) = promotedto.split('_')[-2:]
333            result = (int(major), int(minor))
334
335        return result
336
337    def is_features_struct(self, struct: str):
338        return re.match(r"VkPhysicalDevice.*Features.*", struct) is not None
339
340    def is_properties_struct(self, struct: str):
341        return re.match(r"VkPhysicalDevice.*Properties.*", struct) is not None
342