Skip to content

Kernel

SyntaxDescription
float v = prev[id].RLocal variable
v = prev[id].RAssignment
v += 0.25fCompound assignment
n++Increment and decrement
return new Color4(...)Method with a return value
if (v > 0.5f) { ... }Branch on a condition
if (...) { } else { }Two-way branch
if (id.X == 0) { return ...; }Return partway through
for (int i = 0; i < 4; i++)Loop a fixed number of times
while (i < 4)Conditional loop
{ ... }Block
if (...) { if (...) { } }Nested condition
prev[id]Reading your own cell
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
prev[id] + Color4.BlackAdding cells
prev[id] - Color4.BlackSubtracting cells
prev[id] * 0.5fScaling a cell by a constant
id.X id.YCoordinates of the assigned cell
prev[id.Offset(1, -1)]Reading a cell at a relative position
new KernelId(1, 2)Constructing coordinates
0.5fFloat value
id.X * 2 + 1Integer arithmetic
a && bCombining booleans
!(id.X > 0)Negation
v >= 0.25f && v <= 0.75fRange comparison
cond ? a : bChoosing a value by condition
-vSign negation
(float)nConverting int to float
(int)(v * 4f)Float-to-int conversion
id.X % 4Integer remainder
Mathf.Repeat(v, 0.5f)Float wraparound
new Vector2(x, y)Constructing a 2-component value
v + w, v.magnitudeOperating on a 2-component value
private const float Gain = 0.5f;Type constant
const float gain = 0.5f;Local constant
float sample; float matrix;Same name as a reserved word in the compile target
Falloff(d)Calling a helper function
Mix(a, b)Helper function taking 2 arguments
Gpu.Pack16x2(v)Packing 2 values
Gpu.Unpack16x2(c)Unpacking a packed value
prev[id.Offset(-1000, -1000)]Reading outside the buffer

Forms that compile with different values (2)

Section titled “Forms that compile with different values (2)”

No error occurs. It compiles and runs. But there are conditions where the value differs from regular C#.

SyntaxWhat happens
Mathf.Log(f, g)When the base is 0 or 1 at runtime, the result is ±0 or ±infinity (C# gives NaN for both).
Mathf.Pow(f, g)When the base is negative, the result is NaN (C# returns a value if the exponent is an integer)
SyntaxDescriptionErrorReasonOutside the kernel
do { } while (c)Loop with the check after the bodyTUKI0001not yetCompiles
switchBranching on a valueTUKI0001not yetCompiles
breakLoop exitTUKI0001not yetCompiles
continueContinue to the next iterationTUKI0001not yetCompiles
foreachIterating over a sequenceTUKI0001by designCompiles
a & bBitwise ANDTUKI0001not yetCompiles
a << 1Bit shiftTUKI0001not yetCompiles
double dDouble-precision floatTUKI0001undecidedCompiles
Vector3 v3-component valueTUKI0001not yetCompiles
string sStringsTUKI0001by designCompiles
float[] xsArraysTUKI0001by designCompiles
struct Pair { }A custom value typeTUKI0001undecidedCompiles
Debug.Log(x)Calling a Unity APITUKI0001by designCompiles
Random.valueRandom numberTUKI0001by designCompiles
Time.timeTimeTUKI0001by designCompiles
v == wEquality between 2-component valuesTUKI0001by designCompiles
0f / 0fA constant that becomes NaNTUKI0001by designCompiles
1f / 0fA constant that becomes infinityTUKI0001by designCompiles
gotoJump to an arbitrary locationTUKI0001by designRejected
try / catchCatching exceptionsTUKI0001runtimeRejected
out 引数Returning a value through an argumentTUKI0001not yetRejected
自分を呼ぶ関数RecursionTUKI0001by designRejected
Peek(GpuBuffer2D b, ...)Passing a buffer to a helper functionTUKI0001by design
prev[id.Offset(1, 0)] = cWriting to another cellCS0200by design

A list of what you can call from inside a kernel. Anything not in this table is rejected.

The “Difference from C#” column carries these marks:

  • Adjusted / The compiler rewrites the expression so it produces the same value.
  • Differs / A difference remains even after rewriting. Listed above under “Forms that compile with different values”.
SyntaxDescriptionDifference from C#
Mathf.Abs(f)Absolute value
Mathf.Acos(f)Arccosine
Mathf.Asin(f)Arcsine
Mathf.Atan(f)Arctangent
Mathf.Atan2(f, g)Angle from y, x
Mathf.Ceil(f)Ceiling
Mathf.Clamp(f, g, h)Clamp between a lower and upper boundAdjusted
Mathf.Clamp01(f)Clamp to 0..1
Mathf.Cos(f)Cosine
Mathf.Deg2RadDegrees-to-radians factor
Mathf.Exp(f)Power of e
Mathf.Floor(f)Floor
Mathf.InverseLerp(f, g, h)Where the value falls between a and b (0..1)Adjusted
Mathf.Lerp(f, g, h)Linear interpolation (t is clamped to 0..1)Adjusted
Mathf.LerpUnclamped(f, g, h)Linear interpolation (t is not clamped)
Mathf.Log(f)Natural logarithm
Mathf.Log(f, g)Logarithm with a specified baseDiffers
Mathf.Log10(f)Base-10 logarithm
Mathf.Max(f, g)The larger one
Mathf.Min(f, g)The smaller one
Mathf.PIPi
Mathf.Pow(f, g)PowerDiffers
Mathf.Rad2DegRadians-to-degrees factor
Mathf.Repeat(f, g)Wrap within 0..length (remainder)Adjusted
Mathf.Round(f)Round to nearest (0.5 rounds to even)
Mathf.Sign(f)Sign (0 counts as positive, returns 1)Adjusted
Mathf.Sin(f)Sine
Mathf.SmoothStep(f, g, h)Smooth interpolation (zero velocity at both ends)Adjusted
Mathf.Sqrt(f)Square root
Mathf.Tan(f)Tangent
SyntaxDescriptionDifference from C#
Vector2.Distance(v, w)Distance between 2 points
Vector2.Dot(v, w)Dot product
Vector2.Lerp(v, w, f)Linear interpolation (t is clamped to 0..1)Adjusted
Vector2.LerpUnclamped(v, w, f)Linear interpolation (t is not clamped)
Vector2.Max(v, w)The larger value per component
Vector2.Min(v, w)The smaller value per component
Vector2.one(1, 1)
Vector2.right(1, 0)
Vector2.up(0, 1)
Vector2.zero(0, 0)
SyntaxDescriptionDifference from C#
v.magnitudeLength
v.normalizedDirection normalized to length 1 (returns (0,0) if length is 0)Adjusted
v.sqrMagnitudeSquared length (no square root)
SyntaxDescriptionDifference from C#
Gpu.Pack16x2(v)Pack 2 values into 1 cell (65536 steps each)
Gpu.Unpack16x2(c)Unpack a value packed by Pack16x2

Differences between inside and outside the kernel

Section titled “Differences between inside and outside the kernel”

What you can write inside a kernel is a narrow subset of the language. The “Outside the kernel” column above shows the difference, 18 forms compile in an ordinary method but not inside a kernel.

The error text is the same regardless of which reason caused the rejection. (TUKI0001, “This C# syntax cannot be compiled to Udon.”). The 18 forms above do compile in Udon, so reading the text literally leads to the wrong cause. If the rejected form compiles in an ordinary method, the cause is not the form itself but writing it inside the kernel.

The only way to write is the kernel’s return value, which is written to your own cell. Assigning to another cell stops with CS0200 (assignment to a read-only value) instead of a TUKI number. Because the buffer’s index is read-only, this never reaches this language’s check — the C# rule stops it first.

  • A cell has 4 components, each 8 bits (256 steps). If you need finer values, pack 2 with Gpu.Pack16x2 (65536 steps each)
  • 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)

Color rounds to 8 bits. Packing multiple values into one color loses precision by that many steps.

using UnityEngine;
using Tsukimi;
public class ParLocalVariable : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
float v = prev[id].R;
return new Color4(v, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParAssignment : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
float v = 0f;
v = prev[id].R;
return new Color4(v, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParCompoundAssignment : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
float v = prev[id].R;
v += 0.25f;
return new Color4(v, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParIncrement : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
int n = id.X;
n++;
return new Color4(n * 0.01f, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}

The kernel’s return value is written to that cell. This is the only way to write.

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);
}
[TsukimiKernel]
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);
}
}
using UnityEngine;
using Tsukimi;
public class ParIf : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
float v = prev[id].R;
if (v > 0.5f) { v = 1f; }
return new Color4(v, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParIfElse : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
float v = prev[id].R;
if (v > 0.5f) { v = 1f; } else { v = 0f; }
return new Color4(v, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParEarlyReturn : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
if (id.X == 0) { return Color4.Black; }
return new Color4(prev[id].R, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParFor : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
float v = 0f;
for (int i = 0; i < 4; i++) { v = v + prev[id.Offset(i, 0)].R; }
return new Color4(v * 0.25f, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParWhile : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
float v = 0f;
int i = 0;
while (i < 4) { v = v + prev[id.Offset(i, 0)].R; i++; }
return new Color4(v * 0.25f, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParBlock : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
float v = prev[id].R;
{ v = v * 2f; }
return new Color4(Mathf.Clamp01(v), 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParNestedIf : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
float v = prev[id].R;
if (v > 0.25f) { if (v > 0.75f) { v = 1f; } }
return new Color4(v, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}

id is the coordinate of the cell this kernel is responsible for. prev[id] reads that cell’s value.

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);
}
[TsukimiKernel]
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);
}
[TsukimiKernel]
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);
}
[TsukimiKernel]
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);
}
[TsukimiKernel]
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);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
return Color4.Black;
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParCellAdd : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
Color4 c = prev[id] + Color4.Black;
return new Color4(c.R, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParCellSub : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
Color4 c = prev[id] - Color4.Black;
return new Color4(c.R, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParCellScale : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
Color4 c = prev[id] * 0.5f;
return new Color4(c.R, 0f, 0f, 1f);
}
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);
}
[TsukimiKernel]
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. An index outside the buffer is clamped to the edge value (see “Properties of numbers” below).

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);
}
[TsukimiKernel]
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);
}
[TsukimiKernel]
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);
}
}

Write a float constant by appending f.

using UnityEngine;
using Tsukimi;
public class ParFloatLiteral : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
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);
}
}
using UnityEngine;
using Tsukimi;
public class ParIntArith : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
int n = id.X * 2 + 1;
return new Color4(n * 0.001f, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParBoolLogic : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
bool b = id.X > 0 && id.Y > 0;
return new Color4(b ? 1f : 0f, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParNot : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
bool b = !(id.X > 0);
return new Color4(b ? 1f : 0f, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParComparisonChain : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
bool b = prev[id].R >= 0.25f && prev[id].R <= 0.75f;
return new Color4(b ? 1f : 0f, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParTernary : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
return new Color4(id.X > 32 ? 1f : 0f, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParNegate : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
return new Color4(-(-prev[id].R), 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParCastIntToFloat : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
int n = id.X;
return new Color4((float)n * 0.001f, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParCastFloatToInt : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
int n = (int)(prev[id].R * 4f);
return new Color4(n * 0.25f, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}

% computes the integer remainder. The target shader compiler may warn that it’s slow.

using UnityEngine;
using Tsukimi;
public class ParModuloInt : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
int n = id.X % 4;
return new Color4(n * 0.25f, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}

Use Mathf.Repeat to wrap a float value.

using UnityEngine;
using Tsukimi;
public class ParModuloFloat : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
return new Color4(Mathf.Repeat(prev[id].R, 0.5f), 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParVector2Construct : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
Vector2 v = new Vector2(0.25f, 0.5f);
return new Color4(v.x + v.y, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}

Besides adding and subtracting Vector2 values, you can use members like magnitude.

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

A constant is replaced with its value at compile time.

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

Same name as a reserved word in the compile target

Section titled “Same name as a reserved word in the compile target”

A name that collides with a reserved word on the shader side is renamed at compile time.

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

A static method called from a kernel is emitted alongside it as a shader function. That helper function does not need [TsukimiKernel].

using UnityEngine;
using Tsukimi;
public class ParCallHelper : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
static float Falloff(float d) { return Mathf.Clamp01(1f - d * d); }
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
float d = (id.X - 32) * 0.05f;
return new Color4(Falloff(d), 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}
using UnityEngine;
using Tsukimi;
public class ParHelperTwoArgs : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
static float Mix(float a, float b) { return a * 0.5f + b * 0.5f; }
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
return new Color4(Mix(prev[id].R, prev[id].G), 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}

A cell has 4 components, each 8 bits (256 steps). Packing preserves up to 65536 steps each.

using UnityEngine;
using Tsukimi;
public class ParPackTwoValues : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
Vector2 fine = new Vector2(prev[id].R, prev[id].G);
return Gpu.Pack16x2(fine);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}

A packed value can be split back into the original 2 with Gpu.Unpack16x2.

using UnityEngine;
using Tsukimi;
public class ParPackRoundtrip : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[TsukimiKernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
Vector2 fine = new Vector2(0.5f, 0.25f);
Color4 packed = Gpu.Pack16x2(fine);
Vector2 back = Gpu.Unpack16x2(packed);
return new Color4(back.x, back.y, 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);
}
[TsukimiKernel]
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);
}
}