Skip to main content

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.

info

GraphVarying can be used for GPU particle systems, water simulations, object generation, procedural node placement, and more.

info

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 nodes
  • num_graph_lights - Number of Graph NodeLight instances
  • num_graph_cameras - Number of Graph NodeCamera instances
  • num_graph_objects - Number of Graph NodeObject instances
  • num_graph_instances - Number of Graph NodeInstance instances
  • num_graph_varyings - Number of Graph NodeVarying instances
  • num_graph_scripts - Number of Graph NodeScript instances
  • num_cloned_nodes - Number of cloned nodes in GraphVarying
  • num_released_nodes - Number of released nodes in GraphVarying
  • num_scene_graphs - Number of Scene Graph instances
  • num_scene_lights - Number of Scene Light instances
  • num_scene_cameras - Number of Scene Camera instances
  • num_scene_objects - Number of Scene Object instances
  • num_scene_materials - Number of Scene Material instances
  • scene_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.

info

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";
info

The macro argument is optional. If omitted, the macro name defaults to the uppercased option name.

info

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:

info

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;
info

The optional texture argument defines the variable name used in shader source for the declared texture.

info

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.

info

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;
info

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.

info

Varying buffers can be imported from other GraphVarying instances.

info

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;
info

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.

info

For each uniform variable with the node argument, a get_<uniform_name>(uint node_index) getter function is created.

info

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);
info

The optional uniform argument defines the variable name used in shader source for the declared uniform.

info

The node argument automatically creates the corresponding uniform variable inside NodeVarying children.

info

min, max, bool, color, linear, name, desc, and spacer layout arguments are used as editor hints.

info

Numerical values are resolved using the Expression namespace.

note

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:

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.

info

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.

info

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.

note

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);
}