Source browser
scripts/compile_shader.py
Read the complete file below or download its original bytes. Line numbers provide stable links to a declaration or processing step.
File fingerprint
1#!/usr/bin/env python3
2"""Compile native GLSL compute sources into C++11 adapters and embedded GPU programs.
3
4The frontend translates native layout, buffer, uniform, main and swizzle syntax
5for the documented independent-invocation subset. Interface validation precedes
6C++ compilation. Applications execute the embedded CPU and GPU programs.
7"""
8import argparse # Read the build or test command options.
9import ast # Evaluate restricted metadata expressions through their syntax tree.
10import hashlib # Give generated names stable collision resistant suffixes.
11import operator # Apply only the arithmetic operations allowed in metadata.
12from pathlib import Path # Read shader inputs and write generated adapter paths.
13import re # Recognize native declarations while retaining their source offsets.
14
15
16class ShaderError(ValueError): # Group these parser operations or diagnostic values.
17 """A native shader declaration outside the supported portable contract."""
18
19
20def mask_noncode(source): # Mask comments and text while preserving source offsets.
21 """Preserve offsets/newlines while hiding comments and quoted literals."""
22 pattern = r'//[^\n]*|/\*[\s\S]*?\*/|"(?:\\.|[^"\\])*"|\'(?:\\.|[^\'\\])*\'' # Compute pattern for the following operation.
23 return re.sub(pattern, lambda m: re.sub(r'[^\n]', ' ', m.group()), source) # Return this computed value to the caller.
24
25
26def layout_values(text): # Parse the comma separated GLSL layout qualifiers.
27 result = {} # Compute result for the following operation.
28 for item in text.split(','): # Process each item in this sequence.
29 if not item.strip(): # Select this branch when its condition holds.
30 continue # Skip the remaining work for this iteration.
31 key, _, value = item.strip().partition('=') # Apply strip to these arguments.
32 key = key.strip() # Compute key for the following operation.
33 if key in result: # Select this branch when its condition holds.
34 raise ShaderError('duplicate layout qualifier: ' + key) # Report the shader declaration validation error.
35 result[key.strip()] = value.strip() # Apply strip to these arguments.
36 return result # Return this computed value to the caller.
37
38
39def integer(text, label, default=None, constants=None): # Evaluate the limited integer expressions accepted by resource metadata.
40 if text is None and default is not None: # Select this branch when its condition holds.
41 return default # Return this computed value to the caller.
42 constants = constants or {} # Compute constants for the following operation.
43 operations = { # Compute operations for the following operation.
44 ast.Add: operator.add, # Supply ast for the continued expression.
45 ast.Sub: operator.sub, # Supply ast for the continued expression.
46 ast.Mult: operator.mul, # Supply ast for the continued expression.
47 ast.Div: operator.floordiv, # Supply ast for the continued expression.
48 ast.FloorDiv: operator.floordiv, # Supply ast for the continued expression.
49 ast.Mod: operator.mod, # Supply ast for the continued expression.
50 ast.LShift: operator.lshift, # Supply ast for the continued expression.
51 ast.RShift: operator.rshift, # Supply ast for the continued expression.
52 ast.BitOr: operator.or_, # Supply ast for the continued expression.
53 ast.BitAnd: operator.and_, # Supply ast for the continued expression.
54 ast.BitXor: operator.xor, # Supply ast for the continued expression.
55 } # Finish this expression or collection.
56
57 def evaluate(expression, seen=()): # Resolve constants recursively through the restricted expression tree.
58 expression = re.sub(r'\b(0[xX][0-9a-fA-F]+|[0-9]+)[uU]\b', r'\1', expression) # Compute expression for the following operation.
59
60 def visit(node): # Evaluate one permitted expression node.
61 if isinstance(node, ast.Constant) and isinstance(node.value, int): # Select this branch when its condition holds.
62 return node.value # Return this computed value to the caller.
63 if ( # Select this branch when its condition holds.
64 isinstance(node, ast.Name) # Apply isinstance to these arguments.
65 and node.id in constants # Supply and for the continued expression.
66 and node.id not in seen # Supply and for the continued expression.
67 ): # Finish this expression or collection.
68 return evaluate(constants[node.id], seen + (node.id,)) # Return this computed value to the caller.
69 if isinstance(node, ast.BinOp) and type(node.op) in operations: # Select this branch when its condition holds.
70 left, right = visit(node.left), visit(node.right) # Apply visit to these arguments.
71 if ( # Select this branch when its condition holds.
72 isinstance(node.op, (ast.LShift, ast.RShift)) # Apply isinstance to these arguments.
73 and not 0 <= right < 32 # Supply and for the continued expression.
74 ): # Finish this expression or collection.
75 raise ValueError() # Report the shader declaration validation error.
76 return operations[type(node.op)](left, right) # Return this computed value to the caller.
77 if isinstance(node, ast.UnaryOp) and isinstance( # Select this branch when its condition holds.
78 node.op, (ast.UAdd, ast.USub, ast.Invert) # Supply node for the continued expression.
79 ): # Finish this expression or collection.
80 value = visit(node.operand) # Compute value for the following operation.
81 return ( # Return this computed value to the caller.
82 value # Supply value for the continued expression.
83 if isinstance(node.op, ast.UAdd) # Select this branch when its condition holds.
84 else -value if isinstance(node.op, ast.USub) else ~value # Apply isinstance to these arguments.
85 ) # Finish this expression or collection.
86 raise ValueError() # Report the shader declaration validation error.
87
88 return visit(ast.parse(expression, mode='eval').body) # Return this computed value to the caller.
89
90 try: # Attempt the operation with explicit error handling below.
91 value = evaluate(text or '') # Compute value for the following operation.
92 except (SyntaxError, ValueError, TypeError, ZeroDivisionError, RecursionError): # Convert this failure into the expected diagnostic.
93 raise ShaderError( # Report the shader declaration validation error.
94 label # Supply label for the continued expression.
95 + ' must be a literal nonnegative integer or an integer constant expression'
96 ) # Finish this expression or collection.
97 if value < 0: # Select this branch when its condition holds.
98 raise ShaderError(label + ' must be a nonnegative integer') # Report the shader declaration validation error.
99 if value > 0xFFFFFFFF: # Select this branch when its condition holds.
100 raise ShaderError(label + ' exceeds GLSL uint') # Report the shader declaration validation error.
101 return value # Return this computed value to the caller.
102
103
104STORAGE = {'float', 'int', 'uint', 'vec2', 'vec4', 'ivec2', 'ivec4', 'uvec2', 'uvec4'} # Compute STORAGE for the following operation.
105UNIFORMS = ( # Compute UNIFORMS for the following operation.
106 STORAGE # Supply STORAGE for the continued expression.
107 | {'bool', 'vec3', 'ivec3', 'uvec3', 'bvec2', 'bvec3', 'bvec4'} # Supply bool for the continued expression.
108 | { # Supply this value for the continued expression.
109 'mat2',
110 'mat3',
111 'mat4',
112 *(f'mat{c}x{r}' for c in range(2, 5) for r in range(2, 5)),
113 } # Finish this expression or collection.
114) # Finish this expression or collection.
115IMAGES = { # Compute IMAGES for the following operation.
116 'r32f': ('float', 1, 'image2D'), # Supply r32f for the continued expression.
117 'r32i': ('int', 1, 'iimage2D'), # Supply r32i for the continued expression.
118 'r32ui': ('unsigned', 1, 'uimage2D'), # Supply r32ui for the continued expression.
119 'rgba32f': ('float', 4, 'image2D'), # Supply rgba32f for the continued expression.
120 'rgba32i': ('int', 4, 'iimage2D'), # Supply rgba32i for the continued expression.
121 'rgba32ui': ('unsigned', 4, 'uimage2D'), # Supply rgba32ui for the continued expression.
122} # Finish this expression or collection.
123QUALIFIERS = ( # Compute QUALIFIERS for the following operation.
124 r'(?:(?:readonly|writeonly|coherent|volatile|restrict|highp|mediump|lowp)\s+)*'
125) # Finish this expression or collection.
126
127
128def access_mode(qualifiers): # Translate resource access qualifiers into the runtime access mode.
129 words = qualifiers.split() # Compute words for the following operation.
130 if 'readonly' in words and 'writeonly' in words: # Select this branch when its condition holds.
131 raise ShaderError('resource cannot be both readonly and writeonly') # Report the shader declaration validation error.
132 return ( # Return this computed value to the caller.
133 'readonly'
134 if 'readonly' in words # Select this branch when its condition holds.
135 else 'writeonly' if 'writeonly' in words else 'readwrite' # Supply else for the continued expression.
136 ) # Finish this expression or collection.
137
138
139def member_receiver(source, position): # Find the object receiving a member or swizzle operation.
140 """Return a bare or parenthesized identifier immediately before a member dot."""
141 prefix = source[:position].rstrip() # Compute prefix for the following operation.
142 parentheses = 0 # Compute parentheses for the following operation.
143 while prefix.endswith(')'): # Continue while the parser still has matching input.
144 parentheses += 1 # Compute parentheses for the following operation.
145 prefix = prefix[:-1].rstrip() # Compute prefix for the following operation.
146 identifier = re.search(r'\b([A-Za-z_]\w*)$', prefix) # Compute identifier for the following operation.
147 if not identifier: # Select this branch when its condition holds.
148 return None # Return this computed value to the caller.
149 before = prefix[: identifier.start()] # Compute before for the following operation.
150 for _ in range(parentheses): # Process each in this sequence.
151 before = before.rstrip() # Compute before for the following operation.
152 if not before.endswith('('): # Select this branch when its condition holds.
153 return None # Return this computed value to the caller.
154 before = before[:-1] # Compute before for the following operation.
155 # Keep this member access attached to its enclosing object.
156 if before.rstrip().endswith('.'): # Select this branch when its condition holds.
157 return None # Return this computed value to the caller.
158 return identifier.group(1) # Return this computed value to the caller.
159
160
161def primary_expression_start(code, end): # Locate the complete receiver of a native array length query.
162 """Find the postfix receiver within its surrounding expression boundaries."""
163 while end and code[end - 1].isspace(): # Ignore whitespace between a receiver and its member dot.
164 end -= 1 # Move toward the last token belonging to the receiver.
165 if not end: # A member query must have an expression before its dot.
166 raise ShaderError('array length query has no receiver') # Diagnose incomplete native syntax before emitting C++.
167 if code[end - 1] in ')]': # Receiver expressions can end in a call, grouping or array index.
168 closing = code[end - 1] # Remember which delimiter closes this receiver expression.
169 opening = '(' if closing == ')' else '[' # Select the matching opening delimiter.
170 depth, start = 1, end - 1 # Begin the balanced scan before the closing delimiter.
171 while start and depth: # Walk backward until this exact delimiter pair is balanced.
172 start -= 1 # Inspect the preceding character in the original masked source.
173 if code[start] == closing: # Count nested closing delimiters of the same kind.
174 depth += 1 # Keep the outer delimiter open until its own match is reached.
175 elif code[start] == opening: # Match one nested opening delimiter.
176 depth -= 1 # Remove this balanced level from the search.
177 if depth: # A missing opener would make the native expression incomplete.
178 raise ShaderError('unbalanced array length receiver') # Stop at the existing expression boundary.
179 prefix = code[:start].rstrip() # Inspect the expression immediately preceding the opening delimiter.
180 if closing == ']': # Indexing belongs to its complete array or vector receiver.
181 start = primary_expression_start(code, start) # Include earlier member access, calls and indices.
182 elif prefix: # Parentheses may group an expression or contain a function call's arguments.
183 identifier = re.search(r'\b([A-Za-z_]\w*)$', prefix) # Detect a callee while distinguishing statement keywords.
184 keywords = {'return', 'if', 'while', 'for', 'switch', 'case', 'else', 'do'} # Require a postfix callable expression at this position.
185 if identifier and identifier.group(1) not in keywords: # Include a constructor or function name before its call.
186 start = primary_expression_start(code, start) # Preserve the complete call as the length receiver.
187 elif prefix[-1] in ')]': # A postfix call can follow an indexed or parenthesized receiver.
188 start = primary_expression_start(code, start) # Include that earlier postfix expression as well.
189 else: # A simple receiver ends with an identifier.
190 identifier = re.search(r'\b[A-Za-z_]\w*$', code[:end]) # Select the identifier ending at this member boundary.
191 if not identifier: # Require an array-valued receiver for the length query.
192 raise ShaderError('unsupported array length receiver') # Explain the receiver boundary validation error.
193 start = identifier.start() # Keep the original source offset of this receiver token.
194 prefix = code[:start].rstrip() # Check whether this token belongs to a longer member chain.
195 if prefix.endswith('.'): # Named storage-block fields are part of their block receiver.
196 start = primary_expression_start(code, len(prefix) - 1) # Include that preceding block or nested member expression.
197 return start # Return the first character of the complete postfix receiver.
198
199
200def statement_end(source, position): # Locate the complete statement including nested control flow.
201 """Locate a structured statement's end for lexical for-initializer scope."""
202 while position < len(source) and source[position].isspace(): # Continue while the parser still has matching input.
203 position += 1 # Compute position for the following operation.
204
205 def close(opening, left, right): # Find the matching end of this bracketed expression.
206 depth = 0 # Compute depth for the following operation.
207 for i in range(opening, len(source)): # Process each i in this sequence.
208 if source[i] == left: # Select this branch when its condition holds.
209 depth += 1 # Compute depth for the following operation.
210 elif source[i] == right: # Try this alternative condition after earlier branches fail.
211 depth -= 1 # Supply depth for the continued expression.
212 if not depth: # Select this branch when its condition holds.
213 return i + 1 # Return this computed value to the caller.
214 raise ShaderError('unterminated shader statement') # Report the shader declaration validation error.
215
216 if position < len(source) and source[position] == '{': # Select this branch when its condition holds.
217 return close(position, '{', '}') # Return this computed value to the caller.
218 control = re.match(r'(for|while|if|switch)\s*\(', source[position:]) # Compute control for the following operation.
219 if control: # Select this branch when its condition holds.
220 opening = position + control.end() - 1 # Compute opening for the following operation.
221 end = statement_end(source, close(opening, '(', ')')) # Compute end for the following operation.
222 tail = source[end:] # Compute tail for the following operation.
223 alternate = re.match(r'\s*else\b', tail) if control.group(1) == 'if' else None # Compute alternate for the following operation.
224 return statement_end(source, end + alternate.end()) if alternate else end # Return this computed value to the caller.
225 if re.match(r'do\b', source[position:]): # Select this branch when its condition holds.
226 end = statement_end(source, position + 2) # Compute end for the following operation.
227 tail = re.match(r'\s*while\s*\(', source[end:]) # Compute tail for the following operation.
228 if not tail: # Select this branch when its condition holds.
229 raise ShaderError('malformed do/while statement') # Report the shader declaration validation error.
230 end = close(end + tail.end() - 1, '(', ')') # Compute end for the following operation.
231 while end < len(source) and source[end].isspace(): # Continue while the parser still has matching input.
232 end += 1 # Compute end for the following operation.
233 return end + 1 # Return this computed value to the caller.
234 parentheses = brackets = 0 # Compute parentheses for the following operation.
235 for i in range(position, len(source)): # Process each i in this sequence.
236 token = source[i] # Compute token for the following operation.
237 if token == '(': # Select this branch when its condition holds.
238 parentheses += 1 # Compute parentheses for the following operation.
239 elif token == ')': # Try this alternative condition after earlier branches fail.
240 parentheses -= 1 # Supply parentheses for the continued expression.
241 elif token == '[': # Try this alternative condition after earlier branches fail.
242 brackets += 1 # Compute brackets for the following operation.
243 elif token == ']': # Try this alternative condition after earlier branches fail.
244 brackets -= 1 # Supply brackets for the continued expression.
245 elif token == ';' and not parentheses and not brackets: # Try this alternative condition after earlier branches fail.
246 return i + 1 # Return this computed value to the caller.
247 raise ShaderError('unterminated shader statement') # Report the shader declaration validation error.
248
249
250def scoped_integer_constants(source, definitions, position=None): # Resolve dimension aliases within their lexical statement scope.
251 """Resolve integer declarations in their lexical scope.
252
253 A top-level query returns constants for interface metadata. A position query
254 uses the enclosing scopes and respects local variable shadows.
255 """
256 scopes = [dict(definitions)] # Compute scopes for the following operation.
257 declarations = r'\b(const\s+)?(?:uint|int)\s+(\w+)\s*(?:=\s*([^;{}]+))?;' # Compute declarations for the following operation.
258 tokens = re.compile(r'\bfor\s*\(|[{}]|' + declarations) # Compute tokens for the following operation.
259 loops = [] # Compute loops for the following operation.
260 for token in tokens.finditer(source if position is None else source[:position]): # Process each token in this sequence.
261 while loops and loops[-1] <= token.start(): # Continue while the parser still has matching input.
262 loops.pop() # Apply pop to these arguments.
263 scopes.pop() # Apply pop to these arguments.
264 if re.match(r'for\b', token.group()): # Select this branch when its condition holds.
265 loops.append(statement_end(source, token.start())) # Apply append to these arguments.
266 scopes.append({}) # Apply append to these arguments.
267 elif token.group() == '{': # Try this alternative condition after earlier branches fail.
268 scopes.append({}) # Apply append to these arguments.
269 elif token.group() == '}': # Try this alternative condition after earlier branches fail.
270 if len(scopes) > 1: # Select this branch when its condition holds.
271 scopes.pop() # Apply pop to these arguments.
272 elif position is not None or len(scopes) == 1: # Try this alternative condition after earlier branches fail.
273 qualifier, name, expression = token.groups() # Apply groups to these arguments.
274 scopes[-1][name] = expression.strip() if qualifier and expression else None # Apply strip to these arguments.
275 boundary = len(source) if position is None else position # Compute boundary for the following operation.
276 while loops and loops[-1] <= boundary: # Continue while the parser still has matching input.
277 loops.pop() # Apply pop to these arguments.
278 scopes.pop() # Apply pop to these arguments.
279 result = {} # Compute result for the following operation.
280 for scope in scopes: # Process each scope in this sequence.
281 result.update(scope) # Apply update to these arguments.
282 return result # Return this computed value to the caller.
283
284
285def separate_array_declarators(source, types): # Give each array declarator its own C++ wrapper declaration.
286 """Give comma-separated private arrays their own C++ wrapper type declarations."""
287 code = mask_noncode(source) # Compute code for the following operation.
288 edits = [] # Compute edits for the following operation.
289 pattern = re.compile(r'\b(' + types + r')\s+(\w+)\s*(?=[\[=,;])') # Compute pattern for the following operation.
290 for match in pattern.finditer(code): # Process each match in this sequence.
291 begin = match.start(2) # Compute begin for the following operation.
292 parentheses = brackets = 0 # Compute parentheses for the following operation.
293 cuts = [begin] # Compute cuts for the following operation.
294 end = None # Compute end for the following operation.
295 for position in range(begin, len(code)): # Process each position in this sequence.
296 token = code[position] # Compute token for the following operation.
297 if token == '{' or token == '}': # Select this branch when its condition holds.
298 break # Finish this scan at the current boundary.
299 if token == '(': # Select this branch when its condition holds.
300 parentheses += 1 # Compute parentheses for the following operation.
301 elif token == ')': # Try this alternative condition after earlier branches fail.
302 if not parentheses: # Select this branch when its condition holds.
303 break # Finish this scan at the current boundary.
304 parentheses -= 1 # Supply parentheses for the continued expression.
305 elif token == '[': # Try this alternative condition after earlier branches fail.
306 brackets += 1 # Compute brackets for the following operation.
307 elif token == ']': # Try this alternative condition after earlier branches fail.
308 brackets -= 1 # Supply brackets for the continued expression.
309 elif not parentheses and not brackets: # Try this alternative condition after earlier branches fail.
310 if token == ',': # Select this branch when its condition holds.
311 cuts.append(position + 1) # Apply append to these arguments.
312 elif token == ';': # Try this alternative condition after earlier branches fail.
313 end = position # Compute end for the following operation.
314 break # Finish this scan at the current boundary.
315 if end is None or len(cuts) < 2: # Select this branch when its condition holds.
316 continue # Skip the remaining work for this iteration.
317 stops = [p - 1 for p in cuts[1:]] + [end] # Compute stops for the following operation.
318 declarators = [source[a:b].strip() for a, b in zip(cuts, stops)] # Compute declarators for the following operation.
319 if not any(re.match(r'\w+\s*\[', d) for d in declarators): # Select this branch when its condition holds.
320 continue # Skip the remaining work for this iteration.
321 start = match.start() # Compute start for the following operation.
322 qualifier = re.search(r'\bconst\s*$', code[:start]) # Compute qualifier for the following operation.
323 prefix = '' # Compute prefix for the following operation.
324 if qualifier: # Select this branch when its condition holds.
325 start = qualifier.start() # Compute start for the following operation.
326 prefix = 'const ' # Compute prefix for the following operation.
327 edits.append( # Apply append to these arguments.
328 ( # Finish this expression or collection.
329 start, # Supply start for the continued expression.
330 end + 1, # Supply end for the continued expression.
331 ' '.join(prefix + match.group(1) + ' ' + d + ';' for d in declarators), # Apply join to these arguments.
332 ) # Finish this expression or collection.
333 ) # Finish this expression or collection.
334 for start, end, replacement in reversed(edits): # Process each start, end, replacement in this sequence.
335 source = source[:start] + replacement + source[end:] # Compute source for the following operation.
336 return source # Return this computed value to the caller.
337
338
339def portable_gpu_declarations(source): # Preserve desktop initialization semantics under GLES declaration rules.
340 """Keep declaration values while making desktop initialization valid in GLES."""
341 code = mask_noncode(source) # Compute code for the following operation.
342 types = '|'.join(sorted(UNIFORMS, key=len, reverse=True)) # Compute types for the following operation.
343 function = r'(?P<function>\b(?:void|' + types + r')\s+\w+\s*\([^;{}]*\)\s*\{)' # Compute function for the following operation.
344 declaration = ( # Compute declaration for the following operation.
345 r'(?P<declaration>\b(?P<qualifiers>(?:(?:const|highp|mediump|lowp)\s+)*)(?P<type>'
346 + types # Supply types for the continued expression.
347 + r')\s+(?=[A-Za-z_]\w*\s*(?:\[|=|,|;)))'
348 ) # Finish this expression or collection.
349 events = re.compile( # Compute events for the following operation.
350 function + r'|(?P<loop>\bfor\s*\()|(?P<brace>[{}])|' + declaration # Supply function for the continued expression.
351 ) # Finish this expression or collection.
352 definitions = { # Compute definitions for the following operation.
353 m.group(1): m.group(2).strip() # Apply group to these arguments.
354 for m in re.finditer(r'(?m)^[ \t]*#\s*define\s+(\w+)\s+([^\n]+)', code) # Process each m in this sequence.
355 } # Finish this expression or collection.
356 scopes = [dict(definitions, gl_WorkGroupSize=True)] # Compute scopes for the following operation.
357 loops, edits = [], [] # Supply loops for the continued expression.
358 # Only these GLSL builtins produce constant expressions from constant operands.
359 pure_calls = UNIFORMS | { # Compute pure calls for the following operation.
360 'radians',
361 'degrees',
362 'sin',
363 'cos',
364 'tan',
365 'asin',
366 'acos',
367 'atan',
368 'sinh',
369 'cosh',
370 'tanh',
371 'asinh',
372 'acosh',
373 'atanh',
374 'pow',
375 'exp',
376 'log',
377 'exp2',
378 'log2',
379 'sqrt',
380 'inversesqrt',
381 'abs',
382 'sign',
383 'floor',
384 'trunc',
385 'round',
386 'roundEven',
387 'ceil',
388 'fract',
389 'mod',
390 'min',
391 'max',
392 'clamp',
393 'mix',
394 'step',
395 'smoothstep',
396 'fma',
397 'ldexp',
398 'length',
399 'distance',
400 'dot',
401 'cross',
402 'normalize',
403 'faceforward',
404 'reflect',
405 'refract',
406 'matrixCompMult',
407 'outerProduct',
408 'transpose',
409 'determinant',
410 'inverse',
411 'lessThan',
412 'lessThanEqual',
413 'greaterThan',
414 'greaterThanEqual',
415 'equal',
416 'notEqual',
417 'any',
418 'all',
419 'not',
420 } # Finish this expression or collection.
421
422 def constant(expression, environment, seen=()): # Distinguish constant expressions from runtime local values.
423 for token in re.finditer(r'\b[A-Za-z_]\w*\b', expression): # Process each token in this sequence.
424 name = token.group() # Compute name for the following operation.
425 if expression[: token.start()].rstrip().endswith('.'): # Select this branch when its condition holds.
426 continue # Skip the remaining work for this iteration.
427 if name in pure_calls or name in {'true', 'false'}: # Select this branch when its condition holds.
428 continue # Skip the remaining work for this iteration.
429 value = environment.get(name) # Compute value for the following operation.
430 if value is True: # Select this branch when its condition holds.
431 continue # Skip the remaining work for this iteration.
432 if ( # Select this branch when its condition holds.
433 not value # Require a resolved constant value for this expression.
434 or name in seen # Supply or for the continued expression.
435 or not constant(value, environment, seen + (name,)) # Apply constant to these arguments.
436 ): # Finish this expression or collection.
437 return False # Return this computed value to the caller.
438 return True # Return this computed value to the caller.
439
440 position = 0 # Compute position for the following operation.
441 while True: # Continue while the parser still has matching input.
442 event = events.search(code, position) # Compute event for the following operation.
443 if not event: # Select this branch when its condition holds.
444 break # Finish this scan at the current boundary.
445 while loops and loops[-1] <= event.start(): # Continue while the parser still has matching input.
446 loops.pop() # Apply pop to these arguments.
447 scopes.pop() # Apply pop to these arguments.
448 position = event.end() # Compute position for the following operation.
449 line_start = code.rfind('\n', 0, event.start()) + 1 # Compute line start for the following operation.
450 if code[line_start : event.start()].lstrip().startswith('#'): # Select this branch when its condition holds.
451 position = code.find('\n', event.start()) # Compute position for the following operation.
452 if position < 0: # Select this branch when its condition holds.
453 break # Finish this scan at the current boundary.
454 continue # Skip the remaining work for this iteration.
455 if event.group('function'): # Select this branch when its condition holds.
456 signature = event.group() # Compute signature for the following operation.
457 arguments = signature[signature.index('(') + 1 : signature.rfind(')')] # Compute arguments for the following operation.
458 parameters = re.findall(r'\b(?:' + types + r')\s+(\w+)', arguments) # Compute parameters for the following operation.
459 scopes.append({name: None for name in parameters}) # Apply append to these arguments.
460 continue # Skip the remaining work for this iteration.
461 if event.group('loop'): # Select this branch when its condition holds.
462 loops.append(statement_end(code, event.start())) # Apply append to these arguments.
463 scopes.append({}) # Apply append to these arguments.
464 continue # Skip the remaining work for this iteration.
465 if event.group('brace'): # Select this branch when its condition holds.
466 if event.group() == '{': # Select this branch when its condition holds.
467 scopes.append({}) # Apply append to these arguments.
468 elif len(scopes) > 1: # Try this alternative condition after earlier branches fail.
469 scopes.pop() # Apply pop to these arguments.
470 continue # Skip the remaining work for this iteration.
471 end = statement_end(code, event.start()) - 1 # Compute end for the following operation.
472 # Split commas only outside constructors and array dimensions.
473 boundaries, depth, start = [], 0, event.end() # Apply end to these arguments.
474 for i in range(start, end): # Process each i in this sequence.
475 if code[i] in '([': # Select this branch when its condition holds.
476 depth += 1 # Compute depth for the following operation.
477 elif code[i] in ')]': # Try this alternative condition after earlier branches fail.
478 depth -= 1 # Supply depth for the continued expression.
479 elif code[i] == ',' and depth == 0: # Try this alternative condition after earlier branches fail.
480 boundaries.append((start, i)) # Apply append to these arguments.
481 start = i + 1 # Compute start for the following operation.
482 boundaries.append((start, end)) # Apply append to these arguments.
483 environment = {} # Compute environment for the following operation.
484 for scope in scopes: # Process each scope in this sequence.
485 environment.update(scope) # Apply update to these arguments.
486 parts = [] # Compute parts for the following operation.
487 is_const = 'const' in event.group('qualifiers').split() # Compute is const for the following operation.
488 local = len(scopes) > 1 # Compute local for the following operation.
489 for start, stop in boundaries: # Process each start, stop in this sequence.
490 item = re.match( # Compute item for the following operation.
491 r'\s*(\w+)\s*((?:\[[^]]*\]\s*)*)(?:=\s*([\s\S]*))?$', code[start:stop] # Supply r for the continued expression.
492 ) # Finish this expression or collection.
493 if not item: # Select this branch when its condition holds.
494 break # Finish this scan at the current boundary.
495 name, array, expression = item.groups() # Apply groups to these arguments.
496 known = bool( # Compute known for the following operation.
497 is_const # Supply is const for the continued expression.
498 and expression is not None # Supply and for the continued expression.
499 and constant(expression, environment) # Apply constant to these arguments.
500 ) # Finish this expression or collection.
501 environment[name] = expression if known else None # Supply environment for the continued expression.
502 scopes[-1][name] = environment[name] # Supply scopes for the continued expression.
503 parts.append((start, stop, item, array, expression, known)) # Apply append to these arguments.
504 else: # Handle the remaining case.
505 remove_const = is_const and local and any(not part[-1] for part in parts) # Compute remove const for the following operation.
506 # One const qualifier applies to the complete for-initializer declaration.
507 # Preserve constant dimension aliases when other declarators are runtime values.
508 for_initializer = bool( # Compute for initializer for the following operation.
509 loops # Supply loops for the continued expression.
510 and code[code.rfind('for', 0, event.start()) : event.start()].find(';') # Apply rfind to these arguments.
511 < 0 # Supply this value for the continued expression.
512 ) # Finish this expression or collection.
513 mixed = remove_const and any(part[-1] for part in parts) # Compute mixed for the following operation.
514 if mixed and for_initializer: # Select this branch when its condition holds.
515 raise ShaderError( # Report the shader declaration validation error.
516 'mixed constant/runtime for initializers require separate declarations'
517 ) # Finish this expression or collection.
518 if mixed: # Select this branch when its condition holds.
519 declarations = [] # Compute declarations for the following operation.
520 for start, stop, item, array, expression, known in parts: # Process each start, stop, item, array, expression, known in this sequence.
521 prefix = event.group('qualifiers') # Compute prefix for the following operation.
522 if not known: # Select this branch when its condition holds.
523 prefix = re.sub(r'\bconst\s*', '', prefix) # Compute prefix for the following operation.
524 body = source[start:stop].strip() # Compute body for the following operation.
525 if ( # Select this branch when its condition holds.
526 expression is not None # Supply expression for the continued expression.
527 and not array # Supply and for the continued expression.
528 and event.group('type') in UNIFORMS # Apply group to these arguments.
529 ): # Finish this expression or collection.
530 offset = item.start(3) # Compute offset for the following operation.
531 body = ( # Compute body for the following operation.
532 source[start : start + offset].strip() # Apply strip to these arguments.
533 + event.group('type') # Apply group to these arguments.
534 + '('
535 + source[start + offset : stop].strip() # Apply strip to these arguments.
536 + ')'
537 ) # Finish this expression or collection.
538 declarations.append(prefix + event.group('type') + ' ' + body + ';') # Apply append to these arguments.
539 edits.append((event.start(), end + 1, '\n'.join(declarations))) # Apply append to these arguments.
540 else: # Handle the remaining case.
541 if remove_const: # Select this branch when its condition holds.
542 qualifier = re.search(r'\bconst\b', event.group('qualifiers')) # Compute qualifier for the following operation.
543 edits.append( # Apply append to these arguments.
544 ( # Finish this expression or collection.
545 event.start() + qualifier.start(), # Apply start to these arguments.
546 event.start() + qualifier.end(), # Apply start to these arguments.
547 '',
548 ) # Finish this expression or collection.
549 ) # Finish this expression or collection.
550 for start, stop, item, array, expression, known in parts: # Process each start, stop, item, array, expression, known in this sequence.
551 if ( # Select this branch when its condition holds.
552 expression is not None # Supply expression for the continued expression.
553 and not array # Supply and for the continued expression.
554 and event.group('type') in UNIFORMS # Apply group to these arguments.
555 ): # Finish this expression or collection.
556 first = start + item.start(3) # Compute first for the following operation.
557 edits.append( # Apply append to these arguments.
558 ( # Finish this expression or collection.
559 first, # Supply first for the continued expression.
560 stop, # Supply stop for the continued expression.
561 event.group('type') + '(' + source[first:stop] + ')', # Apply group to these arguments.
562 ) # Finish this expression or collection.
563 ) # Finish this expression or collection.
564 position = end + 1 # Compute position for the following operation.
565 continue # Skip the remaining work for this iteration.
566 position = end + 1 # Compute position for the following operation.
567 for start, end, replacement in sorted(edits, reverse=True): # Process each start, end, replacement in this sequence.
568 source = source[:start] + replacement + source[end:] # Compute source for the following operation.
569 return source # Return this computed value to the caller.
570
571
572def portable_gpu_promotions(source): # Make the desktop numeric promotions explicit for GLES.
573 """Spell out desktop numeric promotions for the stricter GLES type rules."""
574 code = mask_noncode(source) # Compute code for the following operation.
575 code = re.sub(r'(?m)^[ \t]*#[^\n]*', lambda m: ' ' * len(m.group()), code) # Compute code for the following operation.
576 kinds = '|'.join(sorted(UNIFORMS, key=len, reverse=True)) # Compute kinds for the following operation.
577 numeric = re.compile(r'^(float|int|uint|bool|[iub]?vec[234]|mat[234](?:x[234])?)$') # Compute numeric for the following operation.
578 symbols = {} # Compute symbols for the following operation.
579 fields = {} # Compute fields for the following operation.
580 functions = {} # Compute functions for the following operation.
581
582 def remember(name, kind): # Record every declared type for this identifier.
583 symbols.setdefault(name, set()).add(kind) # Apply setdefault to these arguments.
584
585 for declaration in re.finditer( # Process each declaration in this sequence.
586 r'\b(' + kinds + r')\s+([A-Za-z_]\w*)\s*(\[)?', code # Apply b to these arguments.
587 ): # Finish this expression or collection.
588 remember( # Apply remember to these arguments.
589 declaration.group(2), # Apply group to these arguments.
590 declaration.group(1) + ('[]' if declaration.group(3) else ''), # Apply group to these arguments.
591 ) # Finish this expression or collection.
592 for structure in re.finditer(r'\bstruct\s+(\w+)\s*\{([^{}]*)\}', code): # Process each structure in this sequence.
593 fields[structure.group(1)] = { # Apply group to these arguments.
594 name: kind # Supply name for the continued expression.
595 for kind, name in re.findall( # Process each kind, name in this sequence.
596 r'\b(' + kinds + r')\s+(\w+)\s*;', structure.group(2) # Apply b to these arguments.
597 ) # Finish this expression or collection.
598 } # Finish this expression or collection.
599 for instance in re.finditer( # Process each instance in this sequence.
600 r'\b' + structure.group(1) + r'\s+(\w+)\s*[;=]', code # Apply group to these arguments.
601 ): # Finish this expression or collection.
602 remember(instance.group(1), structure.group(1)) # Apply remember to these arguments.
603 signature = re.compile( # Compute signature for the following operation.
604 r'\b(void|' + kinds + r')\s+(\w+)\s*\(([^;{}]*)\)\s*(?=[{;])' # Apply b to these arguments.
605 ) # Finish this expression or collection.
606 for match in signature.finditer(code): # Process each match in this sequence.
607 parameters = [] # Compute parameters for the following operation.
608 for parameter in match.group(3).split(','): # Process each parameter in this sequence.
609 value = re.search(r'\b(' + kinds + r')\s+(\w+)\s*(\[)?', parameter) # Compute value for the following operation.
610 if value: # Select this branch when its condition holds.
611 parameters.append( # Apply append to these arguments.
612 (value.group(1), bool(re.search(r'\b(?:out|inout)\b', parameter))) # Apply group to these arguments.
613 ) # Finish this expression or collection.
614 functions.setdefault(match.group(2), set()).add( # Apply setdefault to these arguments.
615 (match.group(1), tuple(parameters)) # Apply group to these arguments.
616 ) # Finish this expression or collection.
617 symbols.update( # Apply update to these arguments.
618 { # Finish this expression or collection.
619 name: {kind} # Supply name for the continued expression.
620 for name, kind in { # Process each name, kind in this sequence.
621 'gl_GlobalInvocationID': 'uvec3', # Supply gl GlobalInvocationID for the continued expression.
622 'gl_LocalInvocationID': 'uvec3', # Supply gl LocalInvocationID for the continued expression.
623 'gl_WorkGroupID': 'uvec3', # Supply gl WorkGroupID for the continued expression.
624 'gl_NumWorkGroups': 'uvec3', # Supply gl NumWorkGroups for the continued expression.
625 'gl_WorkGroupSize': 'uvec3', # Supply gl WorkGroupSize for the continued expression.
626 'gl_LocalInvocationIndex': 'uint', # Supply gl LocalInvocationIndex for the continued expression.
627 }.items() # Apply items to these arguments.
628 } # Finish this expression or collection.
629 ) # Finish this expression or collection.
630 token_pattern = r'(?:0[xX][\da-fA-F]+[uU]?|(?:\d+\.\d*|\.\d+|\d+)(?:[eE][+-]?\d+)?[fFuU]?)|[A-Za-z_]\w*|(?:<<=|>>=|\+\+|--|==|!=|<=|>=|&&|\|\||<<|>>|\+=|-=|\*=|/=|%=|&=|\|=|\^=)|[^\s]' # Compute token pattern for the following operation.
631 tokens = [(m.group(), m.start(), m.end()) for m in re.finditer(token_pattern, code)] # Compute tokens for the following operation.
632 priorities = { # Compute priorities for the following operation.
633 '=': 1, # Supply this value for the continued expression.
634 '+=': 1, # Supply this value for the continued expression.
635 '-=': 1, # Supply this value for the continued expression.
636 '*=': 1, # Supply this value for the continued expression.
637 '/=': 1, # Supply this value for the continued expression.
638 '%=': 1, # Supply this value for the continued expression.
639 '&=': 1, # Supply this value for the continued expression.
640 '|=': 1, # Supply this value for the continued expression.
641 '^=': 1, # Supply this value for the continued expression.
642 '<<=': 1, # Supply this value for the continued expression.
643 '>>=': 1, # Supply this value for the continued expression.
644 '?': 2, # Supply this value for the continued expression.
645 '||': 3, # Supply this value for the continued expression.
646 '&&': 4, # Supply this value for the continued expression.
647 '|': 5, # Supply this value for the continued expression.
648 '^': 6, # Supply this value for the continued expression.
649 '&': 7, # Supply this value for the continued expression.
650 '==': 8, # Supply this value for the continued expression.
651 '!=': 8, # Supply this value for the continued expression.
652 '<': 9, # Supply this value for the continued expression.
653 '>': 9, # Supply this value for the continued expression.
654 '<=': 9, # Supply this value for the continued expression.
655 '>=': 9, # Supply this value for the continued expression.
656 '<<': 10, # Supply this value for the continued expression.
657 '>>': 10, # Supply this value for the continued expression.
658 '+': 11, # Supply this value for the continued expression.
659 '-': 11, # Supply this value for the continued expression.
660 '*': 12, # Supply this value for the continued expression.
661 '/': 12, # Supply this value for the continued expression.
662 '%': 12, # Supply this value for the continued expression.
663 } # Finish this expression or collection.
664 conversions = set() # Compute conversions for the following operation.
665
666 def scalar(kind): # Find the component type of a numeric scalar or vector.
667 if not kind or not numeric.fullmatch(kind): # Select this branch when its condition holds.
668 return None # Return this computed value to the caller.
669 if kind.startswith('mat') or kind.startswith('vec'): # Select this branch when its condition holds.
670 return 'float' # Return this computed value to the caller.
671 if kind.startswith('ivec'): # Select this branch when its condition holds.
672 return 'int' # Return this computed value to the caller.
673 if kind.startswith('uvec'): # Select this branch when its condition holds.
674 return 'uint' # Return this computed value to the caller.
675 if kind.startswith('bvec'): # Select this branch when its condition holds.
676 return 'bool' # Return this computed value to the caller.
677 return kind # Return this computed value to the caller.
678
679 def shape(kind): # Keep the numeric dimensions separate from the component type.
680 if not kind: # Select this branch when its condition holds.
681 return None # Return this computed value to the caller.
682 return ( # Return this computed value to the caller.
683 kind[kind.index('vec') :] # Apply index to these arguments.
684 if 'vec' in kind # Select this branch when its condition holds.
685 else kind if kind.startswith('mat') else '' # Apply startswith to these arguments.
686 ) # Finish this expression or collection.
687
688 def replace_scalar(kind, base): # Retain the vector shape while changing its component type.
689 dimensions = shape(kind) # Compute dimensions for the following operation.
690 if dimensions and dimensions.startswith('vec'): # Select this branch when its condition holds.
691 return ( # Return this computed value to the caller.
692 '' if base == 'float' else 'i' if base == 'int' else 'u' # Supply if for the continued expression.
693 ) + dimensions # Supply dimensions for the continued expression.
694 return base if dimensions == '' else kind # Return this computed value to the caller.
695
696 def promote(left, right): # Find the common desktop type for these two numeric operands.
697 a, b = scalar(left), scalar(right) # Apply scalar to these arguments.
698 if a not in {'float', 'int', 'uint'} or b not in {'float', 'int', 'uint'}: # Select this branch when its condition holds.
699 return None, None, None # Return this computed value to the caller.
700 if shape(left) and shape(right) and shape(left) != shape(right): # Select this branch when its condition holds.
701 return None, None, None # Return this computed value to the caller.
702 base = 'float' if 'float' in {a, b} else 'uint' if 'uint' in {a, b} else 'int' # Compute base for the following operation.
703 ltype, rtype = replace_scalar(left, base), replace_scalar(right, base) # Apply replace scalar to these arguments.
704 return ltype, rtype, ltype if shape(left) else rtype # Return this computed value to the caller.
705
706 def cast(node, kind): # Request a conversion only when the known component types differ.
707 if ( # Select this branch when its condition holds.
708 node # Supply node for the continued expression.
709 and kind # Supply and for the continued expression.
710 and node[2] # Supply and for the continued expression.
711 and node[2] != kind # Supply and for the continued expression.
712 and scalar(node[2]) in {'float', 'int', 'uint'} # Apply scalar to these arguments.
713 and scalar(kind) in {'float', 'int', 'uint'} # Apply scalar to these arguments.
714 ): # Finish this expression or collection.
715 if shape(node[2]) == shape(kind): # Select this branch when its condition holds.
716 conversions.add((node[0], node[1], kind)) # Apply add to these arguments.
717
718 def symbol(name): # Use an identifier type only when its declarations agree.
719 candidates = symbols.get(name, set()) # Compute candidates for the following operation.
720 return next(iter(candidates)) if len(candidates) == 1 else None # Return this computed value to the caller.
721
722 class Expression: # Group these parser operations or diagnostic values.
723 def __init__(self, position): # Start parsing at this token with a bounded recursion depth.
724 self.position = position # Compute self.position for the following operation.
725 self.depth = 0 # Compute self.depth for the following operation.
726
727 def value(self): # Inspect the next token at the current parser position.
728 return tokens[self.position][0] if self.position < len(tokens) else '' # Return this computed value to the caller.
729
730 def parse(self, minimum=1): # Apply GLSL operator precedence while recording explicit promotions.
731 self.depth += 1 # Compute self.depth for the following operation.
732 if self.depth > 128: # Select this branch when its condition holds.
733 self.depth -= 1 # Supply self for the continued expression.
734 return None # Return this computed value to the caller.
735 node = self.atom() # Compute node for the following operation.
736 while node: # Continue while the parser still has matching input.
737 operation = self.value() # Compute operation for the following operation.
738 priority = priorities.get(operation, 0) # Compute priority for the following operation.
739 if priority < minimum: # Select this branch when its condition holds.
740 break # Finish this scan at the current boundary.
741 self.position += 1 # Compute self.position for the following operation.
742 if operation == '?': # Select this branch when its condition holds.
743 yes = self.parse() # Compute yes for the following operation.
744 if self.value() != ':': # Select this branch when its condition holds.
745 break # Finish this scan at the current boundary.
746 self.position += 1 # Compute self.position for the following operation.
747 no = self.parse(priority) # Parse the conditional expression's false branch.
748 if not yes or not no: # Select this branch when its condition holds.
749 break # Finish this scan at the current boundary.
750 a, b, result = promote(yes[2], no[2]) # Apply promote to these arguments.
751 cast(yes, a) # Apply cast to these arguments.
752 cast(no, b) # Apply cast to these arguments.
753 node = ( # Compute node for the following operation.
754 node[0], # Supply node for the continued expression.
755 no[1], # Use the false branch's ending source position.
756 result or (yes[2] if yes[2] == no[2] else None), # Apply or to these arguments.
757 ) # Finish this expression or collection.
758 continue # Skip the remaining work for this iteration.
759 right = self.parse(priority if priority == 1 else priority + 1) # Compute right for the following operation.
760 if not right: # Select this branch when its condition holds.
761 break # Finish this scan at the current boundary.
762 if operation == '=': # Select this branch when its condition holds.
763 cast(right, node[2]) # Apply cast to these arguments.
764 result = node[2] # Compute result for the following operation.
765 elif operation.endswith('=') and priority == 1: # Try this alternative condition after earlier branches fail.
766 # Compound assignment already converts its result to the left-hand type.
767 _, b, _ = promote(node[2], right[2]) # Apply promote to these arguments.
768 cast(right, b) # Apply cast to these arguments.
769 result = node[2] # Compute result for the following operation.
770 elif operation in {'<<', '>>'}: # Try this alternative condition after earlier branches fail.
771 result = node[2] # Compute result for the following operation.
772 elif operation in {'&&', '||'}: # Try this alternative condition after earlier branches fail.
773 result = 'bool' # Compute result for the following operation.
774 else: # Handle the remaining case.
775 a, b, result = promote(node[2], right[2]) # Apply promote to these arguments.
776 cast(node, a) # Apply cast to these arguments.
777 cast(right, b) # Apply cast to these arguments.
778 if priority in {8, 9}: # Select this branch when its condition holds.
779 result = 'bool' # Compute result for the following operation.
780 node = (node[0], right[1], result) # Compute node for the following operation.
781 self.depth -= 1 # Supply self for the continued expression.
782 return node # Return this computed value to the caller.
783
784 def atom(self): # Read one literal, identifier, call or indexed member expression.
785 if self.position >= len(tokens): # Select this branch when its condition holds.
786 return None # Return this computed value to the caller.
787 value, start, end = tokens[self.position] # Supply value for the continued expression.
788 if value in {'+', '-', '!', '~', '++', '--'}: # Select this branch when its condition holds.
789 self.position += 1 # Compute self.position for the following operation.
790 child = self.atom() # Compute child for the following operation.
791 return ( # Return this computed value to the caller.
792 (start, child[1], 'bool' if value == '!' else child[2]) # Supply start for the continued expression.
793 if child # Select this branch when its condition holds.
794 else None # Supply else for the continued expression.
795 ) # Finish this expression or collection.
796 if value == '(': # Select this branch when its condition holds.
797 self.position += 1 # Compute self.position for the following operation.
798 child = self.parse() # Compute child for the following operation.
799 if not child or self.value() != ')': # Select this branch when its condition holds.
800 return None # Return this computed value to the caller.
801 end = tokens[self.position][2] # Compute end for the following operation.
802 self.position += 1 # Compute self.position for the following operation.
803 node = (start, end, child[2]) # Compute node for the following operation.
804 elif re.fullmatch( # Try this alternative condition after earlier branches fail.
805 r'(?:0[xX][\da-fA-F]+[uU]?|(?:\d+\.\d*|\.\d+|\d+)(?:[eE][+-]?\d+)?[fFuU]?)',
806 value, # Supply value for the continued expression.
807 ): # Finish this expression or collection.
808 kind = ( # Compute kind for the following operation.
809 'uint'
810 if value[-1:] in {'u', 'U'} # Select this branch when its condition holds.
811 else ( # Apply else to these arguments.
812 'float'
813 if not value.lower().startswith('0x') # Select this branch when its condition holds.
814 and re.search(r'[.eEfF]', value) # Apply search to these arguments.
815 else 'int' # Supply else for the continued expression.
816 ) # Finish this expression or collection.
817 ) # Finish this expression or collection.
818 self.position += 1 # Compute self.position for the following operation.
819 node = (start, end, kind) # Compute node for the following operation.
820 elif re.fullmatch(r'[A-Za-z_]\w*', value): # Try this alternative condition after earlier branches fail.
821 self.position += 1 # Compute self.position for the following operation.
822 kind = 'bool' if value in {'true', 'false'} else symbol(value) # Compute kind for the following operation.
823 node = (start, end, kind) # Compute node for the following operation.
824 if self.value() == '(': # Select this branch when its condition holds.
825 self.position += 1 # Compute self.position for the following operation.
826 arguments = [] # Compute arguments for the following operation.
827 while self.position < len(tokens) and self.value() != ')': # Continue while the parser still has matching input.
828 argument = self.parse() # Compute argument for the following operation.
829 if not argument: # Select this branch when its condition holds.
830 return None # Return this computed value to the caller.
831 arguments.append(argument) # Apply append to these arguments.
832 if self.value() != ',': # Select this branch when its condition holds.
833 break # Finish this scan at the current boundary.
834 self.position += 1 # Compute self.position for the following operation.
835 if self.value() != ')': # Select this branch when its condition holds.
836 return None # Return this computed value to the caller.
837 end = tokens[self.position][2] # Compute end for the following operation.
838 self.position += 1 # Compute self.position for the following operation.
839 if value in UNIFORMS: # Select this branch when its condition holds.
840 kind = value # Compute kind for the following operation.
841 elif value in functions and len(functions[value]) == 1: # Try this alternative condition after earlier branches fail.
842 kind, parameters = next(iter(functions[value])) # Apply next to these arguments.
843 if len(parameters) == len(arguments): # Select this branch when its condition holds.
844 for argument, (expected, output) in zip( # Process each argument, (expected, output) in this sequence.
845 arguments, parameters # Supply arguments for the continued expression.
846 ): # Finish this expression or collection.
847 if not output: # Select this branch when its condition holds.
848 cast(argument, expected) # Apply cast to these arguments.
849 elif value in {'dot', 'length', 'distance', 'determinant'}: # Try this alternative condition after earlier branches fail.
850 kind = 'float' # Compute kind for the following operation.
851 elif ( # Try this alternative condition after earlier branches fail.
852 value # Supply value for the continued expression.
853 in { # Supply in for the continued expression.
854 'lessThan',
855 'lessThanEqual',
856 'greaterThan',
857 'greaterThanEqual',
858 'equal',
859 'notEqual',
860 } # Finish this expression or collection.
861 and arguments # Supply and for the continued expression.
862 ): # Finish this expression or collection.
863 kind = ( # Compute kind for the following operation.
864 'b' + shape(arguments[0][2]) # Apply shape to these arguments.
865 if shape(arguments[0][2]) # Select this branch when its condition holds.
866 else None # Supply else for the continued expression.
867 ) # Finish this expression or collection.
868 elif value in {'any', 'all'}: # Try this alternative condition after earlier branches fail.
869 kind = 'bool' # Compute kind for the following operation.
870 elif value in {'imageLoad'}: # Try this alternative condition after earlier branches fail.
871 kind = None # Compute kind for the following operation.
872 elif arguments and value in { # Try this alternative condition after earlier branches fail.
873 'radians',
874 'degrees',
875 'sin',
876 'cos',
877 'tan',
878 'asin',
879 'acos',
880 'atan',
881 'sinh',
882 'cosh',
883 'tanh',
884 'asinh',
885 'acosh',
886 'atanh',
887 'pow',
888 'exp',
889 'log',
890 'exp2',
891 'log2',
892 'sqrt',
893 'inversesqrt',
894 'abs',
895 'sign',
896 'floor',
897 'trunc',
898 'round',
899 'roundEven',
900 'ceil',
901 'fract',
902 'mod',
903 'min',
904 'max',
905 'clamp',
906 'mix',
907 'step',
908 'smoothstep',
909 'fma',
910 'ldexp',
911 'normalize',
912 'faceforward',
913 'reflect',
914 'refract',
915 'matrixCompMult',
916 'transpose',
917 'inverse',
918 }: # Finish this expression or collection.
919 kind = arguments[0][2] # Compute kind for the following operation.
920 else: # Handle the remaining case.
921 kind = None # Compute kind for the following operation.
922 node = (start, end, kind) # Compute node for the following operation.
923 else: # Handle the remaining case.
924 return None # Return this computed value to the caller.
925 while self.position < len(tokens): # Continue while the parser still has matching input.
926 value = self.value() # Compute value for the following operation.
927 if value == '[': # Select this branch when its condition holds.
928 self.position += 1 # Compute self.position for the following operation.
929 self.parse() # Apply parse to these arguments.
930 if self.value() != ']': # Select this branch when its condition holds.
931 return node # Return this computed value to the caller.
932 end = tokens[self.position][2] # Compute end for the following operation.
933 self.position += 1 # Compute self.position for the following operation.
934 kind = node[2] # Compute kind for the following operation.
935 if kind and kind.endswith('[]'): # Select this branch when its condition holds.
936 kind = kind[:-2] # Compute kind for the following operation.
937 elif kind and 'vec' in kind: # Try this alternative condition after earlier branches fail.
938 kind = scalar(kind) # Compute kind for the following operation.
939 elif kind and kind.startswith('mat'): # Try this alternative condition after earlier branches fail.
940 kind = 'vec' + kind[-1] # Compute kind for the following operation.
941 else: # Handle the remaining case.
942 kind = None # Compute kind for the following operation.
943 node = (node[0], end, kind) # Compute node for the following operation.
944 elif value == '.' and self.position + 1 < len(tokens): # Try this alternative condition after earlier branches fail.
945 field, _, end = tokens[self.position + 1] # Supply field for the continued expression.
946 self.position += 2 # Compute self.position for the following operation.
947 kind = node[2] # Compute kind for the following operation.
948 if field == 'length' and self.value() == '(' and self.position + 1 < len(tokens) and tokens[self.position + 1][0] == ')': # Every native GLSL empty array length query has signed int type.
949 end = tokens[self.position + 1][2] # Include both parentheses in the promoted method expression.
950 self.position += 2 # Consume the empty method argument list.
951 kind = 'int' # Apply signed GLSL length semantics before choosing numeric promotions.
952 elif kind in fields: # Ordinary structure members retain their declared component type.
953 kind = fields[kind].get(field) # Compute kind for the following operation.
954 elif ( # Try this alternative condition after earlier branches fail.
955 kind # Supply kind for the continued expression.
956 and 'vec' in kind # Supply and for the continued expression.
957 and re.fullmatch(r'[xyzwrgba stpq]+'.replace(' ', ''), field) # Apply fullmatch to these arguments.
958 ): # Finish this expression or collection.
959 kind = ( # Compute kind for the following operation.
960 scalar(kind) # Apply scalar to these arguments.
961 if len(field) == 1 # Select this branch when its condition holds.
962 else replace_scalar('vec' + str(len(field)), scalar(kind)) # Apply replace scalar to these arguments.
963 ) # Finish this expression or collection.
964 else: # Handle the remaining case.
965 kind = None # Compute kind for the following operation.
966 node = (node[0], end, kind) # Compute node for the following operation.
967 elif value in {'++', '--'}: # Try this alternative condition after earlier branches fail.
968 node = (node[0], tokens[self.position][2], node[2]) # Compute node for the following operation.
969 self.position += 1 # Compute self.position for the following operation.
970 else: # Handle the remaining case.
971 break # Finish this scan at the current boundary.
972 return node # Return this computed value to the caller.
973
974 for position, (value, _, _) in enumerate(tokens): # Process each position, (value, , ) in this sequence.
975 if (position == 0 or tokens[position - 1][0] != '.') and re.fullmatch( # Select this branch when its condition holds.
976 r'[A-Za-z_]\w*|\d[\w.]*|\.[0-9][\w.]*', value # Supply r for the continued expression.
977 ): # Finish this expression or collection.
978 Expression(position).parse() # Apply Expression to these arguments.
979 inserts = {} # Compute inserts for the following operation.
980 for start, end, kind in conversions: # Process each start, end, kind in this sequence.
981 inserts.setdefault(start, []).append((end, kind + '(')) # Apply setdefault to these arguments.
982 inserts.setdefault(end, []).append((start, ')')) # Apply setdefault to these arguments.
983 # Open nested casts widest-first and close them innermost-first to preserve evaluation order.
984 for offset, items in sorted(inserts.items(), reverse=True): # Process each offset, items in this sequence.
985 endings = ''.join( # Compute endings for the following operation.
986 text for start, text in sorted(items, reverse=True) if text == ')' # Apply sorted to these arguments.
987 ) # Finish this expression or collection.
988 beginnings = ''.join( # Compute beginnings for the following operation.
989 text for end, text in sorted(items, reverse=True) if text != ')' # Apply sorted to these arguments.
990 ) # Finish this expression or collection.
991 source = source[:offset] + endings + beginnings + source[offset:] # Compute source for the following operation.
992 return source # Return this computed value to the caller.
993
994
995def portable_integer_mix(source): # Supply boolean integer selection for every supported profile.
996 """Supply integer boolean selection where older profiles lack that overload."""
997 code = mask_noncode(source) # Compute code for the following operation.
998 if not re.search(r'\bmix\s*\(', code) or re.search( # Select this branch when its condition holds.
999 r'\b\w+\s+mix\s*\([^;{}]*\)\s*\{', code # Supply r for the continued expression.
1000 ): # Finish this expression or collection.
1001 return source # Return this computed value to the caller.
1002 name = 'shdr_portable_mix_' + hashlib.sha256(source.encode()).hexdigest()[:12] # Compute name for the following operation.
1003 helper = '#if (!defined(GL_ES) && __VERSION__ < 450) || (defined(GL_ES) && __VERSION__ < 320)\n' # Compute helper for the following operation.
1004 for scalar in ('float', 'int', 'uint'): # Process each scalar in this sequence.
1005 helper += ( # Compute helper for the following operation.
1006 scalar # Supply scalar for the continued expression.
1007 + ' '
1008 + name # Supply name for the continued expression.
1009 + '('
1010 + scalar # Supply scalar for the continued expression.
1011 + ' a,'
1012 + scalar # Supply scalar for the continued expression.
1013 + ' b,bool select_b){return select_b?b:a;}\n'
1014 ) # Finish this expression or collection.
1015 if scalar == 'float': # Select this branch when its condition holds.
1016 helper += ( # Compute helper for the following operation.
1017 'float '
1018 + name # Supply name for the continued expression.
1019 + '(float a,float b,float weight){return mix(a,b,weight);}\n'
1020 ) # Finish this expression or collection.
1021 for size in (2, 3, 4): # Process each size in this sequence.
1022 vector = ( # Compute vector for the following operation.
1023 'vec' if scalar == 'float' else 'ivec' if scalar == 'int' else 'uvec' # Supply vec for the continued expression.
1024 ) + str(size) # Apply str to these arguments.
1025 components = ','.join( # Compute components for the following operation.
1026 'select_b.' + c + '?b.' + c + ':a.' + c for c in 'xyzw'[:size] # Supply select b for the continued expression.
1027 ) # Finish this expression or collection.
1028 helper += ( # Compute helper for the following operation.
1029 vector # Supply vector for the continued expression.
1030 + ' '
1031 + name # Supply name for the continued expression.
1032 + '('
1033 + vector # Supply vector for the continued expression.
1034 + ' a,'
1035 + vector # Supply vector for the continued expression.
1036 + ' b,bvec'
1037 + str(size) # Apply str to these arguments.
1038 + ' select_b){return '
1039 + vector # Supply vector for the continued expression.
1040 + '('
1041 + components # Supply components for the continued expression.
1042 + ');}\n'
1043 ) # Finish this expression or collection.
1044 if scalar == 'float': # Select this branch when its condition holds.
1045 for weight in ('float', vector): # Process each weight in this sequence.
1046 helper += ( # Compute helper for the following operation.
1047 vector # Supply vector for the continued expression.
1048 + ' '
1049 + name # Supply name for the continued expression.
1050 + '('
1051 + vector # Supply vector for the continued expression.
1052 + ' a,'
1053 + vector # Supply vector for the continued expression.
1054 + ' b,'
1055 + weight # Supply weight for the continued expression.
1056 + ' weight){return mix(a,b,weight);}\n'
1057 ) # Finish this expression or collection.
1058 helper += '#define mix ' + name + '\n#endif\n' # Compute helper for the following operation.
1059 return helper + source # Return this computed value to the caller.
1060
1061
1062def needs_invocation_frame(source): # Detect private values whose native initialization must happen for every invocation.
1063 """Find initialized nonconst globals while ignoring interfaces and function locals."""
1064 code = mask_noncode(source) # Hide comments and quoted text before examining declaration scopes.
1065 for macro in re.finditer(r'(?m)^[ \t]*#[ \t]*define\b(?:[^\n]|(?<=\\)\n)*', code): # Include continuation lines when inspecting macro-defined private state.
1066 if re.search(r'(?<![=<>!])=(?!=)', macro.group()): # An assignment macro can introduce a mutable global after preprocessing.
1067 return True # Give macro-declared initialized globals a fresh invocation frame.
1068 code = re.sub(r'(?m)^[ \t]*#[^\n]*', lambda m: ' ' * len(m.group()), code) # Keep preprocessor text out of declaration matching.
1069 declarations = re.compile(r'\b(?P<qualifiers>(?:(?:const|highp|mediump|lowp)\s+)*)(?P<type>\w+)\s+\w+\s*(?:\[[^]]*\]\s*)*(?=[=,;])') # Accept scalar, vector, matrix, array and named private types.
1070 position, braces, parentheses = 0, 0, 0 # Track the scope preceding each possible global declaration.
1071 for match in declarations.finditer(code): # Inspect declarations in their original source order.
1072 for token in code[position:match.start()]: # Advance the lexical scope to this declaration's first token.
1073 if token == '{': # An opening brace enters a function, structure or initializer scope.
1074 braces += 1 # Keep this scan at the top-level declaration scope.
1075 elif token == '}': # A closing brace returns to the enclosing scope.
1076 braces -= 1 # Make subsequent top-level declarations eligible again.
1077 elif token == '(': # Distinguish parameters and call arguments from global declarations.
1078 parentheses += 1 # Keep this scan at the enclosing declaration level.
1079 elif token == ')': # A closing parenthesis ends that parameter or argument scope.
1080 parentheses -= 1 # Restore the enclosing declaration scope.
1081 position = match.start() # Continue counting from the same declaration boundary next time.
1082 if braces or parentheses or 'const' in match.group('qualifiers').split(): # Keep constants and local declarations in their existing scopes.
1083 continue # Keep the unchanged generated fast path for these declarations.
1084 prefix = code[:match.start()].rstrip() # Check qualifiers before a declaration matched from its numeric type.
1085 if re.search(r'\b(?:uniform|const)\s*$', prefix): # Uniform registration and constant values belong to the outer kernel.
1086 continue # Keep registered interfaces in their existing resource storage.
1087 end = statement_end(code, match.start()) # Include all declarators in a comma-separated global declaration.
1088 if '=' in code[match.end():end]: # Any initialized declarator in this statement needs fresh invocation state.
1089 return True # Select the frame only for shaders with initialized mutable globals.
1090 return False # Preserve existing adapter bytes for ordinary shader declarations.
1091
1092
1093def invocation_frame(cpu, name, interfaces): # Isolate native private globals from persistent kernel bindings.
1094 """Keep ports on the kernel and construct private shader state for each CPU call."""
1095 declarations, references, initializers = [], [], [] # Separate registered resources from fresh private implementation state.
1096 for builtin in ('gl_GlobalInvocationID', 'gl_LocalInvocationID', 'gl_WorkGroupID', 'gl_NumWorkGroups', 'gl_WorkGroupSize', 'gl_LocalInvocationIndex'): # Read the coordinates and sizes already prepared by the CPU dispatch loop.
1097 references.append('const decltype(::shdr::kernel::' + builtin + ')& ' + builtin + ';') # Keep the native builtin value immutable inside this invocation frame.
1098 initializers.append(builtin + '(owner.' + builtin + ')') # Bind the frame to this invocation's outer-kernel builtin fields before private initialization.
1099 seen = set() # A push-constant structure needs one reference even though it exposes several fields.
1100 for begin, end, variable, declaration, uniform in interfaces: # Extract only interface regions explicitly marked by transform.
1101 start = cpu.index(begin) # Locate the beginning of this generated interface declaration.
1102 stop = cpu.index(end, start) + len(end) # Include the end marker through its final character.
1103 declarations.append(cpu[start + len(begin):stop - len(end)]) # Retain the fully translated declaration on the outer kernel.
1104 cpu = cpu[:start] + '\n' * cpu[start:stop].count('\n') + cpu[stop:] # Leave private code and its line boundaries inside the frame.
1105 if variable not in seen: # Shared named blocks and push-constant fields reference one root object.
1106 qualifier = 'const ' if uniform else '' # Shader uniforms stay read-only inside invocation code.
1107 references.append(qualifier + 'decltype(' + name + '::' + variable + ')& ' + variable + ';') # Refer to the registered outer resource or uniform value.
1108 initializers.append(variable + '(owner.' + variable + ')') # Initialize references before any private member initializer runs.
1109 seen.add(variable) # Avoid declaring the same named interface reference twice.
1110 cpu = cpu.replace('SHDR_MAIN()', 'void main()') # Declare the frame entry point as an ordinary method.
1111 frame = 'shdr_invocation_' + hashlib.sha256(cpu.encode()).hexdigest()[:12] # Avoid colliding with shader-author names.
1112 initializer_list = ' : ' + ','.join(initializers) if initializers else '' # Give resource-free shaders fresh private state.
1113 return '\n'.join(declarations) + '\nstruct ' + frame + ' {\n' + '\n'.join(references) + '\nexplicit ' + frame + '(' + name + '& owner)' + initializer_list + ' {}\n' + cpu + '\n};\nvoid main() override { ' + frame + ' invocation(*this); invocation.main(); }\n' # Construct one private frame for every native CPU invocation.
1114
1115
1116def transform(source, name, interface_capture=None): # Produce the CPU body and embedded portable GPU body from native GLSL.
1117 """Return the C++ shader class and its GLSL text, interface forms are validated."""
1118 if not re.fullmatch(r'[A-Za-z_]\w*', name): # Select this branch when its condition holds.
1119 raise ShaderError('invalid shader name: ' + name) # Report the shader declaration validation error.
1120 def frame_interface(declaration, variable, uniform=False): # Record exact generated interface boundaries only when an invocation frame is required.
1121 if interface_capture is None: # Preserve the existing emitted text for ordinary shaders.
1122 return declaration # Preserve the fast-path adapter byte for byte.
1123 index = len(interface_capture) # Give each interface a unique generated marker pair.
1124 marker = 'shdr_frame_port_' + hashlib.sha256(source.encode()).hexdigest()[:12] + '_' + str(index) # Derive unique generated markers from this source.
1125 begin = '/* ' + marker + '_begin */' # Mark the start with a source comment.
1126 end = '/* ' + marker + '_end */' # Mark the matching end after any registered uniform descriptors.
1127 interface_capture.append((begin, end, variable, declaration, uniform)) # Keep the root name and access category for the frame reference.
1128 return begin + declaration + end # Let later ordinary CPU translations process the declaration unchanged.
1129 # shdr.h belongs to the CPU build only. All remaining shader input is self-contained.
1130 gpu = re.sub( # Compute gpu for the following operation.
1131 r'^[ \t]*#[ \t]*include[ \t]*[<"]shdr\.h[>"][ \t]*(?://[^\n]*|/\*[^\n]*?\*/[ \t]*)?$',
1132 '',
1133 source, # Supply source for the continued expression.
1134 flags=re.MULTILINE, # Compute flags for the following operation.
1135 ) # Finish this expression or collection.
1136 # The device selects its language profile; retain the imported shader body and line numbers.
1137 gpu = re.sub(r'^[ \t]*#[ \t]*version[^\n]*', '', gpu, flags=re.MULTILINE) # Compute gpu for the following operation.
1138 masked = mask_noncode(gpu) # Compute masked for the following operation.
1139 if re.search(r'^\s*#\s*include\b', masked, re.MULTILINE): # Select this branch when its condition holds.
1140 raise ShaderError( # Report the shader declaration validation error.
1141 'shader includes are unsupported; each shader is self-contained'
1142 ) # Finish this expression or collection.
1143 if re.search( # Select this branch when its condition holds.
1144 r'\b(?:shared|barrier|memoryBarrier\w*|groupMemoryBarrier|atomic\w*)\b', masked # Apply b to these arguments.
1145 ): # Finish this expression or collection.
1146 raise ShaderError( # Report the shader declaration validation error.
1147 'shared memory, barriers and atomics are outside the CPU portable subset'
1148 ) # Finish this expression or collection.
1149 replacements = [] # Compute replacements for the following operation.
1150 ports = [] # Compute ports for the following operation.
1151 named_arrays = set() # Compute named arrays for the following operation.
1152 covered = [] # Compute covered for the following operation.
1153 definitions = { # Compute definitions for the following operation.
1154 m.group(1): m.group(2).strip() # Apply group to these arguments.
1155 for m in re.finditer( # Process each m in this sequence.
1156 r'^[ \t]*#\s*define\s+(\w+)\s+([^\n]+)', masked, re.MULTILINE # Supply r for the continued expression.
1157 ) # Finish this expression or collection.
1158 } # Finish this expression or collection.
1159 constants = scoped_integer_constants(masked, definitions) # Compute constants for the following operation.
1160 resolve = lambda text, label, default=None: integer(text, label, default, constants) # Compute resolve for the following operation.
1161 local = [1, 1, 1] # Compute local for the following operation.
1162 local_matches = list(re.finditer(r'\blayout\s*\(([^)]*)\)\s*in\s*;', masked)) # Compute local matches for the following operation.
1163 gpu_edits = [] # Compute gpu edits for the following operation.
1164 seen_axes = set() # Compute seen axes for the following operation.
1165 for match in local_matches: # Process each match in this sequence.
1166 values = layout_values(match.group(1)) # Compute values for the following operation.
1167 if set(values) - { # Select this branch when its condition holds.
1168 'local_size_' + a + suffix for a in 'xyz' for suffix in ('', '_id') # Apply in to these arguments.
1169 }: # Finish this expression or collection.
1170 raise ShaderError('unsupported compute execution layout') # Report the shader declaration validation error.
1171 for i, axis in enumerate('xyz'): # Process each i, axis in this sequence.
1172 key = 'local_size_' + axis # Compute key for the following operation.
1173 if key not in values and key + '_id' not in values: # Select this branch when its condition holds.
1174 continue # Skip the remaining work for this iteration.
1175 value = resolve(values.get(key), key, 1) # Compute value for the following operation.
1176 if not value: # Select this branch when its condition holds.
1177 raise ShaderError('local workgroup dimensions must be positive') # Report the shader declaration validation error.
1178 if axis in seen_axes and local[i] != value: # Select this branch when its condition holds.
1179 raise ShaderError('conflicting local workgroup sizes') # Report the shader declaration validation error.
1180 seen_axes.add(axis) # Apply add to these arguments.
1181 local[i] = value # Supply local for the continued expression.
1182 covered.append(match.span()) # Apply append to these arguments.
1183 replacements.append((*match.span(), '')) # Apply append to these arguments.
1184 if local_matches: # Select this branch when its condition holds.
1185 for i, match in enumerate(local_matches): # Process each i, match in this sequence.
1186 gpu_edits.append( # Apply append to these arguments.
1187 ( # Finish this expression or collection.
1188 *match.span(), # Apply span to these arguments.
1189 ( # Finish this expression or collection.
1190 ( # Finish this expression or collection.
1191 'layout('
1192 + ','.join( # Apply join to these arguments.
1193 'local_size_' + a + '=' + str(local[j]) # Apply str to these arguments.
1194 for j, a in enumerate('xyz') # Process each j, a in this sequence.
1195 ) # Finish this expression or collection.
1196 + ') in;'
1197 ) # Finish this expression or collection.
1198 if i == 0 # Select this branch when its condition holds.
1199 else '' # Supply else for the continued expression.
1200 ), # Finish this expression or collection.
1201 ) # Finish this expression or collection.
1202 ) # Finish this expression or collection.
1203 else: # Handle the remaining case.
1204 gpu_edits.append( # Apply append to these arguments.
1205 (0, 0, 'layout(local_size_x=1,local_size_y=1,local_size_z=1) in;\n') # Apply layout to these arguments.
1206 ) # Finish this expression or collection.
1207
1208 # Vulkan specialization defaults become ordinary constants in an OpenGL import.
1209 for match in re.finditer( # Process each match in this sequence.
1210 r'\blayout\s*\(\s*constant_id\s*=\s*[^)]+\)\s*(const\s+\w+\s+\w+\s*=\s*[^;]+;)',
1211 masked, # Supply masked for the continued expression.
1212 ): # Finish this expression or collection.
1213 covered.append(match.span()) # Apply append to these arguments.
1214 replacements.append((*match.span(), match.group(1))) # Apply append to these arguments.
1215 gpu_edits.append((*match.span(), gpu[match.start(1) : match.end(1)])) # Apply append to these arguments.
1216
1217 # A scalar/vector push-constant block maps to individually named uniforms.
1218 push_blocks = re.compile( # Compute push blocks for the following operation.
1219 r'\blayout\s*\(\s*push_constant\s*\)\s*uniform\s+(\w+)\s*\{([^{}]*)\}\s*(\w+)\s*;'
1220 ) # Finish this expression or collection.
1221 for match in push_blocks.finditer(masked): # Process each match in this sequence.
1222 block, body, instance = match.groups() # Apply groups to these arguments.
1223 fields = list(re.finditer(r'\b(\w+)\s+(\w+)\s*;', body)) # Compute fields for the following operation.
1224 remainder = body # Compute remainder for the following operation.
1225 for field in reversed(fields): # Process each field in this sequence.
1226 remainder = remainder[: field.start()] + remainder[field.end() :] # Compute remainder for the following operation.
1227 if ( # Select this branch when its condition holds.
1228 remainder.strip() # Apply strip to these arguments.
1229 or not fields # Supply or for the continued expression.
1230 or any(f.group(1) not in UNIFORMS for f in fields) # Apply any to these arguments.
1231 ): # Finish this expression or collection.
1232 raise ShaderError('push constants require scalar/vector/matrix fields') # Report the shader declaration validation error.
1233 declaration = ( # Compute declaration for the following operation.
1234 'struct '
1235 + block # Supply block for the continued expression.
1236 + ' { '
1237 + ' '.join(f.group() for f in fields) # Apply join to these arguments.
1238 + ' } '
1239 + instance # Supply instance for the continued expression.
1240 + '{};\n'
1241 ) # Finish this expression or collection.
1242 for field in fields: # Process each field in this sequence.
1243 scalar, variable = field.groups() # Apply groups to these arguments.
1244 port_name = instance + '.' + variable # Compute port name for the following operation.
1245 ports.append((port_name, 'uniform', 0)) # Apply append to these arguments.
1246 named_arrays.add((instance, variable)) # Apply add to these arguments.
1247 declaration += f'::shdr::uniform_port<{scalar}> shdr_uniform_{instance}_{variable}{{*this,"{port_name}",{port_name}}};\n'
1248 declaration = frame_interface(declaration, instance, True) # Keep the complete push-constant object registered outside private invocation state.
1249 covered.append(match.span()) # Apply append to these arguments.
1250 replacements.append((*match.span(), declaration)) # Apply append to these arguments.
1251 gpu_edits.append( # Apply append to these arguments.
1252 ( # Finish this expression or collection.
1253 *match.span(), # Apply span to these arguments.
1254 'struct '
1255 + block # Supply block for the continued expression.
1256 + ' {'
1257 + gpu[match.start(2) : match.end(2)] # Apply start to these arguments.
1258 + '}; uniform '
1259 + block # Supply block for the continued expression.
1260 + ' '
1261 + instance # Supply instance for the continued expression.
1262 + ';',
1263 ) # Finish this expression or collection.
1264 ) # Finish this expression or collection.
1265
1266 default_buffer = False # Compute default buffer for the following operation.
1267 for match in re.finditer(r'\blayout\s*\(([^)]*)\)\s*buffer\s*;', masked): # Process each match in this sequence.
1268 values = layout_values(match.group(1)) # Compute values for the following operation.
1269 if 'std430' not in values or set(values) - {'std430', 'column_major'}: # Select this branch when its condition holds.
1270 raise ShaderError('default storage buffer layout must be std430') # Report the shader declaration validation error.
1271 default_buffer = True # Compute default buffer for the following operation.
1272 covered.append(match.span()) # Apply append to these arguments.
1273 replacements.append((*match.span(), '')) # Apply append to these arguments.
1274 buffers = re.compile( # Compute buffers for the following operation.
1275 r'(?:(?:\blayout\s*\(([^)]*)\)\s*))?('
1276 + QUALIFIERS # Supply QUALIFIERS for the continued expression.
1277 + r')\bbuffer\s+(\w+)\s*\{([^{}]*)\}\s*(\w+)?\s*;'
1278 ) # Finish this expression or collection.
1279 buffer_matches = list(buffers.finditer(masked)) # Compute buffer matches for the following operation.
1280 reserved_bindings = { # Compute reserved bindings for the following operation.
1281 resolve(layout_values(m.group(1) or '').get('binding'), 'SSBO binding') # Apply resolve to these arguments.
1282 for m in buffer_matches # Process each m in this sequence.
1283 if 'binding' in layout_values(m.group(1) or '') # Select this branch when its condition holds.
1284 } # Finish this expression or collection.
1285 for match in buffer_matches: # Process each match in this sequence.
1286 values = layout_values(match.group(1) or '') # Compute values for the following operation.
1287 if set(values) - {'std430', 'binding', 'column_major', 'set'}: # Select this branch when its condition holds.
1288 raise ShaderError('storage buffer layout must support std430') # Report the shader declaration validation error.
1289 if 'set' in values and resolve(values['set'], 'descriptor set') != 0: # Select this branch when its condition holds.
1290 raise ShaderError('OpenGL imports support only descriptor set zero') # Report the shader declaration validation error.
1291 if 'binding' in values: # Select this branch when its condition holds.
1292 binding = resolve(values['binding'], 'SSBO binding') # Compute binding for the following operation.
1293 else: # Handle the remaining case.
1294 binding = 0 # Compute binding for the following operation.
1295 while binding in reserved_bindings: # Continue while the parser still has matching input.
1296 binding += 1 # Compute binding for the following operation.
1297 reserved_bindings.add(binding) # Apply add to these arguments.
1298 qualifiers = 'std430,binding=' + str(binding) # Compute qualifiers for the following operation.
1299 if 'column_major' in values: # Select this branch when its condition holds.
1300 qualifiers += ',column_major' # Compute qualifiers for the following operation.
1301 if match.group(1) is not None: # Select this branch when its condition holds.
1302 close = masked.index(')', match.start()) + 1 # Compute close for the following operation.
1303 gpu_edits.append((match.start(), close, 'layout(' + qualifiers + ')')) # Apply append to these arguments.
1304 else: # Handle the remaining case.
1305 gpu_edits.append( # Apply append to these arguments.
1306 (match.start(), match.start(), 'layout(' + qualifiers + ') ') # Apply start to these arguments.
1307 ) # Finish this expression or collection.
1308 member = re.fullmatch( # Compute member for the following operation.
1309 r'\s*(?:(?:highp|mediump|lowp)\s+)?(\w+)\s+(\w+)\s*\[\s*([^]]*)\]\s*;\s*',
1310 match.group(4), # Apply group to these arguments.
1311 ) # Finish this expression or collection.
1312 if not member or member.group(1) not in STORAGE: # Select this branch when its condition holds.
1313 raise ShaderError( # Report the shader declaration validation error.
1314 'buffer blocks support one runtime-sized scalar/vec2/vec4 array'
1315 ) # Finish this expression or collection.
1316 scalar, variable, size = member.groups() # Apply groups to these arguments.
1317 fixed = resolve(size.strip(), 'buffer array length') if size.strip() else 0 # Compute fixed for the following operation.
1318 if size.strip() and not fixed: # Select this branch when its condition holds.
1319 raise ShaderError('fixed array length must be positive') # Report the shader declaration validation error.
1320 mode = access_mode(match.group(2)) # Compute mode for the following operation.
1321 if match.group(5): # Select this branch when its condition holds.
1322 instance = match.group(5) # Compute instance for the following operation.
1323 named_arrays.add((instance, variable)) # Apply add to these arguments.
1324 port_name = instance + '.' + variable # Compute port name for the following operation.
1325 declaration = ( # Compute declaration for the following operation.
1326 'struct shdr_block_' + match.group(3) + ' { ' # Apply group to these arguments.
1327 f'::shdr::buffer_port<{scalar},::shdr::access::{mode}> {variable}; '
1328 f'explicit shdr_block_{match.group(3)}(::shdr::kernel& p) : '
1329 f'{variable}(p,{binding},"{port_name}",{fixed}) {{}} }} {instance}{{*this}};'
1330 ) # Finish this expression or collection.
1331 else: # Handle the remaining case.
1332 port_name = variable # Compute port name for the following operation.
1333 declaration = f'::shdr::buffer_port<{scalar},::shdr::access::{mode}> {variable}{{*this,{binding},"{variable}",{fixed}}};'
1334 declaration = frame_interface(declaration, match.group(5) or variable) # Keep storage ports and their owning named block on the persistent kernel.
1335 ports.append((port_name, 'buffer', binding)) # Apply append to these arguments.
1336 covered.append(match.span()) # Apply append to these arguments.
1337 replacements.append((*match.span(), declaration)) # Apply append to these arguments.
1338
1339 images = re.compile( # Compute images for the following operation.
1340 r'\blayout\s*\(([^)]*)\)\s*('
1341 + QUALIFIERS # Supply QUALIFIERS for the continued expression.
1342 + r')uniform\s+('
1343 + QUALIFIERS # Supply QUALIFIERS for the continued expression.
1344 + r')(image2D|iimage2D|uimage2D)\s+(\w+)\s*;'
1345 ) # Finish this expression or collection.
1346 for match in images.finditer(masked): # Process each match in this sequence.
1347 values = layout_values(match.group(1)) # Compute values for the following operation.
1348 formats = set(values) & set(IMAGES) # Compute formats for the following operation.
1349 if len(formats) != 1 or set(values) - formats - {'binding'}: # Select this branch when its condition holds.
1350 raise ShaderError( # Report the shader declaration validation error.
1351 'image requires binding=N and a supported r32/rgba32 format'
1352 ) # Finish this expression or collection.
1353 image_format = formats.pop() # Compute image format for the following operation.
1354 scalar, components, expected_type = IMAGES[image_format] # Supply scalar for the continued expression.
1355 if match.group(4) != expected_type: # Select this branch when its condition holds.
1356 raise ShaderError('image type and format do not match: ' + match.group(5)) # Report the shader declaration validation error.
1357 binding = resolve(values.get('binding'), 'image binding') # Compute binding for the following operation.
1358 mode = access_mode(match.group(2) + match.group(3)) # Compute mode for the following operation.
1359 if components == 4 and mode == 'readwrite': # Select this branch when its condition holds.
1360 raise ShaderError( # Report the shader declaration validation error.
1361 'portable RGBA images require separate readonly/writeonly declarations'
1362 ) # Finish this expression or collection.
1363 declaration = f'SHDR_IMAGE2D({binding},{image_format},{expected_type},{match.group(5)},{mode})'
1364 declaration = frame_interface(declaration, match.group(5)) # Keep the registered image port outside the fresh private frame.
1365 ports.append((match.group(5), 'image', binding)) # Apply append to these arguments.
1366 covered.append(match.span()) # Apply append to these arguments.
1367 replacements.append((*match.span(), declaration)) # Apply append to these arguments.
1368
1369 uniforms = re.compile( # Compute uniforms for the following operation.
1370 r'(?:\blayout\s*\(([^)]*)\)\s*)?\buniform\s+(?:(?:highp|mediump|lowp)\s+)?(\w+)\s+(\w+)\s*;'
1371 ) # Finish this expression or collection.
1372 for match in uniforms.finditer(masked): # Process each match in this sequence.
1373 if any(a <= match.start() < b for a, b in covered): # Select this branch when its condition holds.
1374 continue # Skip the remaining work for this iteration.
1375 if match.group(2) not in UNIFORMS: # Select this branch when its condition holds.
1376 raise ShaderError('unsupported uniform type: ' + match.group(2)) # Report the shader declaration validation error.
1377 if match.group(1) and set(layout_values(match.group(1))) - {'location'}: # Select this branch when its condition holds.
1378 raise ShaderError('unsupported uniform layout qualifier') # Report the shader declaration validation error.
1379 ports.append((match.group(3), 'uniform', 0)) # Apply append to these arguments.
1380 covered.append(match.span()) # Apply append to these arguments.
1381 replacements.append( # Apply append to these arguments.
1382 (*match.span(), frame_interface(f'SHDR_UNIFORM({match.group(2)},{match.group(3)})', match.group(3), True))
1383 ) # Finish this expression or collection.
1384 for match in re.finditer(r'\bprecision\s+(?:highp|mediump|lowp)\s+\w+\s*;', masked): # Process each match in this sequence.
1385 replacements.append((*match.span(), '')) # Apply append to these arguments.
1386 # GLSL array length queries return int even when the host resource API uses an unsigned size.
1387 for match in re.finditer(r'\.\s*length\s*\(\s*\)', masked): # Preserve native signed comparisons for both buffer ports and private arrays.
1388 if any(a <= match.start() < b for a, b in covered): # Replace resource declarations in their dedicated interface pass.
1389 continue # Leave declaration syntax to its existing interface conversion.
1390 first = primary_expression_start(masked, match.start()) # Locate the complete indexed, grouped or named-block receiver.
1391 replacements.append((first, first, 'static_cast<int>(')) # Open a signed cast around the existing swizzle and literal edits.
1392 replacements.append((match.end(), match.end(), ')')) # Close the cast after the complete zero-argument method call.
1393 # Convert native swizzles in the CPU adapter and retain GLSL swizzles in GPU code.
1394 for match in re.finditer(r'\.\s*([xyzw]{1,4}|[rgba]{1,4}|[stpq]{1,4})\b', masked): # Process each match in this sequence.
1395 pattern = match.group(1) # Compute pattern for the following operation.
1396 # Block array names may themselves be r, xy, rgba, etc. Only subsequent
1397 # vector member selections, such as state.xy[0].xy, are real swizzles.
1398 if (member_receiver(masked, match.start()), pattern) in named_arrays: # Select this branch when its condition holds.
1399 continue # Skip the remaining work for this iteration.
1400 if len(pattern) == 1 and pattern in 'xyzw': # Select this branch when its condition holds.
1401 continue # Skip the remaining work for this iteration.
1402 alphabet = ( # Compute alphabet for the following operation.
1403 'xyzw'
1404 if pattern[0] in 'xyzw' # Select this branch when its condition holds.
1405 else 'rgba' if pattern[0] in 'rgba' else 'stpq' # Supply else for the continued expression.
1406 ) # Finish this expression or collection.
1407 indices = ','.join(str(alphabet.index(c)) for c in pattern) # Compute indices for the following operation.
1408 replacements.append((*match.span(), '.template swizzle<' + indices + '>()')) # Apply append to these arguments.
1409 # Unsuffixed GLSL floating literals are float, whereas C++ would make them double.
1410 # Preserve scalar overload resolution and intermediate precision in the CPU adapter.
1411 floating = r'(?<![\w.])(?:(?:\d+\.\d*|\.\d+)(?:[eE][+-]?\d+)?|\d+[eE][+-]?\d+)[fF]?(?![\w.])' # Compute floating for the following operation.
1412 for match in re.finditer(floating, masked): # Process each match in this sequence.
1413 if match.group()[-1] not in 'fF': # Select this branch when its condition holds.
1414 replacements.append((*match.span(), match.group() + 'f')) # Apply append to these arguments.
1415 for match in re.finditer(r'\bnot\s*(?=\()', masked): # Process each match in this sequence.
1416 replacements.append((*match.span(), 'logicalNot')) # Apply append to these arguments.
1417 # Function parameter qualifiers have C++ reference equivalents.
1418 parameters = r'\b(inout|out|in)\s+(?:(?:highp|mediump|lowp)\s+)?(\w+)(?:\s*\[\s*([^]]+)\])?\s+(\w+)(?:\s*\[\s*([^]]+)\])?' # Compute parameters for the following operation.
1419 for match in re.finditer(parameters, masked): # Process each match in this sequence.
1420 if any(a <= match.start() < b for a, b in covered): # Select this branch when its condition holds.
1421 continue # Skip the remaining work for this iteration.
1422 qualifier, scalar, leading_size, variable, trailing_size = match.groups() # Apply groups to these arguments.
1423 if leading_size and trailing_size: # Select this branch when its condition holds.
1424 raise ShaderError('multidimensional helper arrays are unsupported') # Report the shader declaration validation error.
1425 size = leading_size or trailing_size # Compute size for the following operation.
1426 if size: # Select this branch when its condition holds.
1427 scalar = f'::shdr::glsl::array<{scalar},{resolve(size,"parameter array length")}>'
1428 replacements.append( # Apply append to these arguments.
1429 (*match.span(), scalar + (' ' if qualifier == 'in' else '& ') + variable) # Apply span to these arguments.
1430 ) # Finish this expression or collection.
1431 main = list(re.finditer(r'\bvoid\s+main\s*\(\s*(?:void\s*)?\)', masked)) # Compute main for the following operation.
1432 if len(main) != 1: # Select this branch when its condition holds.
1433 raise ShaderError('shader must define exactly one void main()') # Report the shader declaration validation error.
1434 replacements.append((*main[0].span(), 'SHDR_MAIN()')) # Apply append to these arguments.
1435 names = [port[0] for port in ports] # Compute names for the following operation.
1436 if len(names) != len(set(names)): # Select this branch when its condition holds.
1437 raise ShaderError('duplicate resource or uniform name') # Report the shader declaration validation error.
1438 for kind in ('buffer', 'image'): # Process each kind in this sequence.
1439 bindings = [port[2] for port in ports if port[1] == kind] # Compute bindings for the following operation.
1440 if len(bindings) != len(set(bindings)): # Select this branch when its condition holds.
1441 raise ShaderError('duplicate ' + kind + ' binding') # Report the shader declaration validation error.
1442 remaining = masked # Compute remaining for the following operation.
1443 for start, end in sorted(covered, reverse=True): # Process each start, end in this sequence.
1444 remaining = remaining[:start] + ' ' * (end - start) + remaining[end:] # Compute remaining for the following operation.
1445 if re.search(r'\b(?:layout|buffer|uniform)\b', remaining): # Select this branch when its condition holds.
1446 raise ShaderError( # Report the shader declaration validation error.
1447 'unsupported interface declaration; consult docs/portability.html'
1448 ) # Finish this expression or collection.
1449 cpu = gpu # Compute cpu for the following operation.
1450 previous = len(cpu) + 1 # Compute previous for the following operation.
1451 for start, end, replacement in sorted(replacements, reverse=True): # Process each start, end, replacement in this sequence.
1452 if end > previous: # Select this branch when its condition holds.
1453 raise ShaderError('overlapping shader declarations') # Report the shader declaration validation error.
1454 cpu = cpu[:start] + replacement + '\n' * gpu[start:end].count('\n') + cpu[end:] # Compute cpu for the following operation.
1455 previous = start # Compute previous for the following operation.
1456 # Use 32-bit CPU arithmetic for GLSL scalar precision declarations.
1457 cpu = re.sub(r'\b(?:highp|mediump|lowp)\b', '', cpu) # Compute cpu for the following operation.
1458 # Array types and constructors have direct C++11 value-wrapper equivalents.
1459 types = '|'.join(sorted(UNIFORMS, key=len, reverse=True)) # Compute types for the following operation.
1460 cpu = separate_array_declarators(cpu, types) # Compute cpu for the following operation.
1461 code = mask_noncode(cpu) # Compute code for the following operation.
1462 array_edits = [] # Compute array edits for the following operation.
1463 for match in re.finditer(r'\b(' + types + r')\s+(\w+)\s*\[\s*([^]]+)\s*\]', code): # Process each match in this sequence.
1464 count = integer( # Compute count for the following operation.
1465 match.group(3), # Apply group to these arguments.
1466 'private array length',
1467 constants=scoped_integer_constants(code, definitions, match.start()), # Compute constants for the following operation.
1468 ) # Finish this expression or collection.
1469 if not count: # Select this branch when its condition holds.
1470 raise ShaderError('private array length must be positive') # Report the shader declaration validation error.
1471 array_edits.append( # Apply append to these arguments.
1472 ( # Finish this expression or collection.
1473 *match.span(), # Apply span to these arguments.
1474 f'::shdr::glsl::array<{match.group(1)},{count}> {match.group(2)}',
1475 ) # Finish this expression or collection.
1476 ) # Finish this expression or collection.
1477 for match in re.finditer(r'\b(' + types + r')\s*\[\s*([^]]+)\s*\]', code): # Process each match in this sequence.
1478 count = integer( # Compute count for the following operation.
1479 match.group(2), # Apply group to these arguments.
1480 'array type length',
1481 constants=scoped_integer_constants(code, definitions, match.start()), # Compute constants for the following operation.
1482 ) # Finish this expression or collection.
1483 array_edits.append( # Apply append to these arguments.
1484 (*match.span(), f'::shdr::glsl::array<{match.group(1)},{count}>')
1485 ) # Finish this expression or collection.
1486 for match in re.finditer(r'\b(float|int|uint|bool)\s*(?=\()', code): # Process each match in this sequence.
1487 array_edits.append( # Apply append to these arguments.
1488 (*match.span(), f'::shdr::glsl::scalar_cast<{match.group(1)}>')
1489 ) # Finish this expression or collection.
1490 for start, end, replacement in sorted(array_edits, reverse=True): # Process each start, end, replacement in this sequence.
1491 cpu = cpu[:start] + replacement + cpu[end:] # Compute cpu for the following operation.
1492 # Keep directives meaningful to GCC's preprocessor; GLSL extension pragmas concern the GPU only.
1493 cpu = re.sub(r'^[ \t]*#\s*extension[^\n]*', '', cpu, flags=re.MULTILINE) # Compute cpu for the following operation.
1494 for start, end, replacement in sorted(gpu_edits, reverse=True): # Process each start, end, replacement in this sequence.
1495 gpu = gpu[:start] + replacement + gpu[end:] # Compute gpu for the following operation.
1496 gpu = portable_integer_mix(portable_gpu_promotions(portable_gpu_declarations(gpu))) # Compute gpu for the following operation.
1497 return cpu, gpu, local # Return this computed value to the caller.
1498
1499
1500def generate(source, name): # Emit the registered C++11 kernel and its embedded shader source.
1501 interfaces = [] if needs_invocation_frame(source) else None # Select fresh native private state only when the source needs it.
1502 cpu, gpu, local = transform(source, name, interfaces) # Translate the shader while marking frame interfaces when required.
1503 if interfaces is not None: # Give this interface-free shader its initialized private invocation state.
1504 cpu = invocation_frame(cpu, name, interfaces) # Recreate private globals for every CPU invocation after binding uniforms.
1505 delimiter = 'SHDR' + hashlib.sha256(gpu.encode()).hexdigest()[:10] # Compute delimiter for the following operation.
1506 return f'''// Generated from native GLSL .cpp; do not edit.
1507#include "shdr.h"
1508namespace {{
1509static const char shdr_embedded_source[] = R"{delimiter}({gpu}){delimiter}";
1510struct {name} : ::shdr::kernel {{
1511 {name}() : ::shdr::kernel("{name}",::shdr::extent({local[0]},{local[1]},{local[2]})) {{
1512 set_embedded_source(shdr_embedded_source);
1513 }}
1514 std::unique_ptr<::shdr::kernel> clone() const override {{
1515 std::unique_ptr<{name}> copy(new {name}); copy_interfaces(*copy);
1516 return std::unique_ptr<::shdr::kernel>(copy.release());
1517 }}
1518 using shader_uint = ::shdr::glsl::uint;
1519{cpu}
1520}};
1521::shdr::kernel* shdr_factory() {{ return new {name}; }}
1522::shdr::detail::shader_registration shdr_registration("{name}",shdr_factory);
1523}} // anonymous namespace
1524'''.replace(
1525 ' using shader_uint = ::shdr::glsl::uint;',
1526 ' // GLSL names are supplied by the header namespace.',
1527 ).replace( # Apply replace to these arguments.
1528 'namespace {\n', 'using namespace ::shdr::glsl;\nnamespace {\n', 1 # Supply namespace for the continued expression.
1529 ) # Finish this expression or collection.
1530
1531
1532def main(): # Read options and produce the requested generated adapter.
1533 parser = argparse.ArgumentParser(description=__doc__) # Compute parser for the following operation.
1534 parser.add_argument('--input', type=Path, required=True) # Apply add argument to these arguments.
1535 parser.add_argument('--output', type=Path, required=True) # Apply add argument to these arguments.
1536 parser.add_argument('--name', required=True) # Apply add argument to these arguments.
1537 args = parser.parse_args() # Compute args for the following operation.
1538 try: # Attempt the operation with explicit error handling below.
1539 output = generate(args.input.read_text(), args.name) # Compute output for the following operation.
1540 except (OSError, ShaderError) as error: # Convert this failure into the expected diagnostic.
1541 parser.exit(1, str(args.input) + ': ' + str(error) + '\n') # Apply exit to these arguments.
1542 args.output.parent.mkdir(parents=True, exist_ok=True) # Apply mkdir to these arguments.
1543 if not args.output.exists() or args.output.read_text() != output: # Avoid recompiling unchanged adapters when only the frontend implementation changed.
1544 args.output.write_text(output) # Update the generated file only when its complete emitted contents differ.
1545
1546
1547if __name__ == '__main__': # Select this branch when its condition holds.
1548 main() # Apply main to these arguments.