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