• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
17import argparse
18import ctypes
19import json
20import os
21import platform
22import sys
23import xml.etree.ElementTree
24
25if platform.system() == "Windows":
26    VKAPI_DLL = ctypes.windll
27    VKAPI_FUNCTYPE = ctypes.WINFUNCTYPE
28else:
29    VKAPI_DLL = ctypes.cdll
30    VKAPI_FUNCTYPE = ctypes.CFUNCTYPE
31
32# Vulkan types
33
34VkInstance = ctypes.c_void_p
35VkPhysicalDevice = ctypes.c_void_p
36VkDevice = ctypes.c_void_p
37VkResult = ctypes.c_int
38
39
40class 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
68class 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
80PFN_vkVoidFunction = VKAPI_FUNCTYPE(None)
81PFN_vkEnumerateInstanceExtensionProperties = VKAPI_FUNCTYPE(
82    VkResult, ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(VkExtensionProperties))
83PFN_vkEnumerateDeviceExtensionProperties = VKAPI_FUNCTYPE(
84    VkResult, VkPhysicalDevice, ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(VkExtensionProperties))
85PFN_vkEnumerateInstanceLayerProperties = VKAPI_FUNCTYPE(
86    VkResult, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(VkLayerProperties))
87PFN_vkEnumerateDeviceLayerProperties = VKAPI_FUNCTYPE(
88    VkResult, VkPhysicalDevice, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(VkLayerProperties))
89PFN_vkGetInstanceProcAddr = VKAPI_FUNCTYPE(
90    PFN_vkVoidFunction, VkInstance, ctypes.c_char_p)
91PFN_vkGetDeviceProcAddr = VKAPI_FUNCTYPE(
92    PFN_vkVoidFunction, VkDevice, ctypes.c_char_p)
93
94
95class 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
106class 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
275def 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
335def 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
346def 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
365def 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
371def 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
398if __name__ == "__main__":
399    main()
400