TinyFX language
TinyFX is a statically type-checked language for CPU orchestration and GPU rendering or compute. A source program is checked once, then can run through the native VM/runtime, browser WASM/runtime, or generated TypeScript/WebGPU target. The target changes execution and host integration, not the language.
This page covers source structure and control flow. Read types and functions for the type and call rules, and GPU programming for shader, resource, draw, and compute rules. The generated operation reference is authoritative for individual operation signatures, stages, and target availability.
Source form
TinyFX source files use the .tfx extension. Statements may omit ; before a
line break, }, or the end of the file. Semicolons remain required between
statements on the same line and between the initializer, condition, and step
of a C-style for. A continued expression can cross a line break, so use an
explicit semicolon when a following (, [, ., or operator would otherwise
continue the previous expression. Blocks use braces; both // line comments
and /* ... */ block comments work. The formatter writes canonical explicit
semicolons.
// Mutable binding with an inferred type.
let count = 0;
// Immutable binding with an explicit type.
const scale: float = 0.5;
/* A block comment. */
count += 1;let bindings are mutable and const bindings cannot be rebound. A const
binding to a reference value does not make that value deeply immutable: a
permitted mutable field can still be changed through it. See
structs and aggregates.
Integer literals may be decimal, hexadecimal (0xFF), or binary (0b1010).
Floating literals support decimal and exponent notation. Strings, booleans,
array literals, struct literals, tuples used as vector shorthand, and ordinary
operators are expressions. Numeric tuple syntax such as (1.0, 2.0, 3.0)
constructs a vector; explicit constructors such as float3(1, 2, 3) are often
clearer at API boundaries.
TinyFX has familiar arithmetic, comparison, logical, bitwise, assignment, and
prefix/postfix increment/decrement operators. Binary precedence follows the
JavaScript ordering. Matrix multiplication is directional: matrix * vector
is a column-vector product, while vector * matrix is the corresponding
row-vector product.
Statements and control flow
TinyFX has if/else, while, C-style for, array for ... of, break,
continue, return, and the conditional ?: expression.
fn classify(value: float) -> int {
if (value > 0.0) {
return 1;
} else if (value < 0.0) {
return -1;
}
return 0;
}
fn sum(values: int[]) -> int {
let total = 0;
for (let value of values) {
total += value;
}
return total;
}parallel for is intentionally not an ordinary CPU loop. It describes a
restricted, race-safe compute operation and has its own resource and feedback
rules; see data-parallel work. Likewise,
profile "label" { ... } is a CPU-only profiling scope with a literal label;
see profiling.
Program lifecycle
Top-level declarations establish program state. The runtime recognizes two optional entry functions:
setup()runs once when the program starts.loop()runs once per frame when present.
Both must be unannotated, zero-parameter functions because the host invokes
them directly. A program need not have either: static work performed during
initialization or setup() can leave an image in the runtime's persistent
target. Runtime lifecycle explains frame
ordering, input, camera state, and host ownership.
metadata {
title: "Warm gradient",
version: "1.0.0",
description: "A minimal TinyFX program.",
category: "Examples",
tags: ["color", "intro"],
credits: "TinyFX contributors",
}
fn loop() {
draw(shader {
frag {
out.color = (in.uv.x, in.uv.y * 0.5, 0.7, 1.0);
}
});
}metadata { ... } is optional file-level display metadata. The supported
fields are title, version, description, category, tags, and
credits. All except tags take string literals; tags takes a list of
unique string literals. The compiled program exposes the entry file's metadata
and preserves every source file's metadata for tools. Metadata is display
information, not a compatibility or artifact-version declaration.
Files, imports, and names
An import asks the host resolver to add another source file to the program:
import shade, Palette as Colors from "./palette";
fn loop() {
draw(shader {
frag { out.color = shade(in.uv); }
});
}Imports are currently organizational rather than module scopes. Every compiled
user file and the embedded standard library share one program namespace;
imports are deduplicated across transitive and cyclic graphs. The imported
entries, aliases, and export { ... }; declarations are accepted syntax but
do not create a JavaScript-style namespace or private module boundary today.
Avoid duplicate top-level names across program files.
The embedded standard library is always available. An import from /_std/...
is accepted for source organization but does not invoke the application's
resolver. See the standard-library index.
:: addresses static methods and namespace-style APIs, such as
noise::fbm2(...). . addresses fields, instance methods, vector swizzles,
and enum members, such as camera.position, mesh.clone(), color.rgb, or
Blend.alpha. Struct fields and methods are private unless declared pub;
that member visibility is checked even though files share a namespace.
Staging at a glance
The source language is staged: code that reaches a shader or compute kernel must be GPU-compatible, while CPU-only code remains available for orchestration, heap data, assets, and host services. This is checked through the complete call chain, not inferred from a function's spelling alone.
Use gpu and cpu annotations to make intent explicit, or an ordinary fn
when GPU compatibility should be inferred from uses. Functions and staging
has the rules; GPU programming has shader and kernel restrictions.
The generated TypeScript/WebGPU target supports the current general
language/runtime surface rather than only early fullscreen examples; use the
operation reference for precise per-operation evidence
and target-specific limitations.
Reading the rest of the manual
- Types covers values, collections, structs, enums, traits, and GPU-safe data.
- Functions covers calls, defaults, overloads, methods, closures, and stage annotations.
- GPU programming covers shaders, resources, draw passes, feedback, depth, and compute.
- Assets and media covers CPU-side opaque asset and media handles.
- Tooling covers checking, formatting, editor support, and emitted targets.