header: Move vk.xml to version 1.0.29
[platform/upstream/Vulkan-LoaderAndValidationLayers.git] / vk-layer-introspect
1 #!/usr/bin/env python3
2 #
3 # Copyright (c) 2016 Google Inc.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 #     http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 import argparse
18 import ctypes
19 import json
20 import os
21 import platform
22 import sys
23 import xml.etree.ElementTree
24
25 if platform.system() == "Windows":
26     VKAPI_DLL = ctypes.windll
27     VKAPI_FUNCTYPE = ctypes.WINFUNCTYPE
28 else:
29     VKAPI_DLL = ctypes.cdll
30     VKAPI_FUNCTYPE = ctypes.CFUNCTYPE
31
32 # Vulkan types
33
34 VkInstance = ctypes.c_void_p
35 VkPhysicalDevice = ctypes.c_void_p
36 VkDevice = ctypes.c_void_p
37 VkResult = ctypes.c_int
38
39
40 class VkLayerProperties(ctypes.Structure):
41     _fields_ = [("c_layerName", ctypes.c_char * 256),
42                 ("c_specVersion", ctypes.c_uint32),
43                 ("c_implementationVersion", ctypes.c_uint32),
44                 ("c_description", ctypes.c_char * 256)]
45
46     def layer_name(self):
47         return self.c_layerName.decode()
48
49     def spec_version(self):
50         return "%d.%d.%d" % (
51             self.c_specVersion >> 22,
52             (self.c_specVersion >> 12) & 0x3ff,
53             self.c_specVersion & 0xfff)
54
55     def implementation_version(self):
56         return str(self.c_implementationVersion)
57
58     def description(self):
59         return self.c_description.decode()
60
61     def __eq__(self, other):
62         return (self.c_layerName == other.c_layerName and
63                 self.c_specVersion == other.c_specVersion and
64                 self.c_implementationVersion == other.c_implementationVersion and
65                 self.c_description == other.c_description)
66
67
68 class VkExtensionProperties(ctypes.Structure):
69     _fields_ = [("c_extensionName", ctypes.c_char * 256),
70                 ("c_specVersion", ctypes.c_uint32)]
71
72     def extension_name(self):
73         return self.c_extensionName.decode()
74
75     def spec_version(self):
76         return str(self.c_specVersion)
77
78 # Vulkan commands
79
80 PFN_vkVoidFunction = VKAPI_FUNCTYPE(None)
81 PFN_vkEnumerateInstanceExtensionProperties = VKAPI_FUNCTYPE(
82     VkResult, ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(VkExtensionProperties))
83 PFN_vkEnumerateDeviceExtensionProperties = VKAPI_FUNCTYPE(
84     VkResult, VkPhysicalDevice, ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(VkExtensionProperties))
85 PFN_vkEnumerateInstanceLayerProperties = VKAPI_FUNCTYPE(
86     VkResult, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(VkLayerProperties))
87 PFN_vkEnumerateDeviceLayerProperties = VKAPI_FUNCTYPE(
88     VkResult, VkPhysicalDevice, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(VkLayerProperties))
89 PFN_vkGetInstanceProcAddr = VKAPI_FUNCTYPE(
90     PFN_vkVoidFunction, VkInstance, ctypes.c_char_p)
91 PFN_vkGetDeviceProcAddr = VKAPI_FUNCTYPE(
92     PFN_vkVoidFunction, VkDevice, ctypes.c_char_p)
93
94
95 class Layer(object):
96
97     def __init__(self, *args):
98         self.props = args[0]
99         self.is_global = args[1]
100         self.instance_extensions = args[2]
101         self.device_extensions = args[3]
102         self.gipa_name = args[4]
103         self.gdpa_name = args[5]
104
105
106 class LayerLibrary(object):
107
108     def __init__(self, path):
109         self.library = None
110         self.version = 0
111
112         self._load(path)
113         self._negotiate_version()
114
115     def introspect(self):
116         if self.version == 0:
117             layers = self._enumerate_layers_v0()
118         else:
119             raise RuntimeError("unsupported v%d library" % self.version)
120
121         return layers
122
123     def _load(self, path):
124         try:
125             abspath = os.path.abspath(path)
126             self.library = VKAPI_DLL.LoadLibrary(abspath)
127         except OSError:
128             raise RuntimeError("failed to load library")
129
130     def _unload(self):
131         # no clean way to unload
132         pass
133
134     def _negotiate_version(self):
135         # only v0
136         self.version = 0
137
138     def _enumerate_properties_errcheck_v0(self, result, func, args):
139         if isinstance(func, PFN_vkEnumerateInstanceLayerProperties):
140             func_name = "vkEnumerateInstanceLayerProperties"
141         elif isinstance(func, PFN_vkEnumerateDeviceLayerProperties):
142             func_name = "vkEnumerateDeviceLayerProperties"
143         elif isinstance(func, PFN_vkEnumerateInstanceExtensionProperties):
144             func_name = "vkEnumerateInstanceExtensionProperties"
145         elif isinstance(func, PFN_vkEnumerateDeviceExtensionProperties):
146             func_name = "vkEnumerateDeviceExtensionProperties"
147         else:
148             raise AssertionError("unexpected vkEnumerate*Properties call")
149
150         if result != 0:
151             raise RuntimeError(func_name + " failed with " + str(result))
152
153         # pProperties and pCount mismatch
154         if args[-1] and len(args[-1]) != args[-2].value:
155             raise RuntimeError("invalid pCount returned in " + func_name)
156
157         return args[-1]
158
159     def _enumerate_properties_prototype_v0(self, func_name):
160         prototypes = {
161             "vkEnumerateInstanceLayerProperties":
162             PFN_vkEnumerateInstanceLayerProperties,
163             "vkEnumerateDeviceLayerProperties":
164             PFN_vkEnumerateDeviceLayerProperties,
165             "vkEnumerateInstanceExtensionProperties":
166             PFN_vkEnumerateInstanceExtensionProperties,
167             "vkEnumerateDeviceExtensionProperties":
168             PFN_vkEnumerateDeviceExtensionProperties,
169         }
170         prototype = prototypes[func_name]
171
172         try:
173             proc = prototype((func_name, self.library))
174         except AttributeError:
175             raise RuntimeError(func_name + " is missing")
176
177         proc.errcheck = self._enumerate_properties_errcheck_v0
178
179         return proc
180
181     def _get_gipa_name_v0(self, layer_name, can_fallback):
182         names = [layer_name + "GetInstanceProcAddr"]
183         if can_fallback:
184             names.append("vkGetInstanceProcAddr")
185
186         for name in names:
187             try:
188                 PFN_vkGetInstanceProcAddr((name, self.library))
189                 return name
190             except AttributeError:
191                 pass
192
193         raise RuntimeError(" or ".join(names) + " is missing")
194
195     def _get_gdpa_name_v0(self, layer_name, can_fallback):
196         names = [layer_name + "GetDeviceProcAddr"]
197         if can_fallback:
198             names.append("vkGetDeviceProcAddr")
199
200         for name in names:
201             try:
202                 PFN_vkGetDeviceProcAddr((name, self.library))
203                 return name
204             except AttributeError:
205                 pass
206
207         raise RuntimeError(" or ".join(names) + " is missing")
208
209     def _enumerate_layers_v0(self):
210         tmp_count = ctypes.c_uint32()
211
212         # enumerate instance layers
213         enumerate_instance_layer_properties = self._enumerate_properties_prototype_v0(
214             "vkEnumerateInstanceLayerProperties")
215         enumerate_instance_layer_properties(tmp_count, None)
216         p_props = enumerate_instance_layer_properties(
217             tmp_count, (VkLayerProperties * tmp_count.value)())
218
219         # enumerate device layers
220         enumerate_device_layer_properties = self._enumerate_properties_prototype_v0(
221             "vkEnumerateDeviceLayerProperties")
222         enumerate_device_layer_properties(None, tmp_count, None)
223         dev_p_props = enumerate_device_layer_properties(
224             None, tmp_count, (VkLayerProperties * tmp_count.value)())
225
226         # there must not be device-only layers
227         for props in dev_p_props:
228             if props not in p_props:
229                 raise RuntimeError(
230                     "unexpected device-only layer " + props.layer_name())
231
232         layers = []
233         for props in p_props:
234             is_global = (props in dev_p_props)
235
236             # enumerate instance extensions
237             enumerate_instance_extension_properties = self._enumerate_properties_prototype_v0(
238                 "vkEnumerateInstanceExtensionProperties")
239             enumerate_instance_extension_properties(
240                 props.c_layerName, tmp_count, None)
241             instance_extensions = enumerate_instance_extension_properties(
242                 props.c_layerName,
243                 tmp_count,
244                 (VkExtensionProperties * tmp_count.value)())
245
246             gipa_name = self._get_gipa_name_v0(
247                 props.layer_name(),
248                 len(p_props) == 1)
249
250             if is_global:
251                 # enumerate device extensions
252                 enumerate_device_extension_properties = self._enumerate_properties_prototype_v0(
253                     "vkEnumerateDeviceExtensionProperties")
254                 enumerate_device_extension_properties(
255                     None, props.c_layerName, tmp_count, None)
256                 device_extensions = enumerate_device_extension_properties(
257                     None,
258                     props.c_layerName,
259                     tmp_count,
260                     (VkExtensionProperties * tmp_count.value)())
261
262                 gdpa_name = self._get_gdpa_name_v0(
263                     props.layer_name(),
264                     len(p_props) == 1)
265             else:
266                 device_extensions = None
267                 gdpa_name = None
268
269             layers.append(
270                 Layer(props, is_global, instance_extensions, device_extensions, gipa_name, gdpa_name))
271
272         return layers
273
274
275 def serialize_layers(layers, path, ext_cmds):
276     data = {}
277     data["file_format_version"] = '1.0.0'
278
279     for idx, layer in enumerate(layers):
280         layer_data = {}
281
282         layer_data["name"] = layer.props.layer_name()
283         layer_data["api_version"] = layer.props.spec_version()
284         layer_data[
285             "implementation_version"] = layer.props.implementation_version()
286         layer_data["description"] = layer.props.description()
287
288         layer_data["type"] = "GLOBAL" if layer.is_global else "INSTANCE"
289
290         # TODO more flexible
291         layer_data["library_path"] = os.path.join(".", os.path.basename(path))
292
293         funcs = {}
294         if layer.gipa_name != "vkGetInstanceProcAddr":
295             funcs["vkGetInstanceProcAddr"] = layer.gipa_name
296         if layer.is_global and layer.gdpa_name != "vkGetDeviceProcAddr":
297             funcs["vkGetDeviceProcAddr"] = layer.gdpa_name
298         if funcs:
299             layer_data["functions"] = funcs
300
301         if layer.instance_extensions:
302             exts = [{
303                 "name": ext.extension_name(),
304                 "spec_version": ext.spec_version(),
305             } for ext in layer.instance_extensions]
306             layer_data["instance_extensions"] = exts
307
308         if layer.device_extensions:
309             exts = []
310             for ext in layer.device_extensions:
311                 try:
312                     cmds = ext_cmds[ext.extension_name()]
313                 except KeyError:
314                     raise RuntimeError(
315                         "unknown device extension " + ext.extension_name())
316                 else:
317                     ext_data = {}
318                     ext_data["name"] = ext.extension_name()
319                     ext_data["spec_version"] = ext.spec_version()
320                     if cmds:
321                         ext_data["entrypoints"] = cmds
322
323                     exts.append(ext_data)
324
325             layer_data["device_extensions"] = exts
326
327         if idx > 0:
328             data["layer.%d" % idx] = layer_data
329         else:
330             data["layer"] = layer_data
331
332     return data
333
334
335 def dump_json(data):
336     dump = json.dumps(data, indent=4, sort_keys=True)
337
338     # replace "layer.<idx>" by "layer"
339     lines = dump.split("\n")
340     for line in lines:
341         if line.startswith("    \"layer.") and line.endswith("\": {"):
342             line = "    \"layer\": {"
343         print(line)
344
345
346 def parse_vk_xml(path):
347     """Parse vk.xml to get commands added by extensions."""
348     tree = xml.etree.ElementTree.parse(path)
349     extensions = tree.find("extensions")
350
351     ext_cmds = {}
352     for ext in extensions.iter("extension"):
353         if ext.attrib["supported"] != "vulkan":
354             continue
355
356         cmds = []
357         for cmd in ext.iter("command"):
358             cmds.append(cmd.attrib["name"])
359
360         ext_cmds[ext.attrib["name"]] = cmds
361
362     return ext_cmds
363
364
365 def add_custom_ext_cmds(ext_cmds):
366     """Add commands added by in-development extensions."""
367     # VK_LAYER_LUNARG_basic
368     ext_cmds["vkLayerBasicEXT"] = ["vkLayerBasicEXT"]
369
370
371 def main():
372     default_vk_xml = sys.path[0] + "/vk.xml" if sys.path[0] else "vk.xml"
373
374     parser = argparse.ArgumentParser(description="Introspect a layer library.")
375     parser.add_argument(
376         "-x", dest="vk_xml", default=default_vk_xml, help="Path to vk.xml")
377     parser.add_argument(
378         "layer_libs", metavar="layer-lib", nargs="+", help="Path to a layer library")
379     args = parser.parse_args()
380
381     try:
382         ext_cmds = parse_vk_xml(args.vk_xml)
383     except Exception as e:
384         print("failed to parse %s: %s" % (args.vk_xml, e))
385         sys.exit(-1)
386
387     add_custom_ext_cmds(ext_cmds)
388
389     for path in args.layer_libs:
390         try:
391             ll = LayerLibrary(path)
392             layers = ll.introspect()
393             data = serialize_layers(layers, path, ext_cmds)
394             dump_json(data)
395         except RuntimeError as err:
396             print("skipping %s: %s" % (path, err))
397
398 if __name__ == "__main__":
399     main()