add vk-layer-introspect
It can be used to validate the introspection functions or generate the
manifest file for a layer library.
$ ./vk-layer-introspect build/layers/libVkLayer_core_validation.so
{
"file_format_version": "1.0.0",
"layer": {
"api_version": "1.0.13",
"description": "LunarG Validation Layer",
"implementation_version": "1",
"instance_extensions": [
{
"name": "VK_EXT_debug_report",
"spec_version": "2"
}
],
"library_path": "./libVkLayer_core_validation.so",
"name": "VK_LAYER_LUNARG_core_validation",
"type": "GLOBAL"
}
}
diff --git a/vk-layer-introspect b/vk-layer-introspect
new file mode 100755
index 0000000..e64373e
--- /dev/null
+++ b/vk-layer-introspect
@@ -0,0 +1,399 @@
+#!/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()