1#!/usr/bin/python3
2#
3# Copyright (c) 2013-2019 The Khronos Group Inc.
4# Copyright (c) 2021-2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
5# Copyright (c) 2023-2023 RasterGrid Kft.
6#
7# Licensed under the Apache License, Version 2.0 (the "License");
8# you may not use this file except in compliance with the License.
9# You may obtain a copy of the License at
10#
11#     http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS,
15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18
19import argparse, cProfile, pdb, string, sys, time, os
20
21# Simple timer functions
22startTime = None
23
24def startTimer(timeit):
25    global startTime
26    if timeit:
27        startTime = time.process_time()
28
29def endTimer(timeit, msg):
30    global startTime
31    if timeit:
32        endTime = time.process_time()
33        write(msg, endTime - startTime, file=sys.stderr)
34        startTime = None
35
36# Turn a list of strings into a regexp string matching exactly those strings
37def makeREstring(list, default = None):
38    if len(list) > 0 or default is None:
39        return '^(' + '|'.join(list) + ')$'
40    else:
41        return default
42
43# Returns a directory of [ generator function, generator options ] indexed
44# by specified short names. The generator options incorporate the following
45# parameters:
46#
47# args is an parsed argument object; see below for the fields that are used.
48def makeGenOpts(args):
49    global genOpts
50    genOpts = {}
51
52    # API to generate sources for
53    apiname = args.api
54
55    # Default class of extensions to include, or None
56    if args.defaultExtensions is not None:
57        defaultExtensions = args.defaultExtensions
58    else:
59        defaultExtensions = apiname
60
61    # Additional extensions to include (list of extensions)
62    extensions = args.extension
63
64    # Extensions to remove (list of extensions)
65    removeExtensions = args.removeExtensions
66
67    # Extensions to emit (list of extensions)
68    emitExtensions = args.emitExtensions
69
70    # Features to include (list of features)
71    features = args.feature
72
73    # Whether to disable inclusion protect in headers
74    protect = args.protect
75
76    # Output target directory
77    directory = args.directory
78
79    # Path to generated files, particularly api.py
80    genpath = args.genpath
81
82    # Descriptive names for various regexp patterns used to select
83    # versions and extensions
84    allFeatures     = allExtensions = '.*'
85    noFeatures      = noExtensions = None
86
87    # Turn lists of names/patterns into matching regular expressions
88    addExtensionsPat     = makeREstring(extensions, None)
89    removeExtensionsPat  = makeREstring(removeExtensions, None)
90    emitExtensionsPat    = makeREstring(emitExtensions, allExtensions)
91    featuresPat          = makeREstring(features, allFeatures)
92
93    # Copyright text prefixing all headers (list of strings).
94    prefixStrings = [
95        '/*',
96        '** Copyright (c) 2015-2019 The Khronos Group Inc.',
97        '**',
98        '** Licensed under the Apache License, Version 2.0 (the "License");',
99        '** you may not use this file except in compliance with the License.',
100        '** You may obtain a copy of the License at',
101        '**',
102        '**     http://www.apache.org/licenses/LICENSE-2.0',
103        '**',
104        '** Unless required by applicable law or agreed to in writing, software',
105        '** distributed under the License is distributed on an "AS IS" BASIS,',
106        '** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.',
107        '** See the License for the specific language governing permissions and',
108        '** limitations under the License.',
109        '*/',
110        ''
111    ]
112
113    # Text specific to Vulkan headers
114    vkPrefixStrings = [
115        '/*',
116        '** This header is generated from the Khronos Vulkan XML API Registry.',
117        '**',
118        '*/',
119        ''
120    ]
121
122    # Defaults for generating re-inclusion protection wrappers (or not)
123    protectFeature = protect
124
125    # An API style conventions object
126    conventions = VulkanConventions()
127
128    # Loader Generators
129    # Options for dispatch table helper generator
130    genOpts['vk_dispatch_table_helper.h'] = [
131          DispatchTableHelperOutputGenerator,
132          DispatchTableHelperOutputGeneratorOptions(
133            conventions       = conventions,
134            filename          = 'vk_dispatch_table_helper.h',
135            directory         = directory,
136            genpath           = None,
137            apiname           = apiname,
138            profile           = None,
139            versions          = featuresPat,
140            emitversions      = featuresPat,
141            defaultExtensions = defaultExtensions,
142            addExtensions     = addExtensionsPat,
143            removeExtensions  = removeExtensionsPat,
144            emitExtensions    = emitExtensionsPat,
145            prefixText        = prefixStrings + vkPrefixStrings,
146            apicall           = 'VKAPI_ATTR ',
147            apientry          = 'VKAPI_CALL ',
148            apientryp         = 'VKAPI_PTR *',
149            alignFuncParam    = 48,
150            expandEnumerants = False)
151        ]
152
153    # Options for Layer dispatch table generator
154    genOpts['vk_layer_dispatch_table.h'] = [
155          LoaderExtensionOutputGenerator,
156          LoaderExtensionGeneratorOptions(
157            conventions       = conventions,
158            filename          = 'vk_layer_dispatch_table.h',
159            directory         = directory,
160            genpath           = None,
161            apiname           = apiname,
162            profile           = None,
163            versions          = featuresPat,
164            emitversions      = featuresPat,
165            defaultExtensions = defaultExtensions,
166            addExtensions     = addExtensionsPat,
167            removeExtensions  = removeExtensionsPat,
168            emitExtensions    = emitExtensionsPat,
169            prefixText        = prefixStrings + vkPrefixStrings,
170            apicall           = 'VKAPI_ATTR ',
171            apientry          = 'VKAPI_CALL ',
172            apientryp         = 'VKAPI_PTR *',
173            alignFuncParam    = 48,
174            expandEnumerants = False)
175        ]
176
177    # Options for loader extension source generator
178    genOpts['vk_loader_extensions.h'] = [
179          LoaderExtensionOutputGenerator,
180          LoaderExtensionGeneratorOptions(
181            conventions       = conventions,
182            filename          = 'vk_loader_extensions.h',
183            directory         = directory,
184            genpath           = None,
185            apiname           = apiname,
186            profile           = None,
187            versions          = featuresPat,
188            emitversions      = featuresPat,
189            defaultExtensions = defaultExtensions,
190            addExtensions     = addExtensionsPat,
191            removeExtensions  = removeExtensionsPat,
192            emitExtensions    = emitExtensionsPat,
193            prefixText        = prefixStrings + vkPrefixStrings,
194            apicall           = 'VKAPI_ATTR ',
195            apientry          = 'VKAPI_CALL ',
196            apientryp         = 'VKAPI_PTR *',
197            alignFuncParam    = 48,
198            expandEnumerants = False)
199        ]
200
201    # Options for loader extension source generator
202    genOpts['vk_loader_extensions.c'] = [
203          LoaderExtensionOutputGenerator,
204          LoaderExtensionGeneratorOptions(
205            conventions       = conventions,
206            filename          = 'vk_loader_extensions.c',
207            directory         = directory,
208            genpath           = None,
209            apiname           = apiname,
210            profile           = None,
211            versions          = featuresPat,
212            emitversions      = featuresPat,
213            defaultExtensions = defaultExtensions,
214            addExtensions     = addExtensionsPat,
215            removeExtensions  = removeExtensionsPat,
216            emitExtensions    = emitExtensionsPat,
217            prefixText        = prefixStrings + vkPrefixStrings,
218            apicall           = 'VKAPI_ATTR ',
219            apientry          = 'VKAPI_CALL ',
220            apientryp         = 'VKAPI_PTR *',
221            alignFuncParam    = 48,
222            expandEnumerants = False)
223        ]
224
225    # Helper file generator options for vk_object_types.h
226    genOpts['vk_object_types.h'] = [
227          HelperFileOutputGenerator,
228          HelperFileOutputGeneratorOptions(
229            conventions       = conventions,
230            filename          = 'vk_object_types.h',
231            directory         = directory,
232            genpath           = None,
233            apiname           = apiname,
234            profile           = None,
235            versions          = featuresPat,
236            emitversions      = featuresPat,
237            defaultExtensions = defaultExtensions,
238            addExtensions     = addExtensionsPat,
239            removeExtensions  = removeExtensionsPat,
240            emitExtensions    = emitExtensionsPat,
241            prefixText        = prefixStrings + vkPrefixStrings,
242            apicall           = 'VKAPI_ATTR ',
243            apientry          = 'VKAPI_CALL ',
244            apientryp         = 'VKAPI_PTR *',
245            alignFuncParam    = 48,
246            expandEnumerants  = False,
247            helper_file_type  = 'object_types_header')
248        ]
249
250# Create an API generator and corresponding generator options based on
251# the requested target and command line options.
252# This is encapsulated in a function so it can be profiled and/or timed.
253# The args parameter is an parsed argument object containing the following
254# fields that are used:
255#   target - target to generate
256#   directory - directory to generate it in
257#   protect - True if re-inclusion wrappers should be created
258#   extensions - list of additional extensions to include in generated
259#   interfaces
260def genTarget(args):
261    global genOpts
262
263    # Create generator options with parameters specified on command line
264    makeGenOpts(args)
265
266    # Select a generator matching the requested target
267    if (args.target in genOpts.keys()):
268        createGenerator = genOpts[args.target][0]
269        options = genOpts[args.target][1]
270
271        if not args.quiet:
272            write('* Building', options.filename, file=sys.stderr)
273            write('* options.apiname           =', options.apiname, file=sys.stderr)
274            write('* options.versions          =', options.versions, file=sys.stderr)
275            write('* options.emitversions      =', options.emitversions, file=sys.stderr)
276            write('* options.defaultExtensions =', options.defaultExtensions, file=sys.stderr)
277            write('* options.addExtensions     =', options.addExtensions, file=sys.stderr)
278            write('* options.removeExtensions  =', options.removeExtensions, file=sys.stderr)
279            write('* options.emitExtensions    =', options.emitExtensions, file=sys.stderr)
280
281        gen = createGenerator(errFile=errWarn,
282                              warnFile=errWarn,
283                              diagFile=diag)
284        if not args.quiet:
285            write('* Generated', options.filename, file=sys.stderr)
286        return (gen, options)
287    else:
288        write('No generator options for unknown target:', args.target, file=sys.stderr)
289        return none
290
291# -feature name
292# -extension name
293# For both, "name" may be a single name, or a space-separated list
294# of names, or a regular expression.
295if __name__ == '__main__':
296    parser = argparse.ArgumentParser()
297
298    parser.add_argument('-api', action='store',
299                        default='vulkan',
300                        choices=['vulkan'],
301                        help='Specify API name to generate')
302    parser.add_argument('-defaultExtensions', action='store',
303                        default=None,
304                        help='Specify a single class of extensions to add to targets')
305    parser.add_argument('-extension', action='append',
306                        default=[],
307                        help='Specify an extension or extensions to add to targets')
308    parser.add_argument('-removeExtensions', action='append',
309                        default=[],
310                        help='Specify an extension or extensions to remove from targets')
311    parser.add_argument('-emitExtensions', action='append',
312                        default=[],
313                        help='Specify an extension or extensions to emit in targets')
314    parser.add_argument('-feature', action='append',
315                        default=[],
316                        help='Specify a core API feature name or names to add to targets')
317    parser.add_argument('-debug', action='store_true',
318                        help='Enable debugging')
319    parser.add_argument('-dump', action='store_true',
320                        help='Enable dump to stderr')
321    parser.add_argument('-diagfile', action='store',
322                        default=None,
323                        help='Write diagnostics to specified file')
324    parser.add_argument('-errfile', action='store',
325                        default=None,
326                        help='Write errors and warnings to specified file instead of stderr')
327    parser.add_argument('-noprotect', dest='protect', action='store_false',
328                        help='Disable inclusion protection in output headers')
329    parser.add_argument('-profile', action='store_true',
330                        help='Enable profiling')
331    parser.add_argument('-registry', action='store',
332                        default='vk.xml',
333                        help='Use specified registry file instead of vk.xml')
334    parser.add_argument('-time', action='store_true',
335                        help='Enable timing')
336    parser.add_argument('-validate', action='store_true',
337                        help='Enable XML group validation')
338    parser.add_argument('-genpath', action='store', default='gen',
339                        help='Path to generated files')
340    parser.add_argument('-o', action='store', dest='directory',
341                        default='.',
342                        help='Create target and related files in specified directory')
343    parser.add_argument('target', metavar='target', nargs='?',
344                        help='Specify target')
345    parser.add_argument('-quiet', action='store_true', default=True,
346                        help='Suppress script output during normal execution.')
347    parser.add_argument('-verbose', action='store_false', dest='quiet', default=True,
348                        help='Enable script output during normal execution.')
349
350    # This argument tells us where to load the script from the Vulkan-Headers registry
351    parser.add_argument('-scripts', action='store',
352                        help='Find additional scripts in this directory')
353
354    args = parser.parse_args()
355
356    # default scripts path to be same as registry
357    if not args.scripts:
358        args.scripts = os.path.dirname(args.registry)
359        print(args.scripts)
360
361    scripts_dir = os.path.dirname(os.path.abspath(__file__))
362    registry_dir = os.path.join(scripts_dir, args.scripts)
363    sys.path.insert(0, registry_dir)
364
365    # The imports need to be done here so that they can be picked up from Vulkan-Headers
366    from reg import *
367    from generator import write
368    from cgenerator import CGeneratorOptions, COutputGenerator
369
370    from dispatch_table_helper_generator import DispatchTableHelperOutputGenerator, DispatchTableHelperOutputGeneratorOptions
371    from helper_file_generator import HelperFileOutputGenerator, HelperFileOutputGeneratorOptions
372    from loader_extension_generator import LoaderExtensionOutputGenerator, LoaderExtensionGeneratorOptions
373
374    # Temporary workaround for vkconventions python2 compatibility
375    import abc; abc.ABC = abc.ABCMeta('ABC', (object,), {})
376    from vkconventions import VulkanConventions
377
378    # This splits arguments which are space-separated lists
379    args.feature = [name for arg in args.feature for name in arg.split()]
380    args.extension = [name for arg in args.extension for name in arg.split()]
381
382    # create error/warning & diagnostic files
383    if args.errfile:
384        errWarn = open(args.errfile, 'w', encoding='utf-8')
385    else:
386        errWarn = sys.stderr
387
388    if args.diagfile:
389        diag = open(args.diagfile, 'w', encoding='utf-8')
390    else:
391        diag = None
392
393    # Create the API generator & generator options
394    (gen, options) = genTarget(args)
395
396    # Create the registry object with the specified generator and generator
397    # options. The options are set before XML loading as they may affect it.
398    reg = Registry(gen, options)
399
400    # Parse the specified registry XML into an ElementTree objec
401    startTimer(args.time)
402    tree = etree.parse(args.registry)
403    endTimer(args.time, '* Time to make ElementTree =')
404
405    # Load the XML tree into the registry object
406    startTimer(args.time)
407    reg.loadElementTree(tree)
408    endTimer(args.time, '* Time to parse ElementTree =')
409
410    if (args.validate):
411        reg.validateGroups()
412
413    if (args.dump):
414        write('* Dumping registry to regdump.txt', file=sys.stderr)
415        reg.dumpReg(filehandle = open('regdump.txt', 'w', encoding='utf-8'))
416
417    # Finally, use the output generator to create the requested targe
418    if (args.debug):
419        pdb.run('reg.apiGen()')
420    else:
421        startTimer(args.time)
422        reg.apiGen()
423        endTimer(args.time, '* Time to generate ' + options.filename + ' =')
424
425    if not args.quiet:
426        write('* Generated', options.filename, file=sys.stderr)
427