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