Home | History | Annotate | Download | only in vulkan-validation-layers
      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