399 lines
13 KiB
Python
Executable file
399 lines
13 KiB
Python
Executable file
#!/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()
|