Emitter
The emitter generates final GLSL code for each shader stage.
Entry Point
std::string OpenGLGLSLEmitter::emit(
const Program &program,
StageKind stage,
const std::unordered_set<std::string> &used_uniforms
);Called once per stage. used_uniforms is the set of uniform names that the linker determined are referenced in this stage.
Output Order
For each stage, the emitter writes sections in this order:
- Version header:
#version 450 core+ blank line - Global declarations:
StructDecl→struct Name { ... };FuncDecl→ full function with bodyVarDecl(withis_const=true) →const type name = expr;
- Global uniforms/buffers that satisfy both:
- The uniform name appears in
used_uniforms - The uniform is not shadowed by a stage-local declaration
- The uniform name appears in
- Stage items (in order of appearance):
InterfaceBlock→in/out BlockName { fields } instance;InterfaceRef→ resolved from globalInterfaceDeclUniformDecl→layout(...) uniform type name;BufferDecl→layout(...) buffer BlockName { fields } instance;StructDecl,FuncDecl→ same as globals
Dead Code Elimination
Layout Qualifier Generation
layout_quals(const Annotations &) accumulates qualifier fragments from the annotation list in order:
| Annotation | Fragment |
|---|---|
@location(N) | location = N |
@binding(N) | binding = N |
@std430 | std430 |
@std140 | std140 |
Fragments are joined with , and wrapped in layout(...).
Example:
@std430 @binding(2)
buffer Data { float values[]; } data;Emits:
layout(std430, binding = 2) buffer Data {
float values[];
} data;If no qualifying annotations are present, no layout prefix is emitted.
Uniform Shadowing
A global uniform (or buffer block) is shadowed when the target stage contains its own declaration with the same name.
When a stage shadow is detected:
- The global version is suppressed from that stage's output
- The stage-local version is emitted instead
This allows per-stage overrides of globally declared uniforms.
Example
uniform float global_value = 1.0;
@stage vertex {
uniform float global_value = 2.0; // shadows global
void main() {
// uses 2.0 in vertex stage
}
}
@stage fragment {
void main() {
// uses 1.0 in fragment stage
}
}Vertex output:
uniform float global_value = 2.0; // stage-localFragment output:
uniform float global_value = 1.0; // globalShadowing is checked by is_shadowed(name, stage, program), which scans the stage's item list for a UniformDecl or BufferDecl with the matching name.
Interface Resolution
When a stage contains in Name inst or out Name inst (an InterfaceRef node), the emitter:
- Calls
find_interface(program, name)to look up the correspondingInterfaceDecl - Emits the block with:
- The
InterfaceDecl's field list - The original
is_inflag (invsout) - The instance name from the
InterfaceRef
- The
If no matching InterfaceDecl is found, the emitter silently emits nothing (the downstream GLSL compiler will report the error).
Example
interface Varyings {
vec2 v_uv;
vec3 v_normal;
};
@stage vertex {
out Varyings v;
}Emits:
out Varyings {
vec2 v_uv;
vec3 v_normal;
} v;Parenthesization
emit_expr_paren(id) adds parentheses only when the sub-expression is a BinaryExpr or TernaryExpr.
Other expression types (identifiers, calls, field access, subscripts) are emitted without wrapping parentheses.
This produces clean output while ensuring binary operators nested inside other binary expressions are fully parenthesized.
Example
a + b * cEmits:
a + (b * c)func(a + b)Emits:
func((a + b))Float Literal Format
Float values are emitted via snprintf with the %g format specifier.
%g removes trailing zeros and the trailing decimal point when no fractional part is present, producing compact output:
1.0→13.14159→3.141590.5→0.5
TIP
While GLSL technically requires 1.0 for float literals, most drivers accept 1. or 1 when the context is clear. AXSLC uses %g for readability.
Dead Code Elimination
The emitter only includes global uniforms in a stage's output if:
- The uniform name appears in
used_uniforms(computed by the linker) - The uniform is not shadowed by a stage-local uniform
This prevents:
- Unused uniform warnings from the GLSL compiler
- Unnecessary uniform slots in the compiled shader
- Reduced shader binary size
Example
uniform float u_vertex_only; // only used in vertex
uniform float u_fragment_only; // only used in fragment
@stage vertex {
void main() {
float x = u_vertex_only;
}
}
@stage fragment {
void main() {
float y = u_fragment_only;
}
}Vertex output:
uniform float u_vertex_only; // presentFragment output:
uniform float u_fragment_only; // presentEach stage only includes the uniforms it actually uses.
Complete Output Example
Input (AXSL):
@version 450;
struct Material {
vec3 albedo;
};
const float PI = 3.14159;
uniform mat4 u_mvp; // used in vertex only
@stage vertex {
in Attributes {
@location(0) vec3 a_pos;
} a;
void main() {
gl_Position = u_mvp * vec4(a.a_pos, 1.0);
}
}Output (out.vert.glsl):
#version 450 core
struct Material {
vec3 albedo;
};
const float PI = 3.14159;
uniform mat4 u_mvp;
in Attributes {
layout(location = 0) vec3 a_pos;
} a;
void main() {
gl_Position = (u_mvp * vec4(a.a_pos, 1));
}Note:
- Version header is first
- Global struct and constant are included
- Global uniform
u_mvpis included (used in main) - Stage-local
inblock is emitted with location qualifiers - Float literal
1.0is emitted as1(using%g)