Skip to content

GPU kernel

[Kernel] was previously spelled [TsukimiKernel]. The old spelling still works.

SyntaxDescriptionNote
return new Color4(...)Method with a return valueThis is the only way to write.
static Color4 Step(...) => prev[id] * 0.5fExpression-bodied kernel
int bandsInteger parameter
bool invertBoolean parameter
Vector2 shift2-component parameter
Vector3 tint3-component parameter
Vector4 weights4-component parameter
prev[id]Reading your own cellprev[id] reads that cell’s value.
c.R c.G c.B c.AReading a component
new Color4(r, g, b, a)Constructing a cell value
Color4.WhiteConstant color
Color4.BlackReturning a constant color
id.X id.YCoordinates of the assigned cell
prev[id.Offset(1, -1)]Reading a cell at a relative positionAn index pointing outside the buffer is clamped to the edge value (see “Properties of numbers” below).
new KernelId(1, 2)Constructing coordinates
prev[id.Offset(-1000, -1000)]Reading outside the buffer
prev.Wrap(id.Offset(1, 0))Reading wrapped to the far sideUnlike an index clamped to the edge value, it lets you tile a small board. On a buffer 64 wide, asking for -1 gives you cell 63.
prev.Smooth(position)Reading blended with the neighboursNo seams show when you scale it up. It reads four cells, so it costs more than reading one.
SyntaxDescriptionErrorReasonOutside the kernel
foreachIterating over a sequenceTUKI0001by designForms that compile
float v = (i++) + 1fIncrement or decrement where a value is expectedTUKI0001by designForms that compile
float w = (v = 1f) + 1fAssignment where a value is expectedTUKI0001by designForms that compile
double dDouble-precision floatTUKI0001undecidedForms that compile
string sStringsTUKI0001by designForms that compile
float[] xsArraysTUKI0001by designForms that compile
struct Pair { }A custom value typeTUKI0001undecidedForms that compile
Debug.Log(x)Calling a Unity APITUKI0001by designForms that compile
Random.valueRandom numberTUKI0001by designForms that compile
Time.timeTimeTUKI0001by designForms that compile
0f / 0fA constant that becomes NaNTUKI0001by designForms that compile
1f / 0fA constant that becomes infinityTUKI0001by designForms that compile
out argumentReturning a value through an argumentTUKI0001by designForms that compile
gotoJump to an arbitrary locationTUKI0001by designForms that don’t compile
try / catchCatching exceptionsTUKI0001runtimeForms that don’t compile
A function that calls itselfRecursionTUKI0001by designForms that don’t compile
Peek(GpuBuffer2D b, ...)Passing a buffer to a helper functionTUKI0001by design—
prev[id.Offset(1, 0)] = cWriting to another cellCS0200, TUKI0001by design—
TypesContentsExample
GpuBuffer2DAn array of cells. Received as a kernel parameter and read by indexReading your own cell
KernelIdThe coordinates of the assigned cell. X and Y are intCoordinates of the assigned cell
Color4The value of one cell. R G B A are float values from 0 to 1Reading a component
float int boolScalars. Usable as parameters, local variables, and helper function parameters and return valuesInteger parameter
Vector2 Vector3 Vector4Values with 2 to 4 components. The operators are listed under Operators by type2-component parameter
Vector4[]A table. Received only as a parameter marked [Capacity(N)]Running with a table

Pages for statements, expressions, and functions

Section titled “Pages for statements, expressions, and functions”
PageContents
Kernel statements and expressionsControl flow, operators, and the per-type operation tables
Functions callable from a kernelThe built-in table and helper functions

Differences between inside and outside the kernel

Section titled “Differences between inside and outside the kernel”
What you can writeA narrow part of this language. The “Outside the kernel” column of the table above is the difference: the 13 forms can be written in an ordinary method but not inside a kernel
The error messageThe same for either reason (TUKI0001, “C# syntax that cannot be lowered to Udon”). Those 13 forms do work in Udon, so reading the message literally leads you to the wrong cause
How to tellIf the form that errored is supported in an ordinary method, the cause is not the form: it is that you wrote it inside a kernel

The only way to write is the kernel’s return value, which is written to your own cell. Assigning to another cell you get CS0200 (assignment to a read-only element) rather than a TUKI number. Because the buffer’s indexer is read-only, it never reaches this language’s checks and errors first under C#‘s own rules.

  • One cell is 4 components, and a component is 8 bits (256 steps) by default. For finer values inside 0..1, pack two with Gpu.Pack16x2 (65536 steps each, two values per cell). For values outside 0..1, make the buffer with GpuFormat.Half
  • An index outside the buffer is clamped to the edge value, so you don’t need to write your own range check
  • Keeping the side length a multiple of 16 widens the range of supported environments
  • Gpu.Pack16x2 can only be used inside a kernel (you cannot pack a value outside and pass it in)

In a default buffer, colour rounds to 8 bits. Packing several values into one colour loses precision by that many steps.

The kernel’s return value is written to that cell.

using UnityEngine;
using Tsukimi;
public class ParReturn : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
return new Color4(0.5f, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}

If the body is a single expression, it can be written with =>.

using UnityEngine;
using Tsukimi;
public class ParExpressionBody : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
// If the body is a single expression, it can be written with =>
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev) => prev[id] * 0.5f;
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}

Pass a value of the same type at the same position in Gpu.Run.

using UnityEngine;
using Tsukimi;
public class ParArgInt : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
public int bands;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev, int bands)
{
// For an integer parameter, pass an int at the same position in Gpu.Run
float v = prev[id].R * bands;
return new Color4(v, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current, bands);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParArgBool : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
public bool invert;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev, bool invert)
{
float v = prev[id].R;
// A boolean parameter can be used directly as a condition
return new Color4(invert ? 1f - v : v, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current, invert);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParArgVector2 : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
public Vector2 shift;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev, Vector2 shift)
{
// A 2-component parameter. Here it shifts which cell is read
return prev[id.Offset((int)shift.x, (int)shift.y)];
}
void Update()
{
Gpu.Run(nameof(Step), next, current, shift);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParArgVector3 : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
public Vector3 tint;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev, Vector3 tint)
{
Color4 c = prev[id];
// A 3-component parameter. Here it scales the color
return new Color4(c.R * tint.x, c.G * tint.y, c.B * tint.z, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current, tint);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParArgVector4 : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
public Vector4 weights;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev, Vector4 weights)
{
Color4 c = prev[id];
// A 4-component parameter. Here it weights each component
float v = c.R * weights.x + c.G * weights.y + c.B * weights.z + c.A * weights.w;
return new Color4(v, v, v, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current, weights);
Gpu.Swap(ref current, ref next);
}
}

id is the coordinate of the cell that kernel is responsible for.

using UnityEngine;
using Tsukimi;
public class ParCellRead : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
Color4 c = prev[id];
return new Color4(c.R, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}

Each component is a value between 0 and 1.

using UnityEngine;
using Tsukimi;
public class ParCellComponents : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
Color4 c = prev[id];
return new Color4(c.R + c.G + c.B + c.A, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}

Builds the value to write to that cell from 4 components.

using UnityEngine;
using Tsukimi;
public class ParCellConstruct : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
Color4 c = new Color4(0.5f, 0f, 0f, 1f);
return new Color4(c.R, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}

You can use a built-in constant directly as a value.

using UnityEngine;
using Tsukimi;
public class ParCellWhite : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
return new Color4(Color4.White.R, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}

You can return a constant color directly.

using UnityEngine;
using Tsukimi;
public class ParCellBlack : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
return Color4.Black;
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}

You can get the position of the assigned cell as integers.

using UnityEngine;
using Tsukimi;
public class ParIdXy : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
return new Color4((id.X + id.Y) * 0.001f, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}

Offset reads a cell at a relative position.

using UnityEngine;
using Tsukimi;
public class ParIdOffset : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
return new Color4(prev[id.Offset(1, -1)].R, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}

You can build a coordinate yourself to point at any cell.

using UnityEngine;
using Tsukimi;
public class ParIdConstruct : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
KernelId k = new KernelId(1, 2);
return new Color4(prev[k].R, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}

An index outside the buffer is clamped to the edge value, so you don’t need to write your own range check.

using UnityEngine;
using Tsukimi;
public class ParEdgeRead : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
// An index outside the buffer is clamped to the edge value
Color4 outside = prev[id.Offset(-1000, -1000)];
return new Color4(outside.R, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}

Wrap sends an index outside the buffer round to the far side.

using UnityEngine;
using Tsukimi;
public class ParCellWrap : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
// An index outside the buffer wraps round to the far side
Color4 far = prev.Wrap(id.Offset(-1, 0));
return new Color4(far.R, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}

Smooth takes a position (0 to 1) and returns the value blended with the surrounding cells.

using UnityEngine;
using Tsukimi;
public class ParCellSmooth : TsukimiBehaviour
{
private GpuBuffer2D small;
private GpuBuffer2D big;
void Start()
{
small = Gpu.Buffer(16, 16);
big = Gpu.Buffer(256, 256);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D board)
{
// Take a position (0 to 1) and read it blended with the surrounding cells
Vector2 position = new Vector2(id.X * 0.00390625f, id.Y * 0.00390625f);
return board.Smooth(position);
}
void Update()
{
Gpu.Run(nameof(Step), big, small);
}
}