• 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
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
154                    enable = requires
155                    link = 'features-' + link_name
156                    altlink = 'sname:{}::pname:{}'.format(struct, feature)
157
158                    linktext = makeLink(link, altlink)
159                else:
160                    property = subelem.get('property')
161                    member = subelem.get('member')
162                    requires = subelem.get('requires')
163                    value = subelem.get('value')
164
165                    enable = requires
166                    # Properties should have a "feature" prefix
167                    link = 'limits-' + member
168                    # Display the property value by itself if it is not a boolean (matches original table)
169                    # DenormPreserve is an example where it makes sense to just show the
170                    #   member value as it is just a boolean and the name implies "true"
171                    # GroupNonUniformVote is an example where the whole name is too long
172                    #   better to just display the value
173                    if value == "VK_TRUE":
174                        altlink = 'sname:{}::pname:{}'.format(property, member)
175                    else:
176                        altlink = '{}'.format(value)
177
178                    linktext = makeLink(link, altlink)
179
180                # If there are no more enables, do not continue the last line
181                if remaining > 0:
182                    continuation = ' +'
183                else:
184                    continuation = ''
185
186                # condition_string != enable is a small optimization
187                if enable is not None and condition_string != enable:
188                    body.append('ifdef::{}[]'.format(enable))
189                body.append('{} {}{}'.format(indent, linktext, continuation))
190                if enable is not None and condition_string != enable:
191                    body.append('endif::{}[]'.format(enable))
192
193            if elem.tag == 'spirvcapability':
194                captable += prefix + body + suffix
195            else:
196                exttable += prefix + body + suffix
197
198        # Generate the asciidoc include files
199        self.writeBlock('captable.adoc', captable)
200        self.writeBlock('exttable.adoc', exttable)
201
202        # Finish processing in superclass
203        OutputGenerator.endFile(self)
204
205    def writeBlock(self, basename, contents):
206        """Generate an include file.
207
208        - directory - subdirectory to put file in
209        - basename - base name of the file
210        - contents - contents of the file (Asciidoc boilerplate aside)"""
211
212        filename = self.genOpts.directory + '/' + basename
213        self.logMsg('diag', '# Generating include file:', filename)
214        with open(filename, 'w', encoding='utf-8') as fp:
215            write(self.genOpts.conventions.warning_comment, file=fp)
216
217            if len(contents) > 0:
218                for str in contents:
219                    write(str, file=fp)
220            else:
221                self.logMsg('diag', '# No contents for:', filename)
222
223    def genSpirv(self, capinfo, name, alias):
224        """Generate SPIR-V capabilities
225
226        capinfo - dictionary entry for an XML <spirvcapability> or
227            <spirvextension> element
228        name - name attribute of capinfo.elem"""
229
230        OutputGenerator.genSpirv(self, capinfo, name, alias)
231
232        # Just accumulate each element, process in endFile
233        self.spirv.append(capinfo.elem)
234