blob: e64373e14e648d22092f737ab4ab7098be43799b [file] [log] [blame]
Chia-I Wuc4e9ba22016-05-30 07:36:59 +08001#!/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
17import argparse
18import ctypes
19import json
20import os
21import platform
22import sys
23import xml.etree.ElementTree
24
25if platform.system() == "Windows":
26 VKAPI_DLL = ctypes.windll
27 VKAPI_FUNCTYPE = ctypes.WINFUNCTYPE
28else:
29 VKAPI_DLL = ctypes.cdll
30 VKAPI_FUNCTYPE = ctypes.CFUNCTYPE
31
32# Vulkan types
33
34VkInstance = ctypes.c_void_p
35VkPhysicalDevice = ctypes.c_void_p
36VkDevice = ctypes.c_void_p
37VkResult = ctypes.c_int
38
39
40class 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
68class 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
80PFN_vkVoidFunction = VKAPI_FUNCTYPE(None)
81PFN_vkEnumerateInstanceExtensionProperties = VKAPI_FUNCTYPE(
82 VkResult, ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(VkExtensionProperties))
83PFN_vkEnumerateDeviceExtensionProperties = VKAPI_FUNCTYPE(
84 VkResult, VkPhysicalDevice, ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(VkExtensionProperties))
85PFN_vkEnumerateInstanceLayerProperties = VKAPI_FUNCTYPE(
86 VkResult, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(VkLayerProperties))
87PFN_vkEnumerateDeviceLayerProperties = VKAPI_FUNCTYPE(
88 VkResult, VkPhysicalDevice, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(VkLayerProperties))
89PFN_vkGetInstanceProcAddr = VKAPI_FUNCTYPE(
90 PFN_vkVoidFunction, VkInstance, ctypes.c_char_p)
91PFN_vkGetDeviceProcAddr = VKAPI_FUNCTYPE(
92 PFN_vkVoidFunction, VkDevice, ctypes.c_char_p)
93
94
95class 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
106class 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
275def 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
335def 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
346def 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
365def 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
371def 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
398if __name__ == "__main__":
399 main()