Varying
Tellusim Engine provides a simple and powerful interface for compute shaders through the GraphVarying system, enabling full control over compute scripting using *.varying
files.
These files support dynamic recompilation and hot-loading, allowing rapid iteration during development.
GraphVarying can be used for GPU particle systems, water simulations, object generation, procedural node placement, and more.
Tellusim Engine SDK includes numerous example *.varying
scripts.
Varying files use a GLSL-based syntax and define the sequence of compute shaders and GPU algorithms launched directly or indirectly. Compute shaders have full access to the GPU Scene Graph and can modify or generate any Scene properties.
Global Layouts
Varying configurations are defined using layout()
annotations.
Layout values support the following dynamic variables, which can be used in layout value expressions:
num_graph_nodes
- Number of Graph nodesnum_graph_lights
- Number of Graph NodeLight instancesnum_graph_cameras
- Number of Graph NodeCamera instancesnum_graph_objects
- Number of Graph NodeObject instancesnum_graph_instances
- Number of Graph NodeInstance instancesnum_graph_varyings
- Number of Graph NodeVarying instancesnum_graph_scripts
- Number of Graph NodeScript instancesnum_cloned_nodes
- Number of cloned nodes in GraphVaryingnum_released_nodes
- Number of released nodes in GraphVaryingnum_scene_graphs
- Number of Scene Graph instancesnum_scene_lights
- Number of Scene Light instancesnum_scene_cameras
- Number of Scene Camera instancesnum_scene_objects
- Number of Scene Object instancesnum_scene_materials
- Number of Scene Material instancesscene_frame
- The current Scene frame.- All named options - Corresponding option value
- All named uniforms - Corresponding uniform value
Initialization
The following layouts perform a reserve operation for the corresponding nodes during initialization.
This reduces unnecessary buffer reallocations as the buffer grows:
layout(nodes = 32);
layout(lights = 32);
layout(cameras = 32);
layout(objects = 32);
layout(instances = 32);
layout(varyings = 32);
layout(scripts = 32);
Options
Varying options control the shader preprocessor step by defining macros based on option values. They do not affect shader performance at runtime. Each option declaration defines a default value and can optionally specify a custom macro name.
Option variables can be used in layout expressions.
layout(macro = BOOL_OPTION) option bool bool_option = false;
layout(macro = UINT_OPTION) option uint uint_option = 0;
layout(macro = FLOAT_OPTION) option float float_option = 0.0f;
layout(macro = STRING_OPTION, name = Editor Name) option string string_option = "string";
The macro
argument is optional.
If omitted, the macro name defaults to the uppercased option name.
The name
argument specifies a custom display name for the editor interface.
Samplers
Required samplers can be declared using the following layouts:
// Full sampler declaration with all parameters
layout(sampler = generic_sampler, filter = point|linear|bipoint|bilinear|trilinear, wrap = clamp|repeat|mirror|border, lods = 16) sampler generic_sampler;
// Create a linear sampler with repeat wrap mode
layout(sampler = linear_sampler, filter = linear, wrap = repeat) sampler linear_sampler;
Textures
Required textures can be declared using the following layouts:
Varying textures can be imported from other GraphVarying instances, enabling powerful procedural workflows.
// Declare textures and load them from files
layout(texture = varying_2d_texture, name = 2D Texture) texture2D varying_2d_texture = "texture_2d.png";
layout(texture = varying_3d_texture, name = 3D Texture) texture3D varying_3d_texture = "texture_3d.image";
layout(texture = varying_cube_texture, name = Cube Texture) textureCube varying_cube_texture = "texture_cube.image";
// Create texture from a render noise texture
layout(texture = render_noise_texture) texture2DArray render_noise_texture;
// Import texture from other GraphVarying
layout(import = <Graph Name>:<Texture Name>) texture2D imported_texture;
The optional texture
argument defines the variable name used in shader source for the declared texture.
The name
, group
, and desc
arguments specify a custom display name and grouping for the editor interface.
Surfaces
Surfaces represent read-write textures for compute processing. They are useful for procedural materials and ping-pong compute operations.
Varying surfaces can be imported from or exported to other GraphVarying or Material instances.
// Full surface declaration with all parameters
layout(format = all_formats, width = 1, height = 1, depth = 1, layers = 1, mipmaps = 0|1) generic_surface;
// Create 1024x1024 RGf32 surface without mipmaps
layout(format = rgf32, width = 1024, height = 1024) surface2D image_surface;
// Import surface from other GraphVarying
layout(import = <Graph Name>:<Texture Name>) surface2D imported_surface;
// Create surface and export it to the other Material and MaterialShading
layout(format = rgbf32, width = 1, height = 1, export = <Material Name>:<Texture Name>, export = <Shading Material Name>:<Shading Texture Name>) surface2D exported_surface;
The optional surface
argument defines the variable name used in shader source for the declared surface.
Buffers
Buffers are critical for compute shader workloads.
They can be allocated from Scene buffers or created internally using user-defined structures or data types.
Several buffer types are used for GraphVarying management, including clone
, release
, and dispatch
buffers.
Varying buffers can be imported from other GraphVarying instances.
The assigned value defines the buffer size in bytes.
struct MyStruct {
ivec2 seed;
float spawn;
float padding;
};
// Create structured buffer
layout(buffer = struct_buffer) buffer MyStruct struct_buffer = 1024;
// Create uint4 buffer
layout(buffer = uvec4_buffer) buffer uvec4 uvec4_buffer = 1024;
// Allocate SceneBuffer
// Returns the index of the first allocated element
layout() buffer storage storage_buffer = 1024;
layout() buffer scalar scalar_buffer = 1024;
layout() buffer vertex vertex_buffer = 1024;
layout() buffer index index_buffer = 1024;
// Allocate GraphVarying buffers
// Returns the index of the first allocated element
layout() buffer clone create_buffer = 1024;
layout() buffer release release_buffer = 1024;
layout() buffer dispatch dispatch_buffer = 1024;
// Import buffer from other GraphVarying
layout(import = <Graph Name>:<Buffer Name>) buffer vertex imported_vertex_buffer = 1024;
layout(import = <Graph Name>:<Buffer Name>) buffer uint imported_uint_buffer = 1024;
The optional buffer
argument defines the variable name used in shader source for the declared buffer.
Uniforms
Uniforms are dynamic input parameters of GraphVarying and NodeVarying.
All declared uniform variables are available for the compute shaders. GraphVarying provides getters for children NodeVarying uniforms.
For each uniform variable with the node
argument, a get_<uniform_name>(uint node_index)
getter function is created.
Uniform variables can be used in layout expressions.
// Integer parameter with checkbox editor
layout(uniform = bool_integer, bool) int bool_integer = 1;
// Integer parameter with slider editor
layout(min = 1, max = 8) int signed_integer = 1 + 2;
// Floating-point parameter with maximum slider value
// This uniform is automatically created inside child NodeVarying instances.
layout(max = 2.0f, node) float linear_scalar = 2.0f * 3.0f;
// Logarithmic float parameter with 2-digit precision
layout(base = 2.0f, digits = 2) float log_scalar = 1.0f;
// Linear and sRGB color parameters
layout(color, linear) vec3 color_3 = vec3(0.1f);
layout(color) vec4 color_4 = vec4(0.1f, 0.1f, 0.1f, 1.0f);
// Vector parameters with uniform and non-uniform ranges
layout(digits = 2) vec2 vector_2 = vec2(0.0f);
layout(min = 1.0f, max = 2.0f) vec3 vector_3 = vec3(1.0f, 2.0f, 3.0f);
layout(min = 1.0f, max = 3.0f, max_w = 2.0f) vec4 vector_4 = vec4(1.0f, 2.0f, 3.0f, 4.0f) * 2.0f;
// Matrix parameters with default values
layout() mat2x3 matrix_3x2 = mat2x3::translate(1.0f, 2.0f);
layout() mat3x4 matrix_4x3 = mat3x4::scale(1.0f, 2.0f, 3.0f) * mat3x4::rotateX(90.0f);
layout(spacer) mat4 matrix_4x4 = mat4::perspective(60.0f, 1.0f, 0.1f);
The optional uniform
argument defines the variable name used in shader source for the declared uniform.
The node
argument automatically creates the corresponding uniform variable inside NodeVarying children.
min
, max
, bool
, color
, linear
, name
, desc
, and spacer
layout arguments are used as editor hints.
Numerical values are resolved using the Expression namespace.
The row-major Matrix2x3f type corresponds to mat2x3
in GLSL.
The row-major Matrix4x3f type corresponds to mat3x4
in GLSL.
GraphVarying Passes
The following GraphVarying passes are supported:
init
- Initialization compute shader, runs only onceupdate
- Update compute shader, run on each dispatchscan
- PrefixScan algorithmsort
- RadixSort algorithmtree
- SpatialTree generation algorithmgrid
- SpatialGrid generation algorithmfft
- Fourier Transformation
Common layout attributes for all passes:
// Optional pass name, useful for debugging
layout(name = Pass Name);
// Enable or disable the pass
// The value is evaluated dynamically using variables, options, and uniform values
layout(enable = num_graph_nodes > 0);
// Dispatch the pass indirectly using DispatchIndirect arguments at the beginning of the buffer
layout(dispatch = buffer_name);
// Dispatch the pass indirectly using DispatchIndirect arguments at the specified buffer offset
layout(dispatch = buffer_name + 1024);
Compute Passes
The init
and update
passes are standard GLSL compute shaders.
All resources used by the shader must be explicitly declared via layout attributes.
Shaders can be dispatched directly or indirectly.
init|update {
// Local thread group size
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1);
// Global dispatch size
layout(dispatch_x = num_graph_nodes, dispatch_y = 1, dispatch_z = 1);
// Explicitly declare required named resources
layout(sampler_name = 0 or 1);
layout(texture_name = 0 or 1);
layout(buffer_name = 0 or 1);
// Explicitly declare required Scene and GraphVarying buffers
layout(storage_buffer = 0 or 1);
layout(scalar_buffer = 0 or 1);
layout(vertex_buffer = 0 or 1);
layout(index_buffer = 0 or 1);
layout(tracer_buffer = 0 or 1);
layout(collider_buffer = 0 or 1);
// Clear named buffer before dispatch
layout(clear = buffer_name);
// Clone nodes from buffer after dispatch
layout(clone = clone_buffer_name);
// Release nodes from buffer after dispatch
layout(release = release_buffer_name);
// Update GraphVarying root node transformations after dispatch
layout(update = transforms);
// Update GraphVarying spatial trees after dispatch
layout(update = light_tree);
layout(update = object_tree);
layout(update = instance_tree);
// Update all spatial trees after dispatch
layout(update = spatial);
// Shared memory variables
shared {
uint shader_counter;
uint shared_data[64];
}
// Compute kernel
compute {
}
}
Algorithm Passes
scan
, sort
, tree
, and grid
passes dispatch the corresponding GPU algorithms.
These passes can be dispatched directly or indirectly.
scan|sort|tree|grid {
// Target buffer name
layout(buffer = target_buffer_name);
// Target buffer with offset
layout(buffer = target_buffer_name + 1024);
// Number of elements to process
layout(size = num_graph_nodes);
// Offset to the data within the buffer (only required for sort)
layout(data = 1024);
// Bit count used for sorting (optional for sort and grid)
layout(bits = 32);
}
FourierTransform Pass
The fft
pass performs a Fourier transform on a source texture and outputs to a target surface.
The dimensions of the resources must be valid for the FourierTransform class.
fft {
// Source texture
layout(texture = texture_name);
// Target surface
layout(surface = surface_name);
// FourierTransform mode
layout(mode = rf16i|rf32i|rgf16i|rgf32i|rgbf16c|rgbf32c|rgbf16p|rgbf21c);
// FourierTransform operation
layout(op = fcc|bcc|frc|bcr);
}