Atlas Logo

Atlas

Introduction

This document describes the syntax and semantics of the Atlas programming language. Atlas is a real-time visualization language focusing on ease of GPU utilization. The paradigm used by Atlas is a stack of tensors. This offers advantages relating to memory management, making copying, slicing, reversing, and transposing all constant time operations. The workhorse of Atlas is the c (compute) statements, which allow the definition of arbitrary GLSL functions, multiple return values, and parameters of different sizes. Compute statements always compute on the accelerator, and consequently should be restricted to large tensors.

Basic Concepts

Atlas code is stack-oriented and uses a simple command-oriented syntax. Comments start with // and extend to the end of the line. Whitespace and indentation are primarily for readability and do not affect the program's semantics. All commands are terminated with semicolons (;). Labels are declared with l and quotes such as l'aLabel'. The core point of this language is to make it easier to program the GPU. Most commands consume their arguments; for example the sequence 1;if'go'; would push a 1 onto the stack, and then immediately consume it as a truth value and branch to the label 'go'.

Atlas runs natively on Windows 10 and 11, and also on the web using emscripten.

Program Model

The program model used by Atlas is a loop. At the end of the program running, the tensor on top of the stack is used as the RGB 3-channel display tensor, filling the window with that tensor, and then the program restarts from the beginning. The examples and documentation below will make this clearer.

Invocation

Atlas takes one parameter as an argument: the file to be run. All other parameters will be pushed as strings onto the stack. If not given a filename, Atlas will run "main.atl" if present, or print a brief usage message if not. To invoke through a browser copy Atlas.html, Atlas.js, Atlas.wasm and Atlas.data to a served directory. A file named filelist.txt must also be present in the directory, which is a list of files to preload onto the filesystem. The list will include a main.atl and any supporting files. See the docs directory for a concrete example.

Gotchas

When setting variables, the versions which take a number and set a uniform *write to different locations* than the non-uniform versions. Hence it's possible to reset a variable to zero by forgetting to include the numerical argument on a set command. GLSL names are mangled by replacing '.' with '_'. Because of this, and because OpenGL reserves variables containing __, you must not start glsl variable names with an underscore if in a workspace.

Syntactic Sugar

Syntactic sugar exists for getting and setting variables: get'foo' can be shortened to just foo, set'foo' can be shortened to just foo= and set'foo' N can likewise be shortened to foo= N.


Data Types

All data in Atlas is floating point. This means that linear indexing only works out to about 16 million (24 bits), and the effective maximum dimension size (but not total size) of a tensor is 16 million. The total number of elements may not exceed 2^32 (4 gigaelements) or addressing will fail. All other types of data (e.g. strings) are converted into a tensor of floating point values. Strings use backslash as the escape character so that '\'' is a string with a single quote and '\\' is a single backslash etc.

Tensors

Tensors are central to Atlas, as they are the only data structure built into the language. A tensor is a generalized multi-dimensional array. For example, a vector is a 1D tensor, a matrix is a 2D tensor, and dimensions up to 4 are also supported.

Variables

Variables are set with a set command like set'varName' 2 with varName a valid GLSL identifier. Variables set this way will be available as uniforms in shaders, as well as through get'varName' commands.

Control Structures

Atlas uses labels and if statements for control flow. These two structures alone are sufficient to reconstruct the rest. For example 1;if'label'; does an unconditional branch to l'label' located elsewhere in the code.

Functions

Functions are just labels in Atlas followed by a return command. For example, to add the top two elements on the stack:

l'add'; // Add the top two tensors element-wise
+;return;

and then later in the code this function is called by name with just add.

Looping

Atlas programs are run in a loop, once per each frame of animation. Consequently, your program should be designed to pass through rather than block. To quit the program, you can manually call quit. At the end of each loop, a 4 channel rank 3 tensor must be on top of the stack, this is used as the image to display.


Demos

A set of more full fledged demos can be seen in the distributed main.atl here.

Fibonacci Sequence

0;1; // Push the first two elements of the sequence.
l'fib';
1;dup;1;dup;+; // Duplicate the top two elements and then add them.
0;dup;21;-;if'fib'; // Loop back to l'fib' if the top of the stack isn't 21.
print;quit; // Show the results.
prints
CPU tensor 8
shape:
strides:
21.00
CPU tensor 7
shape:
strides:
13.00
... (truncated for brevity)
CPU tensor 0
shape:
strides:
0.00

Simple Gradient

size;if'skip'; // Skip if we've already created the gradient.
6;[16 16 3];c''''
ret[0]=i / 767.0;' 0 1 0 0; // The linear index goes to 16*16*3 - 1 = 767.0.
l'skip';
will display
gradient example


additive

This command makes it so that subsequent c compute commands are done in additive mode. Additive mode means that if two triangles coincide, they both draw, and add together the results.

+ / - / * / / / % / ^ / sin / cos / floor / ceil / log / > / ==

These arithmetic commands take one or two arguments and do one of add, subtract, multiply, divide, modulus, exponentiate, sin, cosine, floor, ceiling, logarithm, compare greater than, or compare equals them, respectively. The comparison operators produce 1.0 for yes and 0.0 for no. The argument on the top of the stack is the subtrahend, denominator, or exponent, while the tensor below is the minuend, numerator, or base. These operations are done on the CPU and therefore should only be done on small tensors. Large tensor computation should be done with the c (compute) command. For example:

[1 2 3];[0.5 4 0.1];*;print;quit;
will print
CPU tensor 0
shape: 3
strides: 1
+----------------+
| 0.50 8.00 0.30 |
+----------------+

b (bury)

This command takes one scalar integer argument, and then buries the top tensor that deep into the stack. For example 1;2;3;4;1;bury;print;quit; would print

CPU tensor 3 ...
This command necessitates a deep copy if the tensor to bury is a copy or slice of some other tensor.

backface

This command toggles backface culling for triangles generated by the vertex shader.

c (compute)

The c command is the core compute operation in Atlas. It lets you execute custom GLSL code on the GPU to produce an output tensor (or texture) from one or more input tensors.

Syntax

c'vertexShaderFuncs'optionalVertexShader'glslFuncs'glslExpression' argCount retCount channels reuse

1. Stack Preparation

Before calling c, you must have pushed the following onto the stack (in order, from top to bottom):

2. Quoted String Parameters

The command requires four quoted strings to define the shader logic:

GLSL Scope: Variables set with a numeric argument via the set command are available (with . replaced by _). You also have access to:

3. Numeric Parameters

Following the strings, four numeric parameters configure the operation:

Example

Compute the element-wise minimum and maximum of two tensors:

[[1 2][3 4]];[[2 1][5 3]];   // Push two tensors
6;[2 2];                     // Vertex count 6, Output shape [2 2]

c''''                        // No vertex/helper code
ret[0] = a(t) < b(t) ? a(t) : b(t);
ret[1] = a(t) < b(t) ? b(t) : a(t);
' 2 2 0 0;                   // 2 args, 2 returns, Tensor mode, No reuse

print;quit;

cat (concatenate)

Given a stack of t1, t2, and axis (a scalar), the command concatenates t2 onto t1 along axis axis. For example:

[[0 1][2 3]];[[4 5][6 7]];0;cat;[[0 1][2 3]];[[4 5][6 7]];1;cat;print;quit;
prints
CPU tensor 1 ...

cls (clear screen)

This command clears the output buffer.

continue

This command causes execution to flow to the top of the currently loaded program as if you had jumped to a label at the start of the executing program.

depth

This command toggles depth testing (32-bit).

dup (duplicate)

Given a scalar integer N on top of the stack, duplicates the Nth item on the stack and places it on top. For example:

[0 1 2];0;dup;print;quit;
prints
CPU tensor 1 ...

e (enclose)

This takes the tensor on top of the stack and raises its rank by 1 appending [1] onto its shape. For example:

.5;e;[[0 1][2 3]];e;print;quit;
prints
CPU tensor 1 ...

ext (extrude)

This takes the tensor on top of the stack and raises its rank by 1 prepending [1] onto its shape. For example:

.5;ext;[[0 1][2 3]];ext;print;quit;
prints
CPU tensor 1 ...

eval

This takes one string argument, an atlas program to be run immediately, not in a loop. When running, this program has access to the invoking program's variables and stack. Variables declared only within an eval statement cannot be accessed outside it; they must be pre-declared in the containing script to be shared. Also note that you cannot call into an outside label from within an eval statement: you must include all relevant code using the include command within the evaluated expression.

first / last

These commands return the first or last element along the first axis, reducing rank by 1. For example:

[[0 1][2 3]];first;print;last;print;quit;
prints
CPU tensor 0 ...

gamepad

This command pushes a vector of vectors corresponding to the gamepad state of attached controllers. The returned vectors are of the form [0:throttleLeft 1:throttleRight 2:leftStickX 3:leftStickY 4:rightStickX 5:rightStickY 6:leftShoulder 7:rightShoulder 8:home 9:up 10:right 11:down 12:left 13:select 14:start 15:a 16:b 17:x 18:y 19:leftStick 10:rightStick]

gamepadRumble

This command takes one argument, a vector like [lowFrequencyRumble highFrequencyRumble duration index], and makes the gamepad with that index vibrate with those frequencies for that duration (in seconds). The frequencies are between 0.0 and 1.0.

get

Gets a named variable and pushes it onto the stack as a tensor. For example

get'vec';
would get a variable named vec and push it onto the stack. The variable name may be used directly: instead of using get'foo' you can just use foo.

gltf

The gltf command takes one immediate string parameter, like

gltf'filename.glb'
, and produces 5 tensors; the vertex data [vertexCount 22] (pos vec4, normal vec3, uv vec2, bones vec4, weights vec4, mat float, tangent vec4) , the index data [indexCount], the bones x keyframes x animations data [4 bones*anims frames 4], and the texture array [maxWidth maxHeight materialCount*2 4], which has rgba in one texture, and xy normal + 6bitsroughness+2bits metallness and baked AO in alpha in the other. The last tensor is a scalar, the animation count, so that the correct position in the bones tensor can be deduced.

if / ifn

The two if and ifn commands implement conditional branching:

get'bool';first;if'jump'; // This branches to the label l'jump' if 'bool' is positive.
0;ifn'jump'; // This unconditionally jumps to l'jump' because a 0 was pushed on the stack and ifn branches on zero.
Looks at the top of the stack, and jumps to the label 'jump' if it is/isn't positive. The top of the stack must be a scalar and is consumed by the if/ifn command.

img (image)

Loads a 4-channel bitmap from a file. For example:

img'font.bmp'
loads font.bmp as a [width height 4] tensor.

include

This includes another file textually, placing all the commands in that file at the location of the include command. For example include'head.atl' would include all the commands of head.atl.

index

Selects elements from a tensor along a specified axis using a vector of indices. The stack should contain (from bottom to top): the source tensor, a vector of indices to select, and a scalar specifying the axis. The result is a new tensor containing only the selected slices along that axis.

For example, to select specific rows (axis 0) from a matrix:

[[10 11 12]
 [20 21 22]
 [30 31 32]
 [40 41 42]];  // Source tensor (4x3 matrix)
[0 2 3];       // Indices: select rows 0, 2, and 3
0;             // Axis 0 (rows)
index;
print;quit;

This produces a 3x3 matrix containing rows 0, 2, and 3:

[[10 11 12]
 [30 31 32]
 [40 41 42]]

To select specific columns (axis 1) instead:

[[10 11 12 13]
 [20 21 22 23]];  // Source tensor (2x4 matrix)
[1 3];            // Indices: select columns 1 and 3
1;                // Axis 1 (columns)
index;
print;quit;

This produces a 2x2 matrix containing columns 1 and 3:

[[11 13]
 [21 23]]

Indices may be repeated or reordered to duplicate or shuffle elements:

[100 200 300];  // Source vector
[2 0 0 1];      // Indices: element 2, then 0 twice, then 1
0;              // Axis 0
index;
print;quit;

This produces:

[300 100 100 200]

input

This loads a 6 element input tensor corresponding to the mouse cursor. The first two elements are the x and y mouse deltas, and the 3rd element is the mouse wheel delta. The next 3 elements correspond to the left, right, and middle mouse buttons, 1.0 if held, 0.0 if released, and 2.0 if double clicked.

keys

This command loads a [512] shaped tensor corresponding to keyboard presses. The array will have a 1 if the corresponding key is being pressed, and 0 if not. For example:

keys;[41 42 0];s;first;ifn'go';quit;l'go';
will exit if esc (SDL_Scancode 41) is pressed. See SDL_Scancode docs.

kettle

This command takes one string argument and one numeric parameter, and saves that many elements off the stack into the filename provided, in a format that is optimized for loading back into the GPU. You set up the stack with the textures and tensors you want to save, kettle them, and then can quickload them later with unkettle. "cooking".

l (length)

This command takes a tensor of rank 1, a vector, and pushes the Euclidean distance of it onto the stack.

load

Loads another Atlas file, resets the stack, and starts executing it. For example: load'mandelbrot.atl'. If called without quotes, it treats the top of the stack (which must be a vector) as the filename.

m (matrix multiply)

This multiplies the top two tensors (which must be rank 2 or less) and pushes the result on the stack. The result is always of rank 2. For example [[1 0 1][2 1 1][0 1 1][1 1 2]];[[1 2 1][2 3 1][4 2 2]];m;print;quit; prints

CPU tensor 0 ...

minmax (minimum and maximum)

This takes the tensor on top of the stack and replaces it by a vector of length two with the minimum and maximum values occuring in the tensor, like [min max].

ortho (orthographic)

This takes a 6-vector argument in the form [left right bottom top near far] and pushes a corresponding orthographic projection matrix onto the stack.

pop

Pops the stack, doing nothing if the stack is already empty (not an error).

proj (projection)

This takes a 5-vector argument in the form [fov width height near far] and pushes a corresponding projection matrix onto the stack.

print

Prints the stack.

[1 2 .3];'Hello, world!';print;

printLine

Prints the string on top of the stack, newline included.

printString

Prints the string on top of the stack, newline not included.

quit

Quits the program.

r (reverse)

Reverses a tensor along a specified dimension (axis). The axis, a scalar, should be on top of the stack, and the tensor below that. This is equivalent to mirroring along an axis. For example:

[[0 1][2 3]];0;r;print;quit;
prints
CPU tensor 0 ...

raise

Given a scalar n, this raises the nth item on the stack to the top of the stack.

rep (repeat)

Given a scalar and tensor on top of the stack, this command repeats the tensor a number of times equal to the scalar. For example:

[0 1 2];3;rep;print;quit;

rep (repeat)

Given a shape and tensor on top of the stack, this command changes the shape of the tensor. The new old and old sizes must match, or an error occurs.

return

Returns from a function to the place that called that label:

1;if'past';l'square'; // Square a scalar on the top of the stack
0;dup;*;return;l'past';2;square;square;print;quit;

rot (rotate)

This function returns a 4x4 rotation matrix. The rotation is performed around a 3D vector (taken from the top of the stack) by an angle (stored as the scalar immediately below the vector on the stack).

s (slice)

Extracts a slice from a tensor along a given axis. The arguments are given as a rank 1 tensor (array) in the form [start end axis], and will slice the tensor below it from start (inclusive) to end (exclusive) along dimension axis.

set

Sets a named variable to a certain value. The size may be explicitly set, as the variables can be used as uniforms for the shaders. For example

[1 2 3];set'vec' 3;
would set a variable named vec with the value [1 2 3], and make it available as a vec3 in compute shaders. The valid sizes are 1,2,3,4 for vectors, and 16 for a 4x4 matrix. If set is called without a size, the named variable will not be set as a uniform. This function necessitates a deep copy, and should be used sparingly on large tensors. A short form of this command exists; foo= or foo= N may be used instead of set'foo' or set'foo' N

sort

This commands takes 1 vector and produces a new vector of equal length containing the indices of that vector in sorted order, from smallest to largest.

shape

This returns a vector containing the shape of a tensor, if called twice it will return the rank of a tensor as a vector.

size

This pushes the size of the stack onto the top of the stack.

t (transpose)

Transposes two dimensions in a tensor. The argument is a rank 1 tensor (array) in the form [axis1 axis2]. The tensor below that has those axes swapped.

textBufferView

This command creates a view of the standard output buffer. It takes one argument, a 3 vector like [width height scrollUp] where scrollUp causes that many visible lines to be skipped, scrolling the buffer up. Words are wrapped to width. This command produces a tensor suitable for use with std.textAreaToFlatTexture or std.textToTexture.

textInput

This command pushes the text input since the last call to this command onto the stack as ascii. This makes it easy to gather text from the keyboard. Up to 65536 characters are buffered.

texture

This command takes the texture on top of the stack, which must be rank 3, 1 or 4 component like [512 512 4] or [256 256 1], and makes it suitable for use as a texture. That is, this command enables and generates mipmaps and anisotropic filtering.

textureArray

This command takes the texture on top of the stack, which must be rank 4, 1 or 4 component like [512 512 512 4] or [256 32 256 1], and turns it into a 2d texture array. These textures can be sampled from using the overloaded af,bf,cf,df( vec3 ) versions. This command takes a single numeric argument, channels, which matches the c compute command: 1 for a single channel float, 10 for a single channel u8 normalized, 4 for an float vec4, and 40 for the rgba u8 version. This command does not turn on mipmapping and anisotropic filtering for the texture, that can be done by then calling the texture command.

timeDelta

This command pushes the frame time, in seconds, onto the stack as a scalar.

time

This command pushes a 2 vector with the the total time in hours as an integer, and the time since last hour in seconds. This provides accurate runtimes for up to 1875 years.

toString

This command takes the top of the stack, which must be a scalar, and returns it as a string for display.

translate

This command pushes a 4x4 translation matrix onto the stack, corresponding to a translation by the 3-vector argument on the stack.

unext (unextrude)

This command performs the reverse of extrusion on the tensor on top of the stack. That is, if the last dimension is of length 1, this command reduces the rank by 1. If the last dimension isn't of length 1, an error is generated.

unkettle

This command takes one string argument, a filename, and loads the kettled tensors and textures stored in it. It is reentrant, it returns on top of the stack a scalar indicating progress. When this reaches 0, it indicates the items have been pushed onto the stack. The filename is only consumed on the first call to unkettle, subsequent calls don't expect it until until progress reaches 0. The returned progress will be a number counting down from 2.0 to 1.0, and then jumps to 0 at completion, so that values close to 1.0 don't get confused with 0.0.

while / for

To do a while loop until i is 5, for example, you can do the following:

[0];set'i' 1; // Set i to 0
l'start';   // The start label
get'i';print;[1];+; // Get i onto the stack and increment it
0;dup;set'i' 1;     // Set i to new value, leave i on stack
[6];-;first;if'start'; // Loop if i != 6
quit;

windowSize

This command pushes a 2-vector onto the stack with the width and height of the display window.

workspace

This command takes one immediate string parameter and sets the current workspace for the current file. For example, workspace'foo' will cause all subsequent sets and gets to be prefixed by "foo." and all glsl variables will be prefixed by "foo_". Because both sets and gets are prefixed, the current file need not prefix normal gets, just the glsl variables must always be prefixed as they are statically compiled at script inititialization. All subsequent commands are prefixed this way until the end of file or next workspace command.

AI Credits

Many AIs participated in the creation of Atlas. Special thanks to the frontier models that assisted with logic, debugging, and documentation over the last two years, including: