Skip to content

generator

FreeBodyEngine.graphics.gl33.generator #

IMPLEMENTATIONS = {'sample': {'kind': 'function', 'source': f'vec4 sample(sampler2DArray tex_array, int index, vec2 texcoords, vec4 uv_rect_array[{MAX_TEXTURE_STACK_SIZE}]) {vec3 _BUILTIN_FUNC_uv = vec3(uv_rect_array[index].xy + texcoords * uv_rect_array[index].zw, float(index));return texture(tex_array, _BUILTIN_FUNC_uv);}vec4 sample(sampler2D tex, vec2 texcoords, vec4 uv_rect) {vec2 _BUILTIN_FUNC_uv = uv_rect.xy + texcoords * uv_rect.zw;return texture(tex, _BUILTIN_FUNC_uv);}', 'call': {'$args[0].type$==texture': 'sample($args[0]$, $args[1]$, _ENGINE_$args[0]$_uv_rect)', '$args[0].type$==textureStack': 'sample($args[0]$, $args[1]$, $args[2]$, _ENGINE_$args[0]$_uv_rect)'}}, 'VERTEX_POSITION': {'kind': 'variable', 'replace': 'gl_Position'}, 'INSTANCE_ID': {'kind': 'variable', 'replace': 'gl_InstanceID'}, 'VERTEX_INDEX': {'kind': 'variable', 'replace': 'gl_VertexID'}, 'TIME': {'kind': 'uniform', 'source': 'uniform float TIME;\n'}, 'texture': {'kind': 'type', 'replace': 'sampler2D'}, 'image': {'kind': 'type', 'replace': 'sampler2D'}, 'image_load': {'kind': 'function', 'call': {'': 'texelFetch($args[0]$, $args[1]$, 0)'}}, 'textureStack': {'kind': 'type', 'replace': 'sampler2DArray'}, 'round': {'kind': 'function', 'call': {'': 'int(round($args[0]$))'}}, 'EmitVertex': {'kind': 'function', 'call': {'': 'EmitVertex()'}}, 'EndPrimitive': {'kind': 'function', 'call': {'': 'EndPrimitive()'}}, 'input_position': {'kind': 'function', 'call': {'': 'gl_in[$args[0]$].gl_Position'}}, 'DISPATCH_SIZE': {'kind': 'uniform', 'source': 'uniform ivec3 DISPATCH_SIZE;\n'}, 'NUM_WORKGROUPS': {'kind': 'uniform', 'source': 'uniform ivec3 NUM_WORKGROUPS;\n'}} module-attribute #

GL33Generator(tree, shader_type=fbusl.ShaderType.FRAGMENT) #

Bases: Generator

Emits GLSL 330 core. Compute/raytrace shaders are emulated as a fullscreen fragment-shader pass (see FreeBodyEngine.graphics.gl33.compute) rather than real GL compute shaders - GL 3.3 has neither. CAPABILITIES documents exactly what that emulation can and can't do; any FBUSL construct outside this set raises a clear error here rather than producing GLSL that looks plausible but doesn't actually work.

Sets up per-instance codegen state: input/output location counters (for layout(location=...)), the shared IMPLEMENTATIONS lookup table, and this shader's buffer blocks/struct defs indexed by name (used later by generate_buffer_block()/_generate_buffer_field_access() to resolve Block.field[index] accesses).

CAPABILITIES = frozenset({'compute.dispatch', 'compute.invocation_id', 'compute.buffer_read', 'compute.image_read', 'geometry.native', 'raytrace.query_emulated'}) class-attribute instance-attribute #

builtins = fbusl.builtins.BUILTINS instance-attribute #

implementations = IMPLEMENTATIONS instance-attribute #

input_index = 0 instance-attribute #

output_index = 0 instance-attribute #

tree = tree instance-attribute #

format_var(name, type_annotation, qualifier='') #

Formats one <type> <name>[array-suffix]; field/parameter declaration (optionally prefixed with a storage qualifier) - shared by generate_struct() (struct fields) and generate_function() (parameters, which strip the trailing ; back off since a parameter list isn't semicolon-terminated).

generate() #

Emits the full GLSL 330 source for self.tree: the version/ extension header (plus a geometry-stage layout(...) line, and the raytrace ray-intrinsics if this shader is a raytrace stage), the IMPLEMENTATIONS' function/uniform injections, then one generated line per top-level AST node.

generate_array_access(node) #

Generates an indexing expression base[index], special-casing Block.field[index] on a registered buffer block (see _generate_buffer_field_access) since that isn't a real GLSL struct/ array access at the FBUSL level.

generate_binop(node) #

Generates a binary expression left OP right, unconditionally parenthesizing any operand that is itself a BinOp. The FBUSL AST already encodes precedence/grouping via tree shape, not via preserved parens, so flattening a nested BinOp without adding its own parens would let GLSL's own precedence table re-parse the flattened text differently than intended whenever a lower-precedence op is nested inside a higher-precedence one (see the inline comment below for a worked example).

generate_buffer_block(node) #

Generates the uniform samplerBuffer declarations (and, for struct-typed fields, the matching _load_<struct>_<block>_<field>() loader function) backing one BufferBlock's fields - GL33 has no real SSBOs, so every buffer field is read back via texelFetch on a buffer texture instead. Requires "compute.buffer_write" if the block isn't declared readonly, since this emulation can only ever read these buffers, never write them.

generate_define(node) #

Generates a #define NAME value preprocessor directive.

generate_function(node) #

Generates a full function definition: signature (via format_var() for each parameter, with the trailing ; it adds for a field declaration stripped back off) plus a body where every statement is re-terminated with exactly one ; regardless of what generate_node() happened to already append.

generate_function_call(node) #

Generates a function call, first checking whether node.name is a require()-gated builtin (raising if this backend lacks the needed capability), then whether IMPLEMENTATIONS has a "function" lowering for it. A lowering's "call" dict maps either the unconditional key "" (always substitute the $args[N]$ template) or an $args[N].type$==<typename> condition, used to overload-dispatch a single FBUSL call (e.g. sample()) onto different GLSL call shapes depending on one argument's resolved type (a texture vs. a textureStack). With no matching lowering, the call passes through unchanged as name(args...).

generate_identifier(node) #

Generates an identifier reference, lowering the three compute "invocation id" builtins to GL33's fullscreen-pass emulation of them (there's no real gl_GlobalInvocationID/gl_WorkGroupID/ gl_LocalInvocationID under this backend - see the module docstring - so they're derived from gl_FragCoord and the entry stage's local_size instead of coming from IMPLEMENTATIONS like everything else). Any other identifier falls through to the IMPLEMENTATIONS variable/type "replace" table, or is emitted unchanged if it isn't one of those either.

generate_if_statement(node) #

Generates an if/else if/else chain, recursively generating node.next_statement (an elif/else link) to build the whole chain from a single if node.

generate_inline_if(node) #

Generates a ternary expression cond ? then : else - GLSL's ternary operator has the same shape as FBUSL's inline-if, so this is a direct textual translation with no lowering needed.

generate_inout(node) #

Generates an in/out/uniform declaration. Inputs/outputs get an auto-incrementing layout(location=...) (tracked across the whole shader via self.input_index/self.output_index, so field order in the FBUSL source determines location assignment); a texture/textureStack uniform additionally emits a matching _ENGINE_<name>_uv_rect[...] uniform for the sub-rect metadata the sample() builtin needs (see IMPLEMENTATIONS["sample"] and generate_inout's _ENGINE_..._uv_rect uniforms it reads). A geometry-stage input is forced into an unsized GLSL array on top of its own type, since geometry shaders receive one value per input- primitive vertex for every @input field regardless of its FBUSL type.

generate_literal(node) #

Generates a scalar literal's GLSL text form. Bools are spelled out as true/false rather than Python's True/False, which GLSL doesn't recognize; everything else is just str() of the coerced Python value.

generate_member_access(node) #

Generates a struct/vector field access base.member (also used for swizzles, since FBUSL doesn't distinguish the two at this level).

generate_node(node) #

Dispatches node to the matching generate_* method based on its AST node type - the single entry point every codegen method (including this one, recursively) goes through to turn a sub-tree into GLSL text. A plain str node is passed through unchanged; any other node type with no lowering falls through to "".

generate_return(node) #

Generates a return statement, bare if node.expression is None.

generate_setter(node) #

Generates a plain assignment left = right (no trailing semicolon - callers append their own statement terminator).

generate_shared_decl(node) #

Generates a shared compute-shader variable declaration, after checking this backend actually has the "compute.shared_memory" capability.

generate_struct(node) #

Generates a struct Name { ... }; declaration, one field per line via format_var().

generate_unary_op(node) #

Generates a unary expression (e.g. -x, !flag), parenthesizing the operand if it's itself a binary expression.

generate_vardecl(node) #

Generates a local variable declaration with its initializer, e.g. vec3 foo = ...;.

generate_while(node) #

Generates a while loop, recursively generating each statement in its body.

get_glsl_type(type_annotation) #

Returns just the base GLSL type name for type_annotation, discarding any array suffix resolve_type() would also produce.

get_type_name(type_annotation) #

Returns the original type name, either directly or from a type dict.

inject_implementation(source, implementations) #

Appends every function/uniform IMPLEMENTATIONS entry's own GLSL source (e.g. sample()'s helper function, or the TIME uniform declaration) to source. Entries with no GLSL of their own (plain type/variable renames, or a "call" template with no "source") are skipped.

resolve_type(type_annotation) #

Resolves an FBUSL type annotation (a plain type name, or a dict for an array type) to a (base_type, array_suffix) pair of GLSL text, applying IMPLEMENTATIONS' "type" renames (e.g. texture -> sampler2D) along the way. An array annotation recurses into its element type and appends its own [length] onto whatever suffix that produced, so nested arrays stack correctly.