This page documents how VisualShader traverses its node graph to produce a GLSL source string, and how ShaderCompiler processes that string into backend-ready output. For documentation on the ShaderLanguage parser and general shader compilation mechanics, see 7.4. For the VisualShaderNode class hierarchy and port types, see 12.2. For the VisualShader resource architecture and graph structure, see 12.1.
The code generation process has two distinct phases. First, VisualShader walks its internal node graph and assembles a GLSL text string. Second, that string is fed to ShaderCompiler, which parses it and emits backend-specific output consumed by the rendering pipeline.
End-to-End Shader Code Generation Pipeline
Sources: scene/resources/visual_shader.cpp41-44 scene/resources/visual_shader.h163-165 servers/rendering/shader_compiler.cpp servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp41-100
VisualShader::get_code() is called by the engine whenever the shader needs to be compiled. It iterates over all Type stages (vertex, fragment, light, etc.) and for each stage calls _write_node() starting from the output node.
The shader type stages are:
VisualShader::Type enum | Suffix used in variable IDs | Shader stage |
|---|---|---|
TYPE_VERTEX | vtx | Vertex |
TYPE_FRAGMENT | frg | Fragment |
TYPE_LIGHT | lgt | Light |
TYPE_START | start | Particle start |
TYPE_PROCESS | process | Particle process |
TYPE_COLLIDE | collide | Particle collide |
TYPE_SKY | sky | Sky |
TYPE_FOG | fog | Fog |
Sources: scene/resources/visual_shader.cpp41-44 scene/resources/visual_shader.h46-59
_write_node() — Graph Traversal_write_node() is the recursive heart of code generation. Its full signature is:
Error _write_node(
Type p_type,
StringBuilder *p_global_code,
StringBuilder *p_global_code_per_node,
HashMap<Type, StringBuilder> *p_global_code_per_func,
StringBuilder *p_code,
Vector<DefaultTextureParam> &r_def_tex_params,
const VMap<ConnectionKey, const List<Connection>::Element *> &p_input_connections,
const VMap<ConnectionKey, const List<Connection>::Element *> &p_output_connections,
int p_node,
HashSet<int> &p_processed,
bool p_for_preview,
HashSet<StringName> &r_classes
) const;
The traversal is depth-first. Before processing a node, it recursively processes all nodes feeding into its input ports. The p_processed HashSet<int> guards against re-visiting nodes that appear in multiple connection paths, ensuring each node contributes its code exactly once.
_write_node() Traversal Logic
Sources: scene/resources/visual_shader.h163-165 scene/resources/visual_shader.cpp46-382
make_unique_id()Every value passed between nodes is a GLSL local variable. The name for each variable is produced by the free function make_unique_id():
A float output named "x" on node 7 in the fragment graph produces the variable name x_frg_7. This makes intermediate variables per-stage and per-node unique, preventing name collisions in the assembled GLSL source.
Sources: scene/resources/visual_shader.cpp41-44
Each VisualShaderNode implements up to four code generation methods. _write_node() calls them in order.
| Method | Output target | Called once per… | Typical use |
|---|---|---|---|
generate_global() | p_global_code | node per stage | uniform declarations |
generate_global_per_node() | p_global_code_per_node | node type across all stages | helper functions, custom GLSL |
generate_global_per_func() | p_global_code_per_func[type] | node per function | function-level preamble |
generate_code() | p_code | node per stage | inline expression |
Node Code Generation Methods and Their Destinations
Sources: scene/resources/visual_shader.cpp372-382 scene/resources/visual_shader.h163-165
generate_code() Signaturep_input_vars[i] is the GLSL variable name holding the value arriving on input port i. If a port is unconnected, its corresponding entry is empty or a default constant string.
p_output_vars[i] is the GLSL variable name into which the node must write its output for port i. The variable is pre-declared by _write_node() before generate_code() is called.
VisualShaderNodeFloatConstant::generate_code() illustrates the simplest case — a node with no inputs that writes a constant to one output:
" " + p_output_vars[0] + " = " + vformat("%.6f", constant) + ";\n"
For a constant of 1.0, this emits: x_frg_7 = 1.000000;
VisualShaderNodeIntConstant::generate_code():
" " + p_output_vars[0] + " = " + itos(constant) + ";\n"
VisualShaderNodeUIntConstant::generate_code():
" " + p_output_vars[0] + " = " + itos(constant) + "u;\n"
Sources: scene/resources/visual_shader_nodes.cpp136-138 scene/resources/visual_shader_nodes.cpp198-200 scene/resources/visual_shader_nodes.cpp260-262
VisualShaderNodeCustom Code GenerationFor user-defined nodes, VisualShaderNodeCustom::generate_code() calls the GDScript/C++ virtual _get_code() and wraps the returned string in an indented block:
generate_global_per_node() calls _get_global_code() and generate_global_per_func() calls _get_func_code(), giving custom nodes access to all four code sections.
Sources: scene/resources/visual_shader.cpp616-681
After _write_node() finishes for all stages, get_code() concatenates the StringBuilders in order to produce the complete GLSL source text fed to the rendering server:
shader_type spatial; // or canvas_item, particles, sky, fog
render_mode ...;
// --- p_global_code (uniforms, structs from all nodes) ---
uniform sampler2D texture_0 : hint_default_white;
...
// --- p_global_code_per_node (helper functions, deduplicated) ---
// CustomNode helper
float my_helper(float x) { ... }
// --- per function: fragment { ---
// p_global_code_per_func["frg"]
void fragment() {
// p_code for TYPE_FRAGMENT
float x_frg_7 = 1.000000;
vec3 n_frg_3 = normalize(vec3(...));
ALBEDO = ...;
}
Sources: scene/resources/visual_shader.h131-132 scene/resources/visual_shader.cpp41-44
ShaderCompilerShaderCompiler is defined in servers/rendering/shader_compiler.cpp and servers/rendering/shader_compiler.h It takes the assembled GLSL string, parses it through ShaderLanguage::compile() to build an AST, then walks that AST using IdentifierActions provided by the render backend.
The ShaderCompiler::compile() signature:
Sources: servers/rendering/shader_compiler.cpp
IdentifierActionsEach render backend (or material shader type) configures an IdentifierActions struct to:
From SceneShaderForwardClustered::ShaderData::set_code():
When ShaderCompiler walks the AST and encounters an identifier matching a key in usage_flag_pointers, it sets the corresponding bool* to true. This allows the backend to know which optional shader features are active without inspecting the source text directly.
Sources: servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp94-180
GeneratedCodeThe output of ShaderCompiler::compile() is populated into ShaderCompiler::GeneratedCode:
| Field | Contents |
|---|---|
vertex_global | Global declarations for the vertex stage |
vertex | Vertex function body |
fragment_global | Global declarations for the fragment stage |
fragment | Fragment function body |
uniforms | List of ShaderLanguage::ShaderNode::Uniform structs |
texture_uniforms | Texture samplers with hints and filters |
defines | Active #define strings |
The backend embeds these strings into its GLSL template (e.g., scene_forward_clustered.glsl) using placeholder markers like #GLOBALS and #MATERIAL_UNIFORMS.
Sources: servers/rendering/shader_compiler.cpp servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl122-126
ShaderCompiler Data Flow by Backend
Sources: servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp41-100 servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp drivers/gles3/storage/material_storage.cpp servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl120-126
The backend's base .glsl files contain placeholder tokens that the render pipeline replaces with the GeneratedCode content before submitting to the GPU driver:
| Placeholder | Replaced by |
|---|---|
#GLOBALS | gen_code.vertex_global or gen_code.fragment_global |
#MATERIAL_UNIFORMS | Uniform block members from gen_code.uniforms |
#CODE | The stage-specific function body |
Sources: servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl122-126 servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl128-132
When a connection links ports of differing types (e.g., PORT_TYPE_VECTOR_3D → PORT_TYPE_SCALAR), _write_node() inserts an implicit conversion in the p_input_vars string. For example, connecting a vec3 output to a float input generates a swizzle like n_frg_5.x as the input variable string rather than n_frg_5 directly. This means generate_code() implementations always receive correctly-typed strings in p_input_vars and do not need to handle mismatched types themselves.
Sources: scene/resources/visual_shader.cpp264-330 scene/resources/visual_shader.h73-90
Refresh this wiki
This wiki was recently refreshed. Please wait 3 days to refresh again.