#!/usr/bin/env python3 # # Copyright (c) 2016 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse import ctypes import json import os import platform import sys import xml.etree.ElementTree if platform.system() == "Windows": VKAPI_DLL = ctypes.windll VKAPI_FUNCTYPE = ctypes.WINFUNCTYPE else: VKAPI_DLL = ctypes.cdll VKAPI_FUNCTYPE = ctypes.CFUNCTYPE # Vulkan types VkInstance = ctypes.c_void_p VkPhysicalDevice = ctypes.c_void_p VkDevice = ctypes.c_void_p VkResult = ctypes.c_int class VkLayerProperties(ctypes.Structure): _fields_ = [("c_layerName", ctypes.c_char * 256), ("c_specVersion", ctypes.c_uint32), ("c_implementationVersion", ctypes.c_uint32), ("c_description", ctypes.c_char * 256)] def layer_name(self): return self.c_layerName.decode() def spec_version(self): return "%d.%d.%d" % ( self.c_specVersion >> 22, (self.c_specVersion >> 12) & 0x3ff, self.c_specVersion & 0xfff) def implementation_version(self): return str(self.c_implementationVersion) def description(self): return self.c_description.decode() def __eq__(self, other): return (self.c_layerName == other.c_layerName and self.c_specVersion == other.c_specVersion and self.c_implementationVersion == other.c_implementationVersion and self.c_description == other.c_description) class VkExtensionProperties(ctypes.Structure): _fields_ = [("c_extensionName", ctypes.c_char * 256), ("c_specVersion", ctypes.c_uint32)] def extension_name(self): return self.c_extensionName.decode() def spec_version(self): return str(self.c_specVersion) # Vulkan commands PFN_vkVoidFunction = VKAPI_FUNCTYPE(None) PFN_vkEnumerateInstanceExtensionProperties = VKAPI_FUNCTYPE( VkResult, ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(VkExtensionProperties)) PFN_vkEnumerateDeviceExtensionProperties = VKAPI_FUNCTYPE( VkResult, VkPhysicalDevice, ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(VkExtensionProperties)) PFN_vkEnumerateInstanceLayerProperties = VKAPI_FUNCTYPE( VkResult, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(VkLayerProperties)) PFN_vkEnumerateDeviceLayerProperties = VKAPI_FUNCTYPE( VkResult, VkPhysicalDevice, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(VkLayerProperties)) PFN_vkGetInstanceProcAddr = VKAPI_FUNCTYPE( PFN_vkVoidFunction, VkInstance, ctypes.c_char_p) PFN_vkGetDeviceProcAddr = VKAPI_FUNCTYPE( PFN_vkVoidFunction, VkDevice, ctypes.c_char_p) class Layer(object): def __init__(self, *args): self.props = args[0] self.is_global = args[1] self.instance_extensions = args[2] self.device_extensions = args[3] self.gipa_name = args[4] self.gdpa_name = args[5] class LayerLibrary(object): def __init__(self, path): self.library = None self.version = 0 self._load(path) self._negotiate_version() def introspect(self): if self.version == 0: layers = self._enumerate_layers_v0() else: raise RuntimeError("unsupported v%d library" % self.version) return layers def _load(self, path): try: abspath = os.path.abspath(path) self.library = VKAPI_DLL.LoadLibrary(abspath) except OSError: raise RuntimeError("failed to load library") def _unload(self): # no clean way to unload pass def _negotiate_version(self): # only v0 self.version = 0 def _enumerate_properties_errcheck_v0(self, result, func, args): if isinstance(func, PFN_vkEnumerateInstanceLayerProperties): func_name = "vkEnumerateInstanceLayerProperties" elif isinstance(func, PFN_vkEnumerateDeviceLayerProperties): func_name = "vkEnumerateDeviceLayerProperties" elif isinstance(func, PFN_vkEnumerateInstanceExtensionProperties): func_name = "vkEnumerateInstanceExtensionProperties" elif isinstance(func, PFN_vkEnumerateDeviceExtensionProperties): func_name = "vkEnumerateDeviceExtensionProperties" else: raise AssertionError("unexpected vkEnumerate*Properties call") if result != 0: raise RuntimeError(func_name + " failed with " + str(result)) # pProperties and pCount mismatch if args[-1] and len(args[-1]) != args[-2].value: raise RuntimeError("invalid pCount returned in " + func_name) return args[-1] def _enumerate_properties_prototype_v0(self, func_name): prototypes = { "vkEnumerateInstanceLayerProperties": PFN_vkEnumerateInstanceLayerProperties, "vkEnumerateDeviceLayerProperties": PFN_vkEnumerateDeviceLayerProperties, "vkEnumerateInstanceExtensionProperties": PFN_vkEnumerateInstanceExtensionProperties, "vkEnumerateDeviceExtensionProperties": PFN_vkEnumerateDeviceExtensionProperties, } prototype = prototypes[func_name] try: proc = prototype((func_name, self.library)) except AttributeError: raise RuntimeError(func_name + " is missing") proc.errcheck = self._enumerate_properties_errcheck_v0 return proc def _get_gipa_name_v0(self, layer_name, can_fallback): names = [layer_name + "GetInstanceProcAddr"] if can_fallback: names.append("vkGetInstanceProcAddr") for name in names: try: PFN_vkGetInstanceProcAddr((name, self.library)) return name except AttributeError: pass raise RuntimeError(" or ".join(names) + " is missing") def _get_gdpa_name_v0(self, layer_name, can_fallback): names = [layer_name + "GetDeviceProcAddr"] if can_fallback: names.append("vkGetDeviceProcAddr") for name in names: try: PFN_vkGetDeviceProcAddr((name, self.library)) return name except AttributeError: pass raise RuntimeError(" or ".join(names) + " is missing") def _enumerate_layers_v0(self): tmp_count = ctypes.c_uint32() # enumerate instance layers enumerate_instance_layer_properties = self._enumerate_properties_prototype_v0( "vkEnumerateInstanceLayerProperties") enumerate_instance_layer_properties(tmp_count, None) p_props = enumerate_instance_layer_properties( tmp_count, (VkLayerProperties * tmp_count.value)()) # enumerate device layers enumerate_device_layer_properties = self._enumerate_properties_prototype_v0( "vkEnumerateDeviceLayerProperties") enumerate_device_layer_properties(None, tmp_count, None) dev_p_props = enumerate_device_layer_properties( None, tmp_count, (VkLayerProperties * tmp_count.value)()) # there must not be device-only layers for props in dev_p_props: if props not in p_props: raise RuntimeError( "unexpected device-only layer " + props.layer_name()) layers = [] for props in p_props: is_global = (props in dev_p_props) # enumerate instance extensions enumerate_instance_extension_properties = self._enumerate_properties_prototype_v0( "vkEnumerateInstanceExtensionProperties") enumerate_instance_extension_properties( props.c_layerName, tmp_count, None) instance_extensions = enumerate_instance_extension_properties( props.c_layerName, tmp_count, (VkExtensionProperties * tmp_count.value)()) gipa_name = self._get_gipa_name_v0( props.layer_name(), len(p_props) == 1) if is_global: # enumerate device extensions enumerate_device_extension_properties = self._enumerate_properties_prototype_v0( "vkEnumerateDeviceExtensionProperties") enumerate_device_extension_properties( None, props.c_layerName, tmp_count, None) device_extensions = enumerate_device_extension_properties( None, props.c_layerName, tmp_count, (VkExtensionProperties * tmp_count.value)()) gdpa_name = self._get_gdpa_name_v0( props.layer_name(), len(p_props) == 1) else: device_extensions = None gdpa_name = None layers.append( Layer(props, is_global, instance_extensions, device_extensions, gipa_name, gdpa_name)) return layers def serialize_layers(layers, path, ext_cmds): data = {} data["file_format_version"] = '1.0.0' for idx, layer in enumerate(layers): layer_data = {} layer_data["name"] = layer.props.layer_name() layer_data["api_version"] = layer.props.spec_version() layer_data[ "implementation_version"] = layer.props.implementation_version() layer_data["description"] = layer.props.description() layer_data["type"] = "GLOBAL" if layer.is_global else "INSTANCE" # TODO more flexible layer_data["library_path"] = os.path.join(".", os.path.basename(path)) funcs = {} if layer.gipa_name != "vkGetInstanceProcAddr": funcs["vkGetInstanceProcAddr"] = layer.gipa_name if layer.is_global and layer.gdpa_name != "vkGetDeviceProcAddr": funcs["vkGetDeviceProcAddr"] = layer.gdpa_name if funcs: layer_data["functions"] = funcs if layer.instance_extensions: exts = [{ "name": ext.extension_name(), "spec_version": ext.spec_version(), } for ext in layer.instance_extensions] layer_data["instance_extensions"] = exts if layer.device_extensions: exts = [] for ext in layer.device_extensions: try: cmds = ext_cmds[ext.extension_name()] except KeyError: raise RuntimeError( "unknown device extension " + ext.extension_name()) else: ext_data = {} ext_data["name"] = ext.extension_name() ext_data["spec_version"] = ext.spec_version() if cmds: ext_data["entrypoints"] = cmds exts.append(ext_data) layer_data["device_extensions"] = exts if idx > 0: data["layer.%d" % idx] = layer_data else: data["layer"] = layer_data return data def dump_json(data): dump = json.dumps(data, indent=4, sort_keys=True) # replace "layer.<idx>" by "layer" lines = dump.split("\n") for line in lines: if line.startswith(" \"layer.") and line.endswith("\": {"): line = " \"layer\": {" print(line) def parse_vk_xml(path): """Parse vk.xml to get commands added by extensions.""" tree = xml.etree.ElementTree.parse(path) extensions = tree.find("extensions") ext_cmds = {} for ext in extensions.iter("extension"): if ext.attrib["supported"] != "vulkan": continue cmds = [] for cmd in ext.iter("command"): cmds.append(cmd.attrib["name"]) ext_cmds[ext.attrib["name"]] = cmds return ext_cmds def add_custom_ext_cmds(ext_cmds): """Add commands added by in-development extensions.""" # VK_LAYER_LUNARG_basic ext_cmds["vkLayerBasicEXT"] = ["vkLayerBasicEXT"] def main(): default_vk_xml = sys.path[0] + "/vk.xml" if sys.path[0] else "vk.xml" parser = argparse.ArgumentParser(description="Introspect a layer library.") parser.add_argument( "-x", dest="vk_xml", default=default_vk_xml, help="Path to vk.xml") parser.add_argument( "layer_libs", metavar="layer-lib", nargs="+", help="Path to a layer library") args = parser.parse_args() try: ext_cmds = parse_vk_xml(args.vk_xml) except Exception as e: print("failed to parse %s: %s" % (args.vk_xml, e)) sys.exit(-1) add_custom_ext_cmds(ext_cmds) for path in args.layer_libs: try: ll = LayerLibrary(path) layers = ll.introspect() data = serialize_layers(layers, path, ext_cmds) dump_json(data) except RuntimeError as err: print("skipping %s: %s" % (path, err)) if __name__ == "__main__": main()