You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
399 lines
13 KiB
399 lines
13 KiB
#!/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()
|
|
|