Furth FortuneEngineering reference
TECHNICAL DOCUMENTATION

2027 · MerTek technology

On this page

2027 / Shader language

Glint

Glint is the shader language Thalassa runs: small programs that shape and color what the graphics card draws. It reads like Trident, with types first, const bindings, brace blocks and one entry named for its stage. Thalassa checks each module once, names it by digest, and lowers it to every graphics backend.

Worked example

A valve tag that stays sharp on a watch and a wall display

The Pump Station 7 scene labels every valve with its tag, such as V-12. On the crew lead's watch the tag is 9 pixels tall; on the control-room wall it is 140. A graphics developer writes one Glint fragment that draws the tag from a distance-field atlas. The edge stays sharp at both sizes.

  1. The module lives in the program's _glint/ folder, and its path names it: a/_glint/hud/labelInk.glint is the module a.glint.hud.labelInk. It declares two resources with explicit slots and one entry, fragment. The entry's parameters are its inputs: the glyph's logical position and its atlas coordinate.

    # a/_glint/hud/labelInk.glint: the module a.glint.hud.labelInk
    uniform<vec4,slot=0> ink
    texture2d<value=vec4,slot=0> atlas
    
    vec4 fragment(vec4 logical, vec2 atlasCoord) {
        const vec4 texel = atlas.sample(atlasCoord)
        const f32 d = texel.x - 0.5
        const f32 w = fwidth(d)
        const f32 coverage = smoothstep(0.0 - w, w, d)
        const vec4 inkValue = ink
        return vec4(inkValue.x, inkValue.y, inkValue.z, inkValue.w * coverage)
    }
  2. The program's compile parses and checks every .glint file through Thalassa's one checker. An earlier version multiplied the four-lane ink by a three-lane color. The compile refused it at line 8, before any device saw it.

    uniform<vec4,slot=0> ink
    texture2d<value=vec4,slot=0> atlas
    
    vec4 fragment(vec4 logical, vec2 atlasCoord) {
        const vec4 texel = atlas.sample(atlasCoord)
        const vec3 rgb = vec3(texel.x, texel.y, texel.z)
        const vec4 inkValue = ink
        return inkValue * rgb
    }
    # refused at line 8: the checked shader refuses this typed
    # operation, stage, or interface
  3. The scene names the checked module by digest and stage, never by file name. A text material with effect none hands its glyphs to that module, and stage 2 is fragment. Two materials that name the same digest share one entry in the scene's module closure.

    const sha256 labelInk = "a3e3dcb192a990e67debea4e4f7759de0f5266fdcee2eb35488a30d8640fd4f6"
    
    chest tagInk() {
        return t.gfx.uiTextMaterial({
            id = 5, effect = "none", aa = 0,
            moduleDigest = labelInk, moduleStage = 2
        })
    }
  4. At commit, Thalassa checks the module against the text-material contract. Input 1 must be the glyph's logical position as a vec4 and input 2 its atlas coordinate as a vec2. Uniform 0 is the resolved foreground and texture 0 the atlas page. A compute module, or one that reads a slot the contract does not supply, refuses by name before any frame draws.

What happened

The tag read V-12 at 9 pixels on the watch and at 140 on the wall, from one module. The same checked module lowered to WGSL, HLSL, SPIR-V and MSL. The Soft renderer drew the reference each backend was compared with. The type error in the earlier version never left the developer's machine.

Source that reads like Trident

Glint takes Trident's grammar wherever both languages have the construct: type-first const bindings, brace blocks, # comments and ingot records. A module has exactly one entry, and its name is its stage: vertex, fragment or compute. The entry's parameters and result derive the stage interface, so no slot number is written twice.

Operators, resource member calls and builtins express the work. @ multiplies a matrix by a vector, albedo.sample(uv) samples a texture, and smoothstep, fwidth, clamp and select are builtins. A name that is a type spelling, such as color, refuses as a binding name.

Glint

A vertex entry with an ingot result

The ingot names the varyings. The first field is the clip position, and the fragment stage reads world as its input.

ingot ValveVaryings { vec4 clip, vec4 world }
uniform<mat4,slot=0> clipFromWorld
uniform<mat4,slot=1> worldFromObject

ValveVaryings vertex(vec3 position) {
    const vec3 p = position
    const vec4 p4 = vec4(p.x, p.y, p.z, 1.0)
    const vec4 world = worldFromObject @ p4
    const vec4 clip = clipFromWorld @ world
    return { clip = clip, world = world }
}
Glint

A fragment entry: the color grade

One uniform, one texture and one input. Renaming tint to shade leaves the module digest unchanged, because names enter no digest.

uniform<vec4,slot=0> tint
texture2d<value=vec4,slot=0> albedo

vec4 fragment(vec2 uv) {
    const vec4 sampled = albedo.sample(uv)
    const vec4 tintValue = tint
    const vec4 graded = sampled * tintValue
    return graded
}

Resources and slots

Every resource is declared with its type, its element type and an explicit slot. Uniforms carry per-draw values such as a tint or a matrix. Textures are read through member calls. Storage buffers and storage images carry the arrays a compute module reads and writes.

Thalassa owns the memory behind every slot; the module only names it. Access words state intent, and the checker holds a module to them. A store into a readOnly buffer refuses with the line and the repair.

  • uniform<vec4,slot=0> tint: one value for the whole draw or dispatch.
  • texture2d<value=vec4,slot=0> albedo: a sampled 2D texture; textureCube is a cube map.
  • storage<u32[],slot=1> counts: an array a compute module reads and writes; readOnly forbids the write.
  • image2d<rgba8Unorm,slot=0> heatmap: a storage image; readWrite admits loads as well as stores.
Glint

Textures in a fragment module

Keyed and positional slot forms mean the same thing: textureCube<vec4,1> and textureCube<value=vec4,slot=1> check to one digest.

uniform<vec4,slot=0> tint
uniform<mat4,slot=4> toClip
texture2d<value=vec4,slot=0> albedo
textureCube<vec4,1> sky

vec4 fragment(vec2 uv, vec3 normal) {
    const vec4 base = albedo.sample(uv)
    const vec4 ambient = sky.sample(normal)
    const vec4 tintValue = tint
    return base * ambient * tintValue
}
Glint

Buffers and images in a compute module

The workgroup size is part of the entry. globalId.x indexes one depth reading per invocation.

readOnly storage<f32[],slot=0> depths
storage<u32[],slot=1> counts
image2d<rgba8Unorm,slot=0> heatmap
readWrite image2d<r32Uint,slot=1> hits
uniform<f32,slot=0> limit

void compute(uvec3 globalId) workgroup (64, 1, 1) {
    const u32 i = globalId.x
    const f32 depth = depths[i]
    const f32 ceiling = limit
    counts[i] = u32(depth > ceiling)
}

Checked operations

Every operation has one defined answer, and the checker fixes every type before a device sees the module. Integer add, subtract and multiply wrap modulo 2^32. Integer division by zero yields zero, and a shift count uses its low five bits.

Float edges are defined as well. Normalizing a zero-length vector yields the zero vector, asin and acos clamp their argument into range, and round ties to even. The transcendental functions come from Keel kernels and are correctly rounded in binary32.

Each lowering writes these rules into its own output, so integer division and zero-length vectors mean the same thing on every backend. A module states its numeric relation in its first line. numerics reference; is exact and is the default; numerics bounded; with an accuracy row admits a stated bound.

Glint

A slope kernel under the exact relation

The module states numerics reference;, so the Soft renderer's exact answer is the one every backend is held to.

numerics reference;
readOnly storage<f32[],slot=0> heights
storage<f32[],slot=1> slopes
uniform<f32,slot=0> spacing

void compute(uvec3 globalId) workgroup (64, 1, 1) {
    const u32 i = globalId.x
    const f32 here = heights[i]
    const f32 next = heights[i + 1]
    const f32 gap = spacing
    slopes[i] = (next - here) / gap
}
Glint

A store the checker refuses

The message names the repair. A derivative such as fwidth in a compute module refuses the same way, because only fragments have neighboring pixels.

readOnly storage<f32[],slot=0> heights
storage<f32[],slot=1> slopes

void compute(uvec3 globalId) workgroup (64, 1, 1) {
    const u32 i = globalId.x
    heights[i] = 0.0
}
# refused at line 6: a `readOnly` buffer takes no store:
# remove `readOnly` or the store

From one module to every backend

Thalassa parses the source once into Glint IR, checks it, and names the checked module by the SHA-256 digest of its canonical image. The renderer joins modules by digest and stage, never by name or label. Local names enter no digest.

The Soft renderer executes the checked IR on the CPU, and four lowerings write the same IR for the GPU backends. Each backend's output is compared with the Soft renderer's under a tolerance declared before the run.

Soft

Runs checked IR on the CPU with fixed evaluation order and no driver math. It is the reference every backend is compared with, and it renders on machines with no graphics card.

WGSL

The text WebGPU devices compile.

HLSL

The text Direct3D 11 compiles on Windows.

SPIR-V

The binary words Vulkan devices load, with no text step.

MSL

The text Metal devices compile.

Thalassa owns resources and submission

A Glint module computes and does nothing else. It has no file, network, process, clock or random-source access, and it cannot allocate GPU memory or hold a device handle. Thalassa allocates every buffer and texture, records and submits the work, and reads results back.

A Trident program reaches Glint only through declared rows. A scene names a module by digest and stage, and Reef binds it by slot to resources Reef owns. A compute call takes typed data in and returns typed data out, with no callback and no shader text.

  • A shader cannot read a file, open a socket or start a process, so a module copied from elsewhere carries no host authority.
  • A script never names a GPU resource, so a buffer is freed when a later scene omits it.
  • A module the Soft renderer cannot run refuses at compile, so every module has a CPU answer to compare against.
FURTH FORTUNE / ENGINEERING REFERENCEBack to top ↑