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() 400