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
74    # these are specific to zink_instance.py:
75    platform_guard = None
76
77    def __init__(self, name, alias="", required=False, nonstandard=False,
78                 properties=False, features=False, conditions=None, guard=False):
79        self.name = name
80        self.alias = alias
81        self.is_required = required
82        self.is_nonstandard = nonstandard
83        self.has_properties = properties
84        self.has_features = features
85        self.enable_conds = conditions
86        self.guard = guard
87
88        if alias == "" and (properties == True or features == True):
89            raise RuntimeError("alias must be available when properties and/or features are used")
90
91    # e.g.: "VK_EXT_robustness2" -> "robustness2"
92    def pure_name(self):
93        return '_'.join(self.name.split('_')[2:])
94
95    # e.g.: "VK_EXT_robustness2" -> "EXT_robustness2"
96    def name_with_vendor(self):
97        return self.name[3:]
98
99    # e.g.: "VK_EXT_robustness2" -> "Robustness2"
100    def name_in_camel_case(self):
101        return "".join([x.title() for x in self.name.split('_')[2:]])
102
103    # e.g.: "VK_EXT_robustness2" -> "VK_EXT_ROBUSTNESS2_EXTENSION_NAME"
104    # do note that inconsistencies exist, i.e. we have
105    # VK_EXT_ROBUSTNESS_2_EXTENSION_NAME defined in the headers, but then
106    # we also have VK_KHR_MAINTENANCE1_EXTENSION_NAME
107    def extension_name(self):
108        return self.name.upper() + "_EXTENSION_NAME"
109
110    # generate a C string literal for the extension
111    def extension_name_literal(self):
112        return '"' + self.name + '"'
113
114    # get the field in zink_device_info that refers to the extension's
115    # feature/properties struct
116    # e.g. rb2_<suffix> for VK_EXT_robustness2
117    def field(self, suffix: str):
118        return self.alias + '_' + suffix
119
120    def physical_device_struct(self, struct: str):
121        if self.name_in_camel_case().endswith(struct):
122            struct = ""
123
124        return ("VkPhysicalDevice"
125                + self.name_in_camel_case()
126                + struct
127                + self.vendor())
128
129    # the sType of the extension's struct
130    # e.g. VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT
131    # for VK_EXT_transform_feedback and struct="FEATURES"
132    def stype(self, struct: str):
133        return ("VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_"
134                + self.pure_name().upper()
135                + '_' + struct + '_'
136                + self.vendor())
137
138    # e.g. EXT in VK_EXT_robustness2
139    def vendor(self):
140        return self.name.split('_')[1]
141
142# Type aliases
143Layer = Extension
144
145class ExtensionRegistryEntry:
146    # type of extension - right now it's either "instance" or "device"
147    ext_type          = ""
148    # the version in which the extension is promoted to core VK
149    promoted_in       = None
150    # functions added by the extension are referred to as "commands" in the registry
151    device_commands   = None
152    pdevice_commands  = None
153    instance_commands = None
154    constants         = None
155    features_struct   = None
156    properties_struct = None
157    # some instance extensions are locked behind certain platforms
158    platform_guard    = ""
159
160class ExtensionRegistry:
161    # key = extension name, value = registry entry
162    registry = dict()
163
164    def __init__(self, vkxml_path: str):
165        vkxml = ElementTree.parse(vkxml_path)
166
167        commands_type = dict()
168        aliases = dict()
169        platform_guards = dict()
170
171        for cmd in vkxml.findall("commands/command"):
172            name = cmd.find("./proto/name")
173
174            if name is not None and name.text:
175                commands_type[name.text] = cmd.find("./param/type").text
176            elif cmd.get("name") is not None:
177                aliases[cmd.get("name")] = cmd.get("alias")
178
179        for (cmd, alias) in aliases.items():
180            commands_type[cmd] = commands_type[alias]
181
182        for platform in vkxml.findall("platforms/platform"):
183            name = platform.get("name")
184            guard = platform.get("protect")
185            platform_guards[name] = guard
186
187        for ext in vkxml.findall("extensions/extension"):
188            # Reserved extensions are marked with `supported="disabled"`
189            if ext.get("supported") == "disabled":
190                continue
191
192            name = ext.attrib["name"]
193
194            entry = ExtensionRegistryEntry()
195            entry.ext_type = ext.attrib["type"]
196            entry.promoted_in = self.parse_promotedto(ext.get("promotedto"))
197
198            entry.device_commands = []
199            entry.pdevice_commands = []
200            entry.instance_commands = []
201
202            for cmd in ext.findall("require/command"):
203                cmd_name = cmd.get("name")
204                if cmd_name:
205                    if commands_type[cmd_name] in ("VkDevice", "VkCommandBuffer", "VkQueue"):
206                        entry.device_commands.append(cmd_name)
207                    elif commands_type[cmd_name] in ("VkPhysicalDevice"):
208                        entry.pdevice_commands.append(cmd_name)
209                    else:
210                        entry.instance_commands.append(cmd_name)
211
212            entry.constants = []
213            for enum in ext.findall("require/enum"):
214                enum_name = enum.get("name")
215                enum_extends = enum.get("extends")
216                # we are only interested in VK_*_EXTENSION_NAME, which does not
217                # have an "extends" attribute
218                if not enum_extends:
219                    entry.constants.append(enum_name)
220
221            for ty in ext.findall("require/type"):
222                ty_name = ty.get("name")
223                if (self.is_features_struct(ty_name) and
224                    entry.features_struct is None):
225                    entry.features_struct = ty_name
226                elif (self.is_properties_struct(ty_name) and
227                      entry.properties_struct is None):
228                    entry.properties_struct = ty_name
229
230            if ext.get("platform") is not None:
231                entry.platform_guard = platform_guards[ext.get("platform")]
232
233            self.registry[name] = entry
234
235    def in_registry(self, ext_name: str):
236        return ext_name in self.registry
237
238    def get_registry_entry(self, ext_name: str):
239        if self.in_registry(ext_name):
240            return self.registry[ext_name]
241
242    # Parses e.g. "VK_VERSION_x_y" to integer tuple (x, y)
243    # For any erroneous inputs, None is returned
244    def parse_promotedto(self, promotedto: str):
245        result = None
246
247        if promotedto and promotedto.startswith("VK_VERSION_"):
248            (major, minor) = promotedto.split('_')[-2:]
249            result = (int(major), int(minor))
250
251        return result
252
253    def is_features_struct(self, struct: str):
254        return re.match(r"VkPhysicalDevice.*Features.*", struct) is not None
255
256    def is_properties_struct(self, struct: str):
257        return re.match(r"VkPhysicalDevice.*Properties.*", struct) is not None
258