1#!/usr/bin/python3 -i
2#
3# Copyright 2013-2023 The Khronos Group Inc.
4#
5# SPDX-License-Identifier: Apache-2.0
6
7from generator import OutputGenerator, write
8from spec_tools.attributes import ExternSyncEntry
9from spec_tools.util import getElemName
10
11import pdb
12
13def makeLink(link, altlink = None):
14    """Create an asciidoctor link, optionally with altlink text
15       if provided"""
16
17    if altlink is not None:
18        return '<<{},{}>>'.format(link, altlink)
19    else:
20        return '<<{}>>'.format(link)
21
22class SpirvCapabilityOutputGenerator(OutputGenerator):
23    """SpirvCapabilityOutputGenerator - subclass of OutputGenerator.
24    Generates AsciiDoc includes of the SPIR-V capabilities table for the
25    features chapter of the API specification.
26
27    ---- methods ----
28    SpirvCapabilityOutputGenerator(errFile, warnFile, diagFile) - args as for
29      OutputGenerator. Defines additional internal state.
30    ---- methods overriding base class ----
31    genCmd(cmdinfo)"""
32
33    def __init__(self, *args, **kwargs):
34        super().__init__(*args, **kwargs)
35
36    def beginFile(self, genOpts):
37        OutputGenerator.beginFile(self, genOpts)
38
39        # Accumulate SPIR-V capability and feature information
40        self.spirv = []
41
42    def getCondition(self, enable, parent):
43        """Return a strings which is the condition under which an
44           enable is supported.
45
46         - enable - ElementTree corresponding to an <enable> XML tag for a
47           SPIR-V capability or extension
48         - parent - Parent <spirvcapability> or <spirvenable> ElementTree,
49           used for error reporting"""
50
51        if enable.get('version'):
52            return enable.get('version')
53        elif enable.get('extension'):
54            return enable.get('extension')
55        elif enable.get('struct') or enable.get('property'):
56            return enable.get('requires')
57        else:
58            self.logMsg('error', f"<{parent.tag} name=\"{parent.get('name')}\"> is missing a required attribute for an <enable>")
59            return ''
60
61    def getConditions(self, enables):
62        """Return a sorted list of strings which are conditions under which
63           one or more of the enables is supported.
64
65         - enables - ElementTree corresponding to a <spirvcapability> or
66           <spirvextension> XML tag"""
67
68        conditions = set()
69        for enable in enables.findall('enable'):
70            condition = self.getCondition(enable, parent=enables)
71            if condition != None:
72                conditions.add(condition)
73        return sorted(conditions)
74
75    def endFile(self):
76        captable = []
77        exttable = []
78
79        # How to "indent" a pseudo-column for better use of space.
80        # {captableindent} is defined in appendices/spirvenv.adoc
81        indent = '{captableindent}'
82
83        for elem in self.spirv:
84            conditions = self.getConditions(elem)
85
86            # Combine all conditions for enables and surround the row with
87            # them
88            if len(conditions) > 0:
89                condition_string = ','.join(conditions)
90                prefix = [ 'ifdef::{}[]'.format(condition_string) ]
91                suffix = [ 'endif::{}[]'.format(condition_string) ]
92            else:
93                prefix = []
94                suffix = []
95
96            body = []
97
98            # Generate an anchor for each capability
99            if elem.tag == 'spirvcapability':
100                anchor = '[[spirvenv-capabilities-table-{}]]'.format(
101                    elem.get('name'))
102            else:
103                # <spirvextension> entries do not get anchors
104                anchor = ''
105
106            # First "cell" in a table row, and a break for the other "cells"
107            body.append('| {}code:{} +'.format(anchor, elem.get('name')))
108
109            # Iterate over each enable emitting a formatting tag for it
110            # Protect the term if there is a version or extension
111            # requirement, and if there are multiple enables (otherwise,
112            # the ifdef protecting the entire row will suffice).
113
114            enables = [e for e in elem.findall('enable')]
115
116            remaining = len(enables)
117            for subelem in enables:
118                remaining -= 1
119
120                # Sentinel value
121                linktext = None
122                if subelem.get('version'):
123                    version = subelem.get('version')
124
125                    # Convert API enum to anchor for version appendices (versions-m.n)
126                    # version must be the spec conditional macro VK_VERSION_m_n, not
127                    # the API version macro VK_API_VERSION_m_n.
128                    enable = version
129                    link = 'versions-' + version[-3:].replace('_', '.')
130                    altlink = version
131
132                    linktext = makeLink(link, altlink)
133                elif subelem.get('extension'):
134                    extension = subelem.get('extension')
135
136                    enable = extension
137                    link = extension
138                    altlink = None
139
140                    # This uses the extension name macro, rather than
141                    # asciidoc markup
142                    linktext = '`apiext:{}`'.format(extension)
143                elif subelem.get('struct'):
144                    struct = subelem.get('struct')
145                    feature = subelem.get('feature')
146                    requires = subelem.get('requires')
147                    alias = subelem.get('alias')
148
149                    link_name = feature
150                    # For cases, like bufferDeviceAddressEXT where need manual help
151                    if alias:
152                        link_name = alias
153                    exceptions = {
154                        'VkPhysicalDeviceCooperativeMatrixFeaturesNV::cooperativeMatrix': 'cooperativeMatrix-NV',
155                    }
156                    if struct + '::' + feature in exceptions:
157                        link_name = exceptions[struct + '::' + feature]
158
159                    enable = requires
160                    link = 'features-' + link_name
161                    altlink = 'sname:{}::pname:{}'.format(struct, feature)
162
163                    linktext = makeLink(link, altlink)
164                else:
165                    property = subelem.get('property')
166                    member = subelem.get('member')
167                    requires = subelem.get('requires')
168                    value = subelem.get('value')
169
170                    enable = requires
171                    # Properties should have a "feature" prefix
172                    link = 'limits-' + member
173                    # Display the property value by itself if it is not a boolean (matches original table)
174                    # DenormPreserve is an example where it makes sense to just show the
175                    #   member value as it is just a boolean and the name implies "true"
176                    # GroupNonUniformVote is an example where the whole name is too long
177                    #   better to just display the value
178                    if value == "VK_TRUE":
179                        altlink = 'sname:{}::pname:{}'.format(property, member)
180                    else:
181                        altlink = '{}'.format(value)
182
183                    linktext = makeLink(link, altlink)
184
185                # If there are no more enables, do not continue the last line
186                if remaining > 0:
187                    continuation = ' +'
188                else:
189                    continuation = ''
190
191                # condition_string != enable is a small optimization
192                if enable is not None and condition_string != enable:
193                    body.append('ifdef::{}[]'.format(enable))
194                body.append('{} {}{}'.format(indent, linktext, continuation))
195                if enable is not None and condition_string != enable:
196                    body.append('endif::{}[]'.format(enable))
197
198            if elem.tag == 'spirvcapability':
199                captable += prefix + body + suffix
200            else:
201                exttable += prefix + body + suffix
202
203        # Generate the asciidoc include files
204        self.writeBlock('captable.adoc', captable)
205        self.writeBlock('exttable.adoc', exttable)
206
207        # Finish processing in superclass
208        OutputGenerator.endFile(self)
209
210    def writeBlock(self, basename, contents):
211        """Generate an include file.
212
213        - directory - subdirectory to put file in
214        - basename - base name of the file
215        - contents - contents of the file (Asciidoc boilerplate aside)"""
216
217        filename = self.genOpts.directory + '/' + basename
218        self.logMsg('diag', '# Generating include file:', filename)
219        with open(filename, 'w', encoding='utf-8') as fp:
220            write(self.genOpts.conventions.warning_comment, file=fp)
221
222            if len(contents) > 0:
223                for str in contents:
224                    write(str, file=fp)
225            else:
226                self.logMsg('diag', '# No contents for:', filename)
227
228    def genSpirv(self, capinfo, name, alias):
229        """Generate SPIR-V capabilities
230
231        capinfo - dictionary entry for an XML <spirvcapability> or
232            <spirvextension> element
233        name - name attribute of capinfo.elem"""
234
235        OutputGenerator.genSpirv(self, capinfo, name, alias)
236
237        # Just accumulate each element, process in endFile
238        self.spirv.append(capinfo.elem)
239