Learn the language - Syntax

Shadeup is a type-checked, garbage-collected programming language designed for writing shader and CPU code in harmony.

If you’ve never done any shader programming before, typically the cpu and gpu code are written in different languages (think C++ and GLSL). Shadeup eliminates this distinction by allowing you to write both in the same language while magicly transpiling/glueing the code and data structures together behind the scenes.

Given that shader languages and CPU languages are designed for different purposes, Shadeup has to balance the two. If you’re familiar with HLSL you’ll notice that Shadeup has a lot of the same functions and types exposed to the root scope. If you’re new to shading languages, you’ll find that a bunch of fancy math functions have taken over the root scope (you can do cos() instead of Math.cos() like in js). This is because we use a lot of math in shaders and the less typing the better.

The following tutorial will quickly get you up to speed with the syntax.

Under the hood

Shadeup is actually just an extension of typescript with some syntatic sugar sprinkled all over the place to make life easier. A lot of the APIs and patterns are just Javascript, so if you’re familiar with that you’ll be right at home.


Let’s start by running through the syntax, I might not explain every little detail so I hope the code is relatively self-explanatory:



Functions are declared with the fn keyword. The return type can be specified via -> or inferred.

Arrow functions

Arrow functions are usable in all parts of CPU code. Unforunately the GPU side is far more limited (more on this later). They are declared with the => operator.


Variables are declared with the let or const keywords. let is used for mutable variables and const for immutable variables. The type can be specified via : type or inferred.


Shadeup inherits a number of types from HLSL. The following are available:

  • int
  • int2, int3, int4
  • float
  • float2, float3, float4
  • float2x2, float3x3, float4x4
  • uint
  • uint2, uint3, uint4
  • bool
  • string (non-GPU)
    • .split(sep: string) -> string[]
    • .includes(substr: string) -> bool
    • .startsWith(str: string) -> bool
    • .endsWith(str: string) -> bool
    • .replace(from: string, to: string)
    • .trim(chars: string = ' \t\n') -> string
    • .lower() -> string
    • .upper() -> string
    • .substr(start: int, end: int) -> string
    • .len() -> int
    • [index: int] -> string
  • T[] Variable length arrays (can be closed over in GPU code, but not created in GPU code)
    • .join(sep: string) -> string
    • .push(val: T) -> string
    • .len() -> int
    • .first() -> T
    • .last() -> T
    • .append(vals: T[])
    • .remove(index: int)
    • [index: int] -> T
  • T[3] e.g. int[3] Fixed length arrays same methods as above (can be created in gpu code)
  • map<K extends Hashable, V> (non-GPU)
    • .has(key: K) -> bool
    • .set(key: K, value: V)
    • .get(key: K) -> V
    • .delete(key: K)
    • .keys() -> K[]
    • .values() -> V[]
    • [index: K] -> V
  • shader Instantiated shader
  • buffer<T> Statically sized buffer of type T where T = numeric primitive (float, float2, etc) or a user-defined struct
  • texture2d<T> 2D texture of type T where T = numeric primitive (float, float2, etc)


Vectors are declared with the () operator, or explicitly via floatN, intN, or uintN keywords.

They can be indexed via .x, .y, .z, .w or .r, .g, .b, .a. They also support swizzlishous access (.xyz, .xy, .xw, etc…)


Most HLSL math is supported (see the reference for more). Here’s a small sample:

Type casting

Numeric type casting is done via type(val)

Other types can be converted to strings via .toString() when available


Shadeup supports for and while loops. break and continue are also supported.


Shadeup supports if, else if and else conditionals. The ? operator is also supported for simple conditionals.


Structs are declared with the struct keyword. They can be instantiated by <structName> { props } They can also carry methods via impl much like Rust.

Member visibility

By default all struct members are private. They can be made public via the pub keyword.


Instance/static methods are declared with the impl keyword.

  • Instance methods are declared with a first parameter of self.
  • Static methods are declared without a self


Traits (interfaces) are declared with the trait keyword. They can be implemented by structs via impl.

Note: Traits are not supported on the GPU.


  • Exports are functions declared with pub
  • Imports are declared with the import keyword. They can be aliased via as.

In the next part we’ll write a program