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.