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.