#!/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()