• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3 -i
2#
3# Copyright 2013-2022 The Khronos Group Inc.
4#
5# SPDX-License-Identifier: Apache-2.0
6
7import re
8from collections import OrderedDict, namedtuple
9from functools import reduce
10from pathlib import Path
11
12from spec_tools.conventions import ProseListFormats as plf
13from generator import OutputGenerator, write
14from spec_tools.attributes import ExternSyncEntry, LengthEntry
15from spec_tools.util import (findNamedElem, findNamedObject, findTypedElem,
16                             getElemName, getElemType)
17from spec_tools.validity import ValidityCollection, ValidityEntry
18
19
20# For parsing/splitting queue bit names - Vulkan only
21QUEUE_BITS_RE = re.compile(r'([^,]+)')
22
23
24class UnhandledCaseError(RuntimeError):
25    def __init__(self, msg=None):
26        if msg:
27            super().__init__('Got a case in the validity generator that we have not explicitly handled: ' + msg)
28        else:
29            super().__init__('Got a case in the validity generator that we have not explicitly handled.')
30
31
32def _genericIterateIntersection(a, b):
33    """Iterate through all elements in a that are also in b.
34
35    Somewhat like a set's intersection(),
36    but not type-specific so it can work with OrderedDicts, etc.
37    It also returns a generator instead of a set,
38    so you can pick a preferred container type,
39    if any.
40    """
41    return (x for x in a if x in b)
42
43
44def _make_ordered_dict(gen):
45    """Make an ordered dict (with None as the values) from a generator."""
46    return OrderedDict(((x, None) for x in gen))
47
48
49def _orderedDictIntersection(a, b):
50    return _make_ordered_dict(_genericIterateIntersection(a, b))
51
52
53def _genericIsDisjoint(a, b):
54    """Return true if nothing in a is also in b.
55
56    Like a set's is_disjoint(),
57    but not type-specific so it can work with OrderedDicts, etc.
58    """
59    for _ in _genericIterateIntersection(a, b):
60        return False
61    # if we never enter the loop...
62    return True
63
64
65def _parse_queue_bits(cmd):
66    """Return a generator of queue bits, with underscores turned to spaces.
67
68    Vulkan-only.
69
70    Return None if the queues attribute is not specified."""
71    queuetypes = cmd.get('queues')
72    if not queuetypes:
73        return None
74    return (qt.replace('_', ' ')
75            for qt in QUEUE_BITS_RE.findall(queuetypes))
76
77
78class ValidityOutputGenerator(OutputGenerator):
79    """ValidityOutputGenerator - subclass of OutputGenerator.
80
81    Generates AsciiDoc includes of valid usage information, for reference
82    pages and the specification. Similar to DocOutputGenerator.
83
84    ---- methods ----
85    ValidityOutputGenerator(errFile, warnFile, diagFile) - args as for
86    OutputGenerator. Defines additional internal state.
87    ---- methods overriding base class ----
88    beginFile(genOpts)
89    endFile()
90    beginFeature(interface, emit)
91    endFeature()
92    genCmd(cmdinfo)
93    """
94
95    def __init__(self, *args, **kwargs):
96        super().__init__(*args, **kwargs)
97
98        self.currentExtension = ''
99
100    @property
101    def null(self):
102        """Preferred spelling of NULL.
103
104        Delegates to the object implementing ConventionsBase.
105        """
106        return self.conventions.null
107
108    @property
109    def structtype_member_name(self):
110        """Return name of the structure type member.
111
112        Delegates to the object implementing ConventionsBase.
113        """
114        return self.conventions.structtype_member_name
115
116    @property
117    def nextpointer_member_name(self):
118        """Return name of the structure pointer chain member.
119
120        Delegates to the object implementing ConventionsBase.
121        """
122        return self.conventions.nextpointer_member_name
123
124    def makeProseList(self, elements, fmt=plf.AND,
125                      comma_for_two_elts=False, *args, **kwargs):
126        """Make a (comma-separated) list for use in prose.
127
128        Adds a connective (by default, 'and')
129        before the last element if there are more than 1.
130
131        Optionally adds a quantifier (like 'any') before a list of 2 or more,
132        if specified by fmt.
133
134        Delegates to the object implementing ConventionsBase.
135        """
136        if not elements:
137            raise ValueError(
138                'Cannot pass an empty list if you are trying to make a prose list.')
139        return self.conventions.makeProseList(elements,
140                                              fmt,
141                                              with_verb=False,
142                                              comma_for_two_elts=comma_for_two_elts,
143                                              *args, **kwargs)
144
145    def makeProseListIs(self, elements, fmt=plf.AND,
146                        comma_for_two_elts=False, *args, **kwargs):
147        """Make a (comma-separated) list for use in prose, followed by either 'is' or 'are' as appropriate.
148
149        Adds a connective (by default, 'and')
150        before the last element if there are more than 1.
151
152        Optionally adds a quantifier (like 'any') before a list of 2 or more,
153        if specified by fmt.
154
155        Delegates to the object implementing ConventionsBase.
156        """
157        if not elements:
158            raise ValueError(
159                'Cannot pass an empty list if you are trying to make a prose list.')
160        return self.conventions.makeProseList(elements,
161                                              fmt,
162                                              with_verb=True,
163                                              comma_for_two_elts=comma_for_two_elts,
164                                              *args, **kwargs)
165
166    def makeValidityCollection(self, entity_name):
167        """Create a ValidityCollection object, passing along our Conventions."""
168        return ValidityCollection(entity_name, self.conventions)
169
170    def beginFile(self, genOpts):
171        if not genOpts.conventions:
172            raise RuntimeError(
173                'Must specify conventions object to generator options')
174        self.conventions = genOpts.conventions
175        # Vulkan says 'must: be a valid pointer' a lot, OpenXR just says
176        # 'must: be a pointer'.
177        self.valid_pointer_text = ' '.join(
178            (x for x in (self.conventions.valid_pointer_prefix, 'pointer') if x))
179        OutputGenerator.beginFile(self, genOpts)
180
181    def endFile(self):
182        OutputGenerator.endFile(self)
183
184    def beginFeature(self, interface, emit):
185        # Start processing in superclass
186        OutputGenerator.beginFeature(self, interface, emit)
187        self.currentExtension = interface.get('name')
188
189    def endFeature(self):
190        # Finish processing in superclass
191        OutputGenerator.endFeature(self)
192
193    @property
194    def struct_macro(self):
195        """Get the appropriate format macro for a structure."""
196        # delegate to conventions
197        return self.conventions.struct_macro
198
199    def makeStructName(self, name):
200        """Prepend the appropriate format macro for a structure to a structure type name."""
201        # delegate to conventions
202        return self.conventions.makeStructName(name)
203
204    def makeParameterName(self, name):
205        """Prepend the appropriate format macro for a parameter/member to a parameter name."""
206        return 'pname:' + name
207
208    def makeBaseTypeName(self, name):
209        """Prepend the appropriate format macro for a 'base type' to a type name."""
210        return 'basetype:' + name
211
212    def makeEnumerationName(self, name):
213        """Prepend the appropriate format macro for an enumeration type to a enum type name."""
214        return 'elink:' + name
215
216    def makeFlagsName(self, name):
217        """Prepend the appropriate format macro for a flags type to a flags type name."""
218        return 'tlink:' + name
219
220    def makeFuncPointerName(self, name):
221        """Prepend the appropriate format macro for a function pointer type to a type name."""
222        return 'tlink:' + name
223
224    def makeExternalTypeName(self, name):
225        """Prepend the appropriate format macro for an external type like uint32_t to a type name."""
226        # delegate to conventions
227        return self.conventions.makeExternalTypeName(name)
228
229    def makeEnumerantName(self, name):
230        """Prepend the appropriate format macro for an enumerate (value) to a enum value name."""
231        return 'ename:' + name
232
233    def writeInclude(self, directory, basename, validity: ValidityCollection,
234                     threadsafety, commandpropertiesentry=None,
235                     successcodes=None, errorcodes=None):
236        """Generate an include file.
237
238        directory - subdirectory to put file in (absolute or relative pathname)
239        basename - base name of the file
240        validity - ValidityCollection to write.
241        threadsafety - List (may be empty) of thread safety statements to write.
242        successcodes - Optional success codes to document.
243        errorcodes - Optional error codes to document.
244        """
245        # Create subdirectory, if needed
246        directory = Path(directory)
247        if not directory.is_absolute():
248            directory = Path(self.genOpts.directory) / directory
249        self.makeDir(str(directory))
250
251        # Create validity file
252        filename = str(directory / f'{basename}{self.file_suffix}')
253        self.logMsg('diag', '# Generating include file:', filename)
254
255        with open(filename, 'w', encoding='utf-8') as fp:
256            write(self.conventions.warning_comment, file=fp)
257
258            # Valid Usage
259            if validity:
260                write('.Valid Usage (Implicit)', file=fp)
261                write('****', file=fp)
262                write(validity, file=fp, end='')
263                write('****', file=fp)
264                write('', file=fp)
265
266            # Host Synchronization
267            if threadsafety:
268                # The heading of this block differs between projects, so an Asciidoc attribute is used.
269                write('.{externsynctitle}', file=fp)
270                write('****', file=fp)
271                write(threadsafety, file=fp, end='')
272                write('****', file=fp)
273                write('', file=fp)
274
275            # Command Properties - contained within a block, to avoid table numbering
276            if commandpropertiesentry:
277                write('.Command Properties', file=fp)
278                write('****', file=fp)
279                write('[options="header", width="100%"]', file=fp)
280                write('|====', file=fp)
281                write(self.makeCommandPropertiesTableHeader(), file=fp)
282                write(commandpropertiesentry, file=fp)
283                write('|====', file=fp)
284                write('****', file=fp)
285                write('', file=fp)
286
287            # Success Codes - contained within a block, to avoid table numbering
288            if successcodes or errorcodes:
289                write('.Return Codes', file=fp)
290                write('****', file=fp)
291                if successcodes:
292                    write('ifndef::doctype-manpage[]', file=fp)
293                    write('<<fundamentals-successcodes,Success>>::', file=fp)
294                    write('endif::doctype-manpage[]', file=fp)
295                    write('ifdef::doctype-manpage[]', file=fp)
296                    write('On success, this command returns::', file=fp)
297                    write('endif::doctype-manpage[]', file=fp)
298                    write(successcodes, file=fp)
299                if errorcodes:
300                    write('ifndef::doctype-manpage[]', file=fp)
301                    write('<<fundamentals-errorcodes,Failure>>::', file=fp)
302                    write('endif::doctype-manpage[]', file=fp)
303                    write('ifdef::doctype-manpage[]', file=fp)
304                    write('On failure, this command returns::', file=fp)
305                    write('endif::doctype-manpage[]', file=fp)
306                    write(errorcodes, file=fp)
307                write('****', file=fp)
308                write('', file=fp)
309
310    def paramIsStaticArray(self, param):
311        """Check if the parameter passed in is a static array."""
312        tail = param.find('name').tail
313        return tail and tail[0] == '['
314
315    def paramIsConst(self, param):
316        """Check if the parameter passed in has a type that mentions const."""
317        return param.text is not None and 'const' in param.text
318
319    def staticArrayLength(self, param):
320        """Get the length of a parameter that has been identified as a static array."""
321        paramenumsize = param.find('enum')
322        if paramenumsize is not None:
323            return paramenumsize.text
324            # TODO switch to below when cosmetic changes OK
325            # return self.makeEnumerantName(paramenumsize.text)
326
327        return param.find('name').tail[1:-1]
328
329    def getHandleDispatchableAncestors(self, typename):
330        """Get the ancestors of a handle object."""
331        ancestors = []
332        current = typename
333        while True:
334            current = self.getHandleParent(current)
335            if current is None:
336                return ancestors
337            if self.isHandleTypeDispatchable(current):
338                ancestors.append(current)
339
340    def isHandleTypeDispatchable(self, handlename):
341        """Check if a parent object is dispatchable or not."""
342        handle = self.registry.tree.find(
343            "types/type/[name='" + handlename + "'][@category='handle']")
344        if handle is not None and getElemType(handle) == 'VK_DEFINE_HANDLE':
345            return True
346        else:
347            return False
348
349    def isHandleOptional(self, param, params):
350        # Simple, if it is optional, return true
351        if param.get('optional') is not None:
352            return True
353
354        # If no validity is being generated, it usually means that validity is complex and not absolute, so say yes.
355        if param.get('noautovalidity') is not None:
356            return True
357
358        # If the parameter is an array and we have not already returned, find out if any of the len parameters are optional
359        if self.paramIsArray(param):
360            for length in LengthEntry.parse_len_from_param(param):
361                if not length.other_param_name:
362                    # do not care about constants or "null-terminated"
363                    continue
364
365                other_param = findNamedElem(params, length.other_param_name)
366                if other_param is None:
367                    self.logMsg('warn', length.other_param_name,
368                                'is listed as a length for parameter', param, 'but no such parameter exists')
369                if other_param and other_param.get('optional'):
370                    return True
371
372        return False
373
374    def makeOptionalPre(self, param):
375        # Do not generate this stub for bitflags
376        param_name = getElemName(param)
377        paramtype = getElemType(param)
378        type_category = self.getTypeCategory(paramtype)
379        is_optional = param.get('optional').split(',')[0] == 'true'
380        if type_category != 'bitmask' and is_optional:
381            if self.paramIsArray(param) or self.paramIsPointer(param):
382                optional_val = self.null
383            elif type_category == 'handle':
384                if self.isHandleTypeDispatchable(paramtype):
385                    optional_val = self.null
386                else:
387                    optional_val = 'dlink:' + self.conventions.api_prefix + 'NULL_HANDLE'
388            else:
389                optional_val = self.conventions.zero
390            return 'If {} is not {}, '.format(
391                self.makeParameterName(param_name),
392                optional_val)
393
394        return ""
395
396    def makeParamValidityPre(self, param, params, selector):
397        """Make the start of an entry for a parameter's validity, including a chunk of text if it is an array."""
398        param_name = getElemName(param)
399        paramtype = getElemType(param)
400
401        # General pre-amble. Check optionality and add stuff.
402        entry = ValidityEntry(anchor=(param_name, 'parameter'))
403        is_optional = param.get('optional') is not None and param.get('optional').split(',')[0] == 'true'
404
405        # This is for a union member, and the valid member is chosen by an enum selection
406        if selector:
407            selection = param.get('selection')
408
409            entry += 'If {} is {}, '.format(
410                self.makeParameterName(selector),
411                self.makeEnumerantName(selection))
412
413            if is_optional:
414                entry += "and "
415                optionalpre = self.makeOptionalPre(param)
416                entry += optionalpre[0].lower() + optionalpre[1:]
417
418            return entry
419
420        if self.paramIsStaticArray(param):
421            if paramtype != 'char':
422                entry += 'Any given element of '
423            return entry
424
425        if self.paramIsArray(param) and param.get('len') != LengthEntry.NULL_TERMINATED_STRING:
426            # Find all the parameters that are called out as optional,
427            # so we can document that they might be zero, and the array may be ignored
428            optionallengths = []
429            for length in LengthEntry.parse_len_from_param(param):
430                if not length.other_param_name:
431                    # Only care about length entries that are parameter names
432                    continue
433
434                other_param = findNamedElem(params, length.other_param_name)
435                other_param_optional = (other_param is not None) and (
436                    other_param.get('optional') is not None)
437
438                if other_param is None or not other_param_optional:
439                    # Do not care about not-found params or non-optional params
440                    continue
441
442                if self.paramIsPointer(other_param):
443                    optionallengths.append(
444                        'the value referenced by ' + self.makeParameterName(length.other_param_name))
445                else:
446                    optionallengths.append(
447                        self.makeParameterName(length.other_param_name))
448
449            # Document that these arrays may be ignored if any of the length values are 0
450            if optionallengths or is_optional:
451                entry += 'If '
452            if optionallengths:
453                entry += self.makeProseListIs(optionallengths, fmt=plf.OR)
454                entry += ' not %s, ' % self.conventions.zero
455            # TODO enabling this in OpenXR, as used in Vulkan, causes nonsensical things like
456            # "If pname:propertyCapacityInput is not `0`, and pname:properties is not `NULL`, pname:properties must: be a pointer to an array of pname:propertyCapacityInput slink:XrApiLayerProperties structures"
457            if optionallengths and is_optional:
458                entry += 'and '
459            if is_optional:
460                entry += self.makeParameterName(param_name)
461                # TODO switch when cosmetic changes OK
462                # entry += ' is not {}, '.format(self.null)
463                entry += ' is not `NULL`, '
464            return entry
465
466        if param.get('optional'):
467            entry += self.makeOptionalPre(param)
468            return entry
469
470        # If none of the early returns happened, we at least return an empty
471        # entry with an anchor.
472        return entry
473
474    def createValidationLineForParameterImpl(self, blockname, param, params, typetext, selector, parentname):
475        """Make the generic validity portion used for all parameters.
476
477        May return None if nothing to validate.
478        """
479        if param.get('noautovalidity') is not None:
480            return None
481
482        validity = self.makeValidityCollection(blockname)
483        param_name = getElemName(param)
484        paramtype = getElemType(param)
485
486        entry = self.makeParamValidityPre(param, params, selector)
487
488        # This is for a child member of a union
489        if selector:
490            entry += 'the {} member of {} must: be '.format(self.makeParameterName(param_name), self.makeParameterName(parentname))
491        else:
492            entry += '{} must: be '.format(self.makeParameterName(param_name))
493
494        if self.paramIsStaticArray(param) and paramtype == 'char':
495            # TODO this is a minor hack to determine if this is a command parameter or a struct member
496            if self.paramIsConst(param) or blockname.startswith(self.conventions.type_prefix):
497                entry += 'a null-terminated UTF-8 string whose length is less than or equal to '
498                entry += self.staticArrayLength(param)
499            else:
500                # This is a command's output parameter
501                entry += 'a character array of length %s ' % self.staticArrayLength(param)
502            validity += entry
503            return validity
504
505        elif self.paramIsArray(param):
506            # Arrays. These are hard to get right, apparently
507
508            lengths = LengthEntry.parse_len_from_param(param)
509
510            for i, length in enumerate(LengthEntry.parse_len_from_param(param)):
511                if i == 0:
512                    # If the first index, make it singular.
513                    entry += 'a '
514                    array_text = 'an array'
515                    pointer_text = self.valid_pointer_text
516                else:
517                    array_text = 'arrays'
518                    pointer_text = self.valid_pointer_text + 's'
519
520                if length.null_terminated:
521                    # This should always be the last thing.
522                    # If it ever is not for some bizarre reason, then this
523                    # will need some massaging.
524                    entry += 'null-terminated '
525                elif length.number == 1:
526                    entry += pointer_text
527                    entry += ' to '
528                else:
529                    entry += pointer_text
530                    entry += ' to '
531                    entry += array_text
532                    entry += ' of '
533                    # Handle equations, which are currently denoted with latex
534                    if length.math:
535                        # Handle equations, which are currently denoted with latex
536                        entry += str(length)
537                    else:
538                        entry += self.makeParameterName(str(length))
539                    entry += ' '
540
541            # Void pointers do not actually point at anything - remove the word "to"
542            if paramtype == 'void':
543                if lengths[-1].number == 1:
544                    if len(lengths) > 1:
545                        # Take care of the extra s added by the post array chunk function. #HACK#
546                        entry.drop_end(5)
547                    else:
548                        entry.drop_end(4)
549
550                    # This has not been hit, so this has not been tested recently.
551                    raise UnhandledCaseError(
552                        "Got void pointer param/member with last length 1")
553                else:
554                    # An array of void values is a byte array.
555                    entry += 'byte'
556
557            elif paramtype == 'char':
558                # A null terminated array of chars is a string
559                if lengths[-1].null_terminated:
560                    entry += 'UTF-8 string'
561                else:
562                    # Else it is just a bunch of chars
563                    entry += 'char value'
564
565            elif self.paramIsConst(param):
566                # If a value is "const" that means it will not get modified,
567                # so it must be valid going into the function.
568                if 'const' in param.text:
569
570                    if not self.isStructAlwaysValid(paramtype):
571                        entry += 'valid '
572
573            # Check if the array elements are optional
574            array_element_optional = param.get('optional') is not None    \
575                      and len(param.get('optional').split(',')) == len(LengthEntry.parse_len_from_param(param)) + 1 \
576                      and param.get('optional').split(',')[-1] == 'true'
577            if array_element_optional and self.getTypeCategory(paramtype) != 'bitmask': # bitmask is handled later
578                entry += 'or dlink:' + self.conventions.api_prefix + 'NULL_HANDLE '
579
580            entry += typetext
581
582            # pluralize
583            if len(lengths) > 1 or (lengths[0] != 1 and not lengths[0].null_terminated):
584                entry += 's'
585
586            return self.handleRequiredBitmask(blockname, param, paramtype, entry, 'true' if array_element_optional else None)
587
588        if self.paramIsPointer(param):
589            # Handle pointers - which are really special case arrays (i.e.
590            # they do not have a length)
591            # TODO  should do something here if someone ever uses some intricate comma-separated `optional`
592            pointercount = param.find('type').tail.count('*')
593
594            # Treat void* as an int
595            if paramtype == 'void':
596                optional = param.get('optional')
597                # If there is only void*, it is just optional int - we do not need any language.
598                if pointercount == 1 and optional is not None:
599                    return None  # early return
600                # Treat the inner-most void* as an int
601                pointercount -= 1
602
603            # Could be multi-level pointers (e.g. ppData - pointer to a pointer). Handle that.
604            entry += 'a '
605            entry += (self.valid_pointer_text + ' to a ') * pointercount
606
607            # Handle void* and pointers to it
608            if paramtype == 'void':
609                if optional is None or optional.split(',')[pointercount]:
610                    # The last void* is just optional int (e.g. to be filled by the impl.)
611                    typetext = 'pointer value'
612
613            # If a value is "const" that means it will not get modified, so
614            # it must be valid going into the function.
615            elif self.paramIsConst(param) and paramtype != 'void':
616                entry += 'valid '
617
618            entry += typetext
619            return self.handleRequiredBitmask(blockname, param, paramtype, entry, param.get('optional'))
620
621        # Add additional line for non-optional bitmasks
622        if self.getTypeCategory(paramtype) == 'bitmask':
623            # TODO does not really handle if someone tries something like optional="true,false"
624            # TODO OpenXR has 0 or a valid combination of flags, for optional things.
625            # Vulkan does not...
626            # isMandatory = param.get('optional') is None
627            # if not isMandatory:
628            #     entry += self.conventions.zero
629            #     entry += ' or '
630            # Non-pointer, non-optional things must be valid
631            entry += 'a valid {}'.format(typetext)
632
633            return self.handleRequiredBitmask(blockname, param, paramtype, entry, param.get('optional'))
634
635        # Non-pointer, non-optional things must be valid
636        entry += 'a valid {}'.format(typetext)
637        return entry
638
639    def handleRequiredBitmask(self, blockname, param, paramtype, entry, optional):
640        # TODO does not really handle if someone tries something like optional="true,false"
641        if self.getTypeCategory(paramtype) != 'bitmask' or optional == 'true':
642            return entry
643        if self.paramIsPointer(param) and not self.paramIsArray(param):
644            # This is presumably an output parameter
645            return entry
646
647        param_name = getElemName(param)
648        # If mandatory, then we need two entries instead of just one.
649        validity = self.makeValidityCollection(blockname)
650        validity += entry
651
652        entry2 = ValidityEntry(anchor=(param_name, 'requiredbitmask'))
653        if self.paramIsArray(param):
654            entry2 += 'Each element of '
655        entry2 += '{} must: not be {}'.format(
656            self.makeParameterName(param_name), self.conventions.zero)
657        validity += entry2
658        return validity
659
660    def createValidationLineForParameter(self, blockname, param, params, typecategory, selector, parentname):
661        """Make an entire validation entry for a given parameter."""
662        param_name = getElemName(param)
663        paramtype = getElemType(param)
664
665        is_array = self.paramIsArray(param)
666        is_pointer = self.paramIsPointer(param)
667        needs_recursive_validity = (is_array
668                                    or is_pointer
669                                    or not self.isStructAlwaysValid(paramtype))
670        typetext = None
671        if paramtype in ('void', 'char'):
672            # Chars and void are special cases - we call the impl function,
673            # but do not use the typetext.
674            # A null-terminated char array is a string, else it is chars.
675            # An array of void values is a byte array, a void pointer is just a pointer to nothing in particular
676            typetext = ''
677
678        elif typecategory == 'bitmask':
679            bitsname = paramtype.replace('Flags', 'FlagBits')
680            bitselem = self.registry.tree.find("enums[@name='" + bitsname + "']")
681
682            # If bitsname is an alias, then use the alias to get bitselem.
683            typeElem = self.registry.lookupElementInfo(bitsname, self.registry.typedict)
684            if typeElem is not None:
685                alias = self.registry.getAlias(typeElem.elem, self.registry.typedict)
686                if alias is not None:
687                    bitselem = self.registry.tree.find("enums[@name='" + alias + "']")
688
689            if bitselem is None or len(bitselem.findall('enum[@required="true"]')) == 0:
690                # Empty bit mask: presumably just a placeholder (or only in
691                # an extension not enabled for this build)
692                entry = ValidityEntry(
693                    anchor=(param_name, 'zerobitmask'))
694                entry += self.makeParameterName(param_name)
695                entry += ' must: be '
696                entry += self.conventions.zero
697                # Early return
698                return entry
699
700            is_const = self.paramIsConst(param)
701
702            if is_array:
703                if is_const:
704                    # input an array of bitmask values
705                    template = 'combinations of {bitsname} value'
706                else:
707                    template = '{paramtype} value'
708            elif is_pointer:
709                if is_const:
710                    template = 'combination of {bitsname} values'
711                else:
712                    template = '{paramtype} value'
713            else:
714                template = 'combination of {bitsname} values'
715
716            # The above few cases all use makeEnumerationName, just with different context.
717            typetext = template.format(
718                bitsname=self.makeEnumerationName(bitsname),
719                paramtype=self.makeFlagsName(paramtype))
720
721        elif typecategory == 'handle':
722            typetext = '{} handle'.format(self.makeStructName(paramtype))
723
724        elif typecategory == 'enum':
725            typetext = '{} value'.format(self.makeEnumerationName(paramtype))
726
727        elif typecategory == 'funcpointer':
728            typetext = '{} value'.format(self.makeFuncPointerName(paramtype))
729
730        elif typecategory == 'struct':
731            if needs_recursive_validity:
732                typetext = '{} structure'.format(
733                    self.makeStructName(paramtype))
734
735        elif typecategory == 'union':
736            if needs_recursive_validity:
737                typetext = '{} union'.format(self.makeStructName(paramtype))
738
739        elif self.paramIsArray(param) or self.paramIsPointer(param):
740            # TODO sync cosmetic changes from OpenXR?
741            if typecategory is None:
742                typetext = f'code:{paramtype} value'
743            else:
744                typetext = '{} value'.format(self.makeBaseTypeName(paramtype))
745
746        elif typecategory is None:
747            if not self.isStructAlwaysValid(paramtype):
748                typetext = '{} value'.format(
749                    self.makeExternalTypeName(paramtype))
750
751            # "a valid uint32_t value" does not make much sense.
752            pass
753
754        # If any of the above conditions matched and set typetext,
755        # we call using it.
756        if typetext is not None:
757            return self.createValidationLineForParameterImpl(
758                blockname, param, params, typetext, selector, parentname)
759        return None
760
761    def makeHandleValidityParent(self, param, params):
762        """Make a validity entry for a handle's parent object.
763
764        Creates 'parent' VUID.
765        """
766        param_name = getElemName(param)
767        paramtype = getElemType(param)
768
769        # Deal with handle parents
770        handleparent = self.getHandleParent(paramtype)
771        if handleparent is None:
772            return None
773
774        otherparam = findTypedElem(params, handleparent)
775        if otherparam is None:
776            return None
777
778        parent_name = getElemName(otherparam)
779        entry = ValidityEntry(anchor=(param_name, 'parent'))
780
781        is_optional = self.isHandleOptional(param, params)
782
783        if self.paramIsArray(param):
784            template = 'Each element of {}'
785            if is_optional:
786                template += ' that is a valid handle'
787        elif is_optional:
788            template = 'If {} is a valid handle, it'
789        else:
790            # not optional, not an array. Just say the parameter name.
791            template = '{}'
792
793        entry += template.format(self.makeParameterName(param_name))
794
795        entry += ' must: have been created, allocated, or retrieved from {}'.format(
796            self.makeParameterName(parent_name))
797
798        return entry
799
800    def makeAsciiDocHandlesCommonAncestor(self, blockname, handles, params):
801        """Make an asciidoc validity entry for a common ancestors between handles.
802
803        Only handles parent validity for signatures taking multiple handles
804        any ancestors also being supplied to this function.
805        (e.g. "Each of x, y, and z must: come from the same slink:ParentHandle")
806        See self.makeAsciiDocHandleParent() for instances where the parent
807        handle is named and also passed.
808
809        Creates 'commonparent' VUID.
810        """
811        # TODO Replace with refactored code from OpenXR
812        entry = None
813
814        if len(handles) > 1:
815            ancestormap = {}
816            anyoptional = False
817            # Find all the ancestors
818            for param in handles:
819                paramtype = getElemType(param)
820
821                if not self.paramIsPointer(param) or (param.text and 'const' in param.text):
822                    ancestors = self.getHandleDispatchableAncestors(paramtype)
823
824                    ancestormap[param] = ancestors
825
826                    anyoptional |= self.isHandleOptional(param, params)
827
828            # Remove redundant ancestor lists
829            for param in handles:
830                paramtype = getElemType(param)
831
832                removals = []
833                for ancestors in ancestormap.items():
834                    if paramtype in ancestors[1]:
835                        removals.append(ancestors[0])
836
837                if removals != []:
838                    for removal in removals:
839                        del(ancestormap[removal])
840
841            # Intersect
842
843            if len(ancestormap.values()) > 1:
844                current = list(ancestormap.values())[0]
845                for ancestors in list(ancestormap.values())[1:]:
846                    current = [val for val in current if val in ancestors]
847
848                if len(current) > 0:
849                    commonancestor = current[0]
850
851                    if len(ancestormap.keys()) > 1:
852
853                        entry = ValidityEntry(anchor=('commonparent',))
854
855                        parametertexts = []
856                        for param in ancestormap.keys():
857                            param_name = getElemName(param)
858                            parametertext = self.makeParameterName(param_name)
859                            if self.paramIsArray(param):
860                                parametertext = 'the elements of ' + parametertext
861                            parametertexts.append(parametertext)
862
863                        parametertexts.sort()
864
865                        if len(parametertexts) > 2:
866                            entry += 'Each of '
867                        else:
868                            entry += 'Both of '
869
870                        entry += self.makeProseList(parametertexts,
871                                                    comma_for_two_elts=True)
872                        if anyoptional is True:
873                            entry += ' that are valid handles of non-ignored parameters'
874                        entry += ' must: have been created, allocated, or retrieved from the same '
875                        entry += self.makeStructName(commonancestor)
876
877        return entry
878
879    def makeStructureTypeFromName(self, structname):
880        """Create text for a structure type name, like ename:VK_STRUCTURE_TYPE_CREATE_INSTANCE_INFO"""
881        return self.makeEnumerantName(self.conventions.generate_structure_type_from_name(structname))
882
883    def makeStructureTypeValidity(self, structname):
884        """Generate an validity line for the type value of a struct.
885
886        Creates VUID named like the member name.
887        """
888        info = self.registry.typedict.get(structname)
889        assert(info is not None)
890
891        # If this fails (meaning we have something other than a struct in here),
892        # then the caller is wrong:
893        # probably passing the wrong value for structname.
894        members = info.getMembers()
895        assert(members)
896
897        # If this fails, see caller: this should only get called for a struct type with a type value.
898        param = findNamedElem(members, self.structtype_member_name)
899        # OpenXR gets some structs without a type field in here, so cannot assert
900        assert(param is not None)
901        # if param is None:
902        #     return None
903
904        entry = ValidityEntry(
905            anchor=(self.structtype_member_name, self.structtype_member_name))
906        entry += self.makeParameterName(self.structtype_member_name)
907        entry += ' must: be '
908
909        values = param.get('values', '').split(',')
910        if values:
911            # Extract each enumerant value. They could be validated in the
912            # same fashion as validextensionstructs in
913            # makeStructureExtensionPointer, although that is not relevant in
914            # the current extension struct model.
915            entry += self.makeProseList((self.makeEnumerantName(v)
916                                         for v in values), 'or')
917            return entry
918
919        if 'Base' in structname:
920            # This type does not even have any values for its type, and it
921            # seems like it might be a base struct that we would expect to
922            # lack its own type, so omit the entire statement
923            return None
924
925        self.logMsg('warn', 'No values were marked-up for the structure type member of',
926                    structname, 'so making one up!')
927        entry += self.makeStructureTypeFromName(structname)
928
929        return entry
930
931    def makeStructureExtensionPointer(self, blockname, param):
932        """Generate an validity line for the pointer chain member value of a struct."""
933        param_name = getElemName(param)
934
935        if param.get('validextensionstructs') is not None:
936            self.logMsg('warn', blockname,
937                        'validextensionstructs is deprecated/removed', '\n')
938
939        entry = ValidityEntry(
940            anchor=(param_name, self.nextpointer_member_name))
941        validextensionstructs = self.registry.validextensionstructs.get(
942            blockname)
943        extensionstructs = []
944        duplicatestructs = []
945
946        if validextensionstructs is not None:
947            # Check each structure name and skip it if not required by the
948            # generator. This allows tagging extension structs in the XML
949            # that are only included in validity when needed for the spec
950            # being targeted.
951            # Track the required structures, and of the required structures,
952            # those that allow duplicates in the pNext chain.
953            for struct in validextensionstructs:
954                # Unpleasantly breaks encapsulation. Should be a method in the registry class
955                t = self.registry.lookupElementInfo(
956                    struct, self.registry.typedict)
957                if t is None:
958                    self.logMsg('warn', 'makeStructureExtensionPointer: struct', struct,
959                                'is in a validextensionstructs= attribute but is not in the registry')
960                elif t.required:
961                    extensionstructs.append('slink:' + struct)
962                    if t.elem.get('allowduplicate') == 'true':
963                        duplicatestructs.append('slink:' + struct)
964                else:
965                    self.logMsg(
966                        'diag', 'makeStructureExtensionPointer: struct', struct, 'IS NOT required')
967
968        if not extensionstructs:
969            entry += '{} must: be {}'.format(
970                self.makeParameterName(param_name), self.null)
971            return entry
972
973        if len(extensionstructs) == 1:
974            entry += '{} must: be {} or a pointer to a valid instance of {}'.format(self.makeParameterName(param_name), self.null,
975                                                                                    extensionstructs[0])
976        else:
977            # More than one extension struct.
978            entry += 'Each {} member of any structure (including this one) in the pname:{} chain '.format(
979                self.makeParameterName(param_name), self.nextpointer_member_name)
980            entry += 'must: be either {} or a pointer to a valid instance of '.format(
981                self.null)
982
983            entry += self.makeProseList(extensionstructs, fmt=plf.OR)
984
985        validity = self.makeValidityCollection(blockname)
986        validity += entry
987
988        # Generate VU statement requiring unique structures in the pNext
989        # chain.
990        # NOTE: OpenXR always allows non-unique type values. Instances other
991        # than the first are just ignored
992
993        vu = ('The pname:' +
994              self.structtype_member_name +
995              ' value of each struct in the pname:' +
996              self.nextpointer_member_name +
997              ' chain must: be unique')
998        anchor = (self.conventions.member_used_for_unique_vuid, 'unique')
999
1000        # If duplicates of some structures are allowed, they are called out
1001        # explicitly.
1002        num = len(duplicatestructs)
1003        if num > 0:
1004            vu = (vu +
1005                  ', with the exception of structures of type ' +
1006                  self.makeProseList(duplicatestructs, fmt=plf.OR))
1007
1008        validity.addValidityEntry(vu, anchor = anchor )
1009
1010        return validity
1011
1012    def addSharedStructMemberValidity(self, struct, blockname, param, validity):
1013        """Generate language to independently validate a parameter, for those validated even in output.
1014
1015        Return value indicates whether it was handled internally (True) or if it may need more validity (False)."""
1016        param_name = getElemName(param)
1017        paramtype = getElemType(param)
1018        if param.get('noautovalidity') is None:
1019
1020            if self.conventions.is_structure_type_member(paramtype, param_name):
1021                validity += self.makeStructureTypeValidity(blockname)
1022                return True
1023
1024            if self.conventions.is_nextpointer_member(paramtype, param_name):
1025                # Vulkan: the addition of validity here is conditional unlike OpenXR.
1026                if struct.get('structextends') is None:
1027                    validity += self.makeStructureExtensionPointer(
1028                        blockname, param)
1029                return True
1030        return False
1031
1032    def makeOutputOnlyStructValidity(self, cmd, blockname, params):
1033        """Generate all the valid usage information for a struct that is entirely output.
1034
1035        That is, it is only ever filled out by the implementation other than
1036        the structure type and pointer chain members.
1037        Thus, we only create validity for the pointer chain member.
1038        """
1039        # Start the validity collection for this struct
1040        validity = self.makeValidityCollection(blockname)
1041
1042        for param in params:
1043            self.addSharedStructMemberValidity(
1044                cmd, blockname, param, validity)
1045
1046        return validity
1047
1048    def isVKVersion11(self):
1049        """Returns true if VK_VERSION_1_1 is being emitted."""
1050        vk11 = re.match(self.registry.genOpts.emitversions, 'VK_VERSION_1_1') is not None
1051        return vk11
1052
1053    def videocodingRequired(self):
1054        """Returns true if VK_KHR_video_queue is being emitted and thus validity
1055        with respect to the videocoding attribute should be generated."""
1056        return 'VK_KHR_video_queue' in self.registry.requiredextensions
1057
1058    def getVideocoding(self, cmd):
1059        """Returns the value of the videocoding attribute, also considering the
1060        default value when the attribute is not present."""
1061        videocoding = cmd.get('videocoding')
1062        if videocoding is None:
1063            videocoding = 'outside'
1064        return videocoding
1065
1066    def makeStructOrCommandValidity(self, cmd, blockname, params):
1067        """Generate all the valid usage information for a given struct or command."""
1068        validity = self.makeValidityCollection(blockname)
1069        handles = []
1070        arraylengths = dict()
1071        for param in params:
1072            param_name = getElemName(param)
1073            paramtype = getElemType(param)
1074
1075            # Valid usage ID tags (VUID) are generated for various
1076            # conditions based on the name of the block (structure or
1077            # command), name of the element (member or parameter), and type
1078            # of VU statement.
1079
1080            # Get the type's category
1081            typecategory = self.getTypeCategory(paramtype)
1082
1083            if not self.addSharedStructMemberValidity(
1084                    cmd, blockname, param, validity):
1085                if not param.get('selector'):
1086                    validity += self.createValidationLineForParameter(
1087                        blockname, param, params, typecategory, None, None)
1088                else:
1089                    selector = param.get('selector')
1090                    if typecategory != 'union':
1091                        self.logMsg('warn', 'selector attribute set on non-union parameter', param_name, 'in', blockname)
1092
1093                    paraminfo = self.registry.lookupElementInfo(paramtype, self.registry.typedict)
1094
1095                    for member in paraminfo.getMembers():
1096                        membertype = getElemType(member)
1097                        membertypecategory = self.getTypeCategory(membertype)
1098
1099                        validity += self.createValidationLineForParameter(
1100                            blockname, member, paraminfo.getMembers(), membertypecategory, selector, param_name)
1101
1102            # Ensure that any parenting is properly validated, and list that a handle was found
1103            if typecategory == 'handle':
1104                handles.append(param)
1105
1106            # Get the array length for this parameter
1107            lengths = LengthEntry.parse_len_from_param(param)
1108            if lengths:
1109                arraylengths.update({length.other_param_name: length
1110                                     for length in lengths
1111                                     if length.other_param_name})
1112
1113        # For any vkQueue* functions, there might be queue type data
1114        if 'vkQueue' in blockname:
1115            # The queue type must be valid
1116            queuebits = _parse_queue_bits(cmd)
1117            if queuebits:
1118                entry = ValidityEntry(anchor=('queuetype',))
1119                entry += 'The pname:queue must: support '
1120                entry += self.makeProseList(queuebits,
1121                                            fmt=plf.OR, comma_for_two_elts=True)
1122                entry += ' operations'
1123                validity += entry
1124
1125        if 'vkCmd' in blockname:
1126            # The commandBuffer parameter must be being recorded
1127            entry = ValidityEntry(anchor=('commandBuffer', 'recording'))
1128            entry += 'pname:commandBuffer must: be in the <<commandbuffers-lifecycle, recording state>>'
1129            validity += entry
1130
1131            #
1132            # Start of valid queue type validation - command pool must have been
1133            # allocated against a queue with at least one of the valid queue types
1134            entry = ValidityEntry(anchor=('commandBuffer', 'cmdpool'))
1135
1136            #
1137            # This test for vkCmdFillBuffer is a hack, since we have no path
1138            # to conditionally have queues enabled or disabled by an extension.
1139            # As the VU stuff is all moving out (hopefully soon), this hack solves the issue for now
1140            if blockname == 'vkCmdFillBuffer':
1141                entry += 'The sname:VkCommandPool that pname:commandBuffer was allocated from must: support '
1142                if self.isVKVersion11() or 'VK_KHR_maintenance1' in self.registry.requiredextensions:
1143                    entry += 'transfer, graphics or compute operations'
1144                else:
1145                    entry += 'graphics or compute operations'
1146            else:
1147                # The queue type must be valid
1148                queuebits = _parse_queue_bits(cmd)
1149                assert(queuebits)
1150                entry += 'The sname:VkCommandPool that pname:commandBuffer was allocated from must: support '
1151                entry += self.makeProseList(queuebits,
1152                                            fmt=plf.OR, comma_for_two_elts=True)
1153                entry += ' operations'
1154            validity += entry
1155
1156            # Must be called inside/outside a render pass appropriately
1157            renderpass = cmd.get('renderpass')
1158
1159            if renderpass != 'both':
1160                entry = ValidityEntry(anchor=('renderpass',))
1161                entry += 'This command must: only be called '
1162                entry += renderpass
1163                entry += ' of a render pass instance'
1164                validity += entry
1165
1166            # Must be called inside/outside a video coding scope appropriately
1167            if self.videocodingRequired():
1168                videocoding = self.getVideocoding(cmd)
1169                if videocoding != 'both':
1170                    entry = ValidityEntry(anchor=('videocoding',))
1171                    entry += 'This command must: only be called '
1172                    entry += videocoding
1173                    entry += ' of a video coding scope'
1174                    validity += entry
1175
1176            # Must be in the right level command buffer
1177            cmdbufferlevel = cmd.get('cmdbufferlevel')
1178
1179            if cmdbufferlevel != 'primary,secondary':
1180                entry = ValidityEntry(anchor=('bufferlevel',))
1181                entry += 'pname:commandBuffer must: be a '
1182                entry += cmdbufferlevel
1183                entry += ' sname:VkCommandBuffer'
1184                validity += entry
1185
1186        # Any non-optional arraylengths should specify they must be greater than 0
1187        array_length_params = ((param, getElemName(param))
1188                               for param in params
1189                               if getElemName(param) in arraylengths)
1190
1191        for param, param_name in array_length_params:
1192            if param.get('optional') is not None:
1193                continue
1194
1195            length = arraylengths[param_name]
1196            full_length = length.full_reference
1197
1198            # Is this just a name of a param? If false, then it is some kind
1199            # of qualified name (a member of a param for instance)
1200            simple_param_reference = (len(length.param_ref_parts) == 1)
1201            if not simple_param_reference:
1202                # Loop through to see if any parameters in the chain are optional
1203                array_length_parent = cmd
1204                array_length_optional = False
1205                for part in length.param_ref_parts:
1206                    # Overwrite the param so it ends up as the bottom level parameter for later checks
1207                    param = array_length_parent.find("*/[name='{}']".format(part))
1208
1209                    # If any parameter in the chain is optional, skip the implicit length requirement
1210                    array_length_optional |= (param.get('optional') is not None)
1211
1212                    # Lookup the type of the parameter for the next loop iteration
1213                    type = param.findtext('type')
1214                    array_length_parent = self.registry.tree.find("./types/type/[@name='{}']".format(type))
1215
1216                if array_length_optional:
1217                    continue
1218
1219            # Get all the array dependencies
1220            arrays = cmd.findall(
1221                "param/[@len='{}'][@optional='true']".format(full_length))
1222
1223            # Get all the optional array dependencies, including those not generating validity for some reason
1224            optionalarrays = arrays + \
1225                cmd.findall(
1226                    "param/[@len='{}'][@noautovalidity='true']".format(full_length))
1227
1228            entry = ValidityEntry(anchor=(full_length, 'arraylength'))
1229            # Allow lengths to be arbitrary if all their dependents are optional
1230            if optionalarrays and len(optionalarrays) == len(arrays):
1231                entry += 'If '
1232                # TODO sync this section from OpenXR once cosmetic changes OK
1233
1234                optional_array_names = (self.makeParameterName(getElemName(array))
1235                                        for array in optionalarrays)
1236                entry += self.makeProseListIs(optional_array_names,
1237                                              plf.ANY_OR, comma_for_two_elts=True)
1238
1239                entry += ' not {}, '.format(self.null)
1240
1241            # TODO end needs sync cosmetic
1242            if self.paramIsPointer(param):
1243                entry += 'the value referenced by '
1244
1245            # Split and re-join here to insert pname: around ::
1246            entry += '::'.join(self.makeParameterName(part)
1247                               for part in full_length.split('::'))
1248            # TODO replace the previous statement with the following when cosmetic changes OK
1249            # entry += length.get_human_readable(make_param_name=self.makeParameterName)
1250
1251            entry += ' must: be greater than '
1252            entry += self.conventions.zero
1253            validity += entry
1254
1255        # Find the parents of all objects referenced in this command
1256        for param in handles:
1257            # Do not detect a parent for return values!
1258            if not self.paramIsPointer(param) or self.paramIsConst(param):
1259                validity += self.makeHandleValidityParent(param, params)
1260
1261        # Find the common ancestor of all objects referenced in this command
1262        validity += self.makeAsciiDocHandlesCommonAncestor(
1263            blockname, handles, params)
1264
1265        return validity
1266
1267    def makeThreadSafetyBlock(self, cmd, paramtext):
1268        """Generate thread-safety validity entries for cmd/structure"""
1269        # See also makeThreadSafetyBlock in validitygenerator.py
1270        validity = self.makeValidityCollection(getElemName(cmd))
1271
1272        # This text varies between projects, so an Asciidoctor attribute is used.
1273        extsync_prefix = "{externsyncprefix} "
1274
1275        # Find and add any parameters that are thread unsafe
1276        explicitexternsyncparams = cmd.findall(paramtext + "[@externsync]")
1277        if explicitexternsyncparams is not None:
1278            for param in explicitexternsyncparams:
1279                externsyncattribs = ExternSyncEntry.parse_externsync_from_param(
1280                    param)
1281                param_name = getElemName(param)
1282
1283                for attrib in externsyncattribs:
1284                    entry = ValidityEntry()
1285                    entry += extsync_prefix
1286                    if attrib.entirely_extern_sync:
1287                        if self.paramIsArray(param):
1288                            entry += 'each member of '
1289                        elif self.paramIsPointer(param):
1290                            entry += 'the object referenced by '
1291
1292                        entry += self.makeParameterName(param_name)
1293
1294                        if attrib.children_extern_sync:
1295                            entry += ', and any child handles,'
1296
1297                    else:
1298                        entry += 'pname:'
1299                        entry += str(attrib.full_reference)
1300                        # TODO switch to the following when cosmetic changes OK
1301                        # entry += attrib.get_human_readable(make_param_name=self.makeParameterName)
1302                    entry += ' must: be externally synchronized'
1303                    validity += entry
1304
1305        # Vulkan-specific
1306        # For any vkCmd* functions, the command pool is externally synchronized
1307        if cmd.find('proto/name') is not None and 'vkCmd' in cmd.find('proto/name').text:
1308            entry = ValidityEntry()
1309            entry += extsync_prefix
1310            entry += 'the sname:VkCommandPool that pname:commandBuffer was allocated from must: be externally synchronized'
1311            validity += entry
1312
1313        # Find and add any "implicit" parameters that are thread unsafe
1314        implicitexternsyncparams = cmd.find('implicitexternsyncparams')
1315        if implicitexternsyncparams is not None:
1316            for elem in implicitexternsyncparams:
1317                entry = ValidityEntry()
1318                entry += extsync_prefix
1319                entry += elem.text
1320                entry += ' must: be externally synchronized'
1321                validity += entry
1322
1323        return validity
1324
1325    def makeCommandPropertiesTableHeader(self):
1326        header  = '|<<VkCommandBufferLevel,Command Buffer Levels>>'
1327        header += '|<<vkCmdBeginRenderPass,Render Pass Scope>>'
1328        if self.videocodingRequired():
1329            header += '|<<vkCmdBeginVideoCodingKHR,Video Coding Scope>>'
1330        header += '|<<VkQueueFlagBits,Supported Queue Types>>'
1331        header += '|<<fundamentals-queueoperation-command-types,Command Type>>'
1332        return header
1333
1334    def makeCommandPropertiesTableEntry(self, cmd, name):
1335
1336        if 'vkCmd' in name:
1337            # Must be called in primary/secondary command buffers appropriately
1338            cmdbufferlevel = cmd.get('cmdbufferlevel')
1339            cmdbufferlevel = (' + \n').join(cmdbufferlevel.title().split(','))
1340            entry = '|' + cmdbufferlevel
1341
1342            # Must be called inside/outside a render pass appropriately
1343            renderpass = cmd.get('renderpass')
1344            renderpass = renderpass.capitalize()
1345            entry += '|' + renderpass
1346
1347            # Must be called inside/outside a video coding scope appropriately
1348            if self.videocodingRequired():
1349                videocoding = self.getVideocoding(cmd)
1350                videocoding = videocoding.capitalize()
1351                entry += '|' + videocoding
1352
1353            #
1354            # This test for vkCmdFillBuffer is a hack, since we have no path
1355            # to conditionally have queues enabled or disabled by an extension.
1356            # As the VU stuff is all moving out (hopefully soon), this hack solves the issue for now
1357            if name == 'vkCmdFillBuffer':
1358                if self.isVKVersion11() or 'VK_KHR_maintenance1' in self.registry.requiredextensions:
1359                    queues = 'Transfer + \nGraphics + \nCompute'
1360                else:
1361                    queues = 'Graphics + \nCompute'
1362            else:
1363                queues = cmd.get('queues')
1364                queues = (' + \n').join(queues.title().split(','))
1365
1366            entry += '|' + queues
1367
1368            # Print the task type
1369            tasks = cmd.get('tasks')
1370            tasks = (' + \n').join(tasks.title().split(','))
1371            entry += '|' + tasks
1372
1373            return entry
1374        elif 'vkQueue' in name:
1375            # For queue commands there are no command buffer level, render
1376            # pass, or video coding scope specific restrictions, but the
1377            # queue types are considered
1378            entry = '|-|-|'
1379            if self.videocodingRequired():
1380                entry += '-|'
1381
1382            queues = cmd.get('queues')
1383            if queues is None:
1384                queues = 'Any'
1385            else:
1386                queues = (' + \n').join(queues.upper().split(','))
1387
1388            return entry + queues
1389
1390        return None
1391
1392
1393    def findRequiredEnums(self, enums):
1394        """Check each enumerant name in the enums list and remove it if not
1395        required by the generator. This allows specifying success and error
1396        codes for extensions that are only included in validity when needed
1397        for the spec being targeted."""
1398        return self.keepOnlyRequired(enums, self.registry.enumdict)
1399
1400    def findRequiredCommands(self, commands):
1401        """Check each command name in the commands list and remove it if not
1402        required by the generator.
1403
1404        This will allow some state operations to take place before endFile."""
1405        return self.keepOnlyRequired(commands, self.registry.cmddict)
1406
1407    def keepOnlyRequired(self, names, info_dict):
1408        """Check each element name in the supplied dictionary and remove it if not
1409        required by the generator.
1410
1411        This will allow some operations to take place before endFile no matter the order of generation."""
1412        # TODO Unpleasantly breaks encapsulation. Should be a method in the registry class
1413
1414        def is_required(name):
1415            info = self.registry.lookupElementInfo(name, info_dict)
1416            if info is None:
1417                return False
1418            if not info.required:
1419                self.logMsg('diag', 'keepOnlyRequired: element',
1420                            name, 'IS NOT required, skipping')
1421            return info.required
1422
1423        return [name
1424                for name in names
1425                if is_required(name)]
1426
1427    def makeReturnCodeList(self, attrib, cmd, name):
1428        """Return a list of possible return codes for a function.
1429
1430        attrib is either 'successcodes' or 'errorcodes'.
1431        """
1432        return_lines = []
1433        RETURN_CODE_FORMAT = '* ename:{}'
1434
1435        codes_attr = cmd.get(attrib)
1436        if codes_attr:
1437            codes = self.findRequiredEnums(codes_attr.split(','))
1438            if codes:
1439                return_lines.extend((RETURN_CODE_FORMAT.format(code)
1440                                     for code in codes))
1441
1442        applicable_ext_codes = (ext_code
1443                                for ext_code in self.registry.commandextensionsuccesses
1444                                if ext_code.command == name)
1445        for ext_code in applicable_ext_codes:
1446            line = RETURN_CODE_FORMAT.format(ext_code.value)
1447            if ext_code.extension:
1448                line += ' [only if {} is enabled]'.format(
1449                    self.conventions.formatExtension(ext_code.extension))
1450
1451            return_lines.append(line)
1452        if return_lines:
1453            return '\n'.join(return_lines)
1454
1455        return None
1456
1457    def makeSuccessCodes(self, cmd, name):
1458        return self.makeReturnCodeList('successcodes', cmd, name)
1459
1460    def makeErrorCodes(self, cmd, name):
1461        return self.makeReturnCodeList('errorcodes', cmd, name)
1462
1463    def genCmd(self, cmdinfo, name, alias):
1464        """Command generation."""
1465        OutputGenerator.genCmd(self, cmdinfo, name, alias)
1466
1467        # @@@ (Jon) something needs to be done here to handle aliases, probably
1468
1469        validity = self.makeValidityCollection(name)
1470
1471        # OpenXR-only: make sure extension is enabled
1472        # validity.possiblyAddExtensionRequirement(self.currentExtension, 'calling flink:')
1473
1474        validity += self.makeStructOrCommandValidity(
1475            cmdinfo.elem, name, cmdinfo.getParams())
1476
1477        threadsafety = self.makeThreadSafetyBlock(cmdinfo.elem, 'param')
1478        commandpropertiesentry = None
1479
1480        # Vulkan-specific
1481        commandpropertiesentry = self.makeCommandPropertiesTableEntry(
1482            cmdinfo.elem, name)
1483        successcodes = self.makeSuccessCodes(cmdinfo.elem, name)
1484        errorcodes = self.makeErrorCodes(cmdinfo.elem, name)
1485
1486        # OpenXR-specific
1487        # self.generateStateValidity(validity, name)
1488
1489        self.writeInclude('protos', name, validity, threadsafety,
1490                          commandpropertiesentry, successcodes, errorcodes)
1491
1492    def genStruct(self, typeinfo, typeName, alias):
1493        """Struct Generation."""
1494        OutputGenerator.genStruct(self, typeinfo, typeName, alias)
1495
1496        # @@@ (Jon) something needs to be done here to handle aliases, probably
1497
1498        # Anything that is only ever returned cannot be set by the user, so
1499        # should not have any validity information.
1500        validity = self.makeValidityCollection(typeName)
1501        threadsafety = []
1502
1503        # OpenXR-only: make sure extension is enabled
1504        # validity.possiblyAddExtensionRequirement(self.currentExtension, 'using slink:')
1505
1506        if typeinfo.elem.get('category') != 'union':
1507            if typeinfo.elem.get('returnedonly') is None:
1508                validity += self.makeStructOrCommandValidity(
1509                    typeinfo.elem, typeName, typeinfo.getMembers())
1510                threadsafety = self.makeThreadSafetyBlock(typeinfo.elem, 'member')
1511
1512            else:
1513                # Need to generate structure type and next pointer chain member validation
1514                validity += self.makeOutputOnlyStructValidity(
1515                    typeinfo.elem, typeName, typeinfo.getMembers())
1516
1517        self.writeInclude('structs', typeName, validity,
1518                          threadsafety, None, None, None)
1519
1520    def genGroup(self, groupinfo, groupName, alias):
1521        """Group (e.g. C "enum" type) generation.
1522        For the validity generator, this just tags individual enumerants
1523        as required or not.
1524        """
1525        OutputGenerator.genGroup(self, groupinfo, groupName, alias)
1526
1527        # @@@ (Jon) something needs to be done here to handle aliases, probably
1528
1529        groupElem = groupinfo.elem
1530
1531        # Loop over the nested 'enum' tags. Keep track of the minimum and
1532        # maximum numeric values, if they can be determined; but only for
1533        # core API enumerants, not extension enumerants. This is inferred
1534        # by looking for 'extends' attributes.
1535        for elem in groupElem.findall('enum'):
1536            name = elem.get('name')
1537            ei = self.registry.lookupElementInfo(name, self.registry.enumdict)
1538
1539            if ei is None:
1540                self.logMsg('error',
1541                    f'genGroup({groupName}) - no element found for enum {name}')
1542
1543            # Tag enumerant as required or not
1544            ei.required = self.isEnumRequired(elem)
1545
1546    def genType(self, typeinfo, name, alias):
1547        """Type Generation."""
1548        OutputGenerator.genType(self, typeinfo, name, alias)
1549
1550        # @@@ (Jon) something needs to be done here to handle aliases, probably
1551
1552        category = typeinfo.elem.get('category')
1553        if category in ('struct', 'union'):
1554            self.genStruct(typeinfo, name, alias)
1555