Skip to content

GPU scene language ​

The scene language declares simple 3D objects and assigns their properties. The compiler resolves the declarations on the CPU. mountScene draws the accepted scene with a WebGL2 signed-distance-field (SDF) renderer. Start with the live scene example, then change one property at a time.

This page describes source0 and source1 at local source snapshot 5f5ad866205a21510ac69552e704cc162fa253b5. The language is a bounded scene format. CSS selectors, JavaScript expressions and arbitrary GLSL are separate formats and are not accepted here.

Typed Source2 generation ​

Use Generate a scene with typed inputs for the separate 4dcb0ebb Source2 compiler with @param, @repeat and @when. Its application displays coefficient-based diagnostic boxes and does not execute the derived shaders. This page keeps its older Source0/1 parser and mountScene contracts.

Practice styles and local transforms ​

The GPU composition examples show type styles with ID overrides, rounded-box dimensions and local capsule endpoints under rotation. Complete sources and actual images accompany the live examples.

Choose the correct language ​

InputPurposeCPU entryDrawing entry
Source0/1 @scene textDeclare SDF primitives and their styleparseScene, compileGpumountScene
@shader textSet an opaque mesh fragment colorcompileMeshMaterialMeshRenderer.setMaterial
EngineSceneV0 dataPrepare CPU hierarchy, transforms, bounds and drawsprepareEngineSceneNo general Engine renderer in this source snapshot
CAD @model textDefine CAD geometry in model unitscompileCadModel from CADTessellate, convert, then mount an admitted mesh

The scene format does not embed a material block. The material language changes a mesh color. It does not create scene objects. The CPU Engine data format does not parse @scene text.

First complete source1 scene ​

css
@scene shapes {
  version: 1;
  background: #101820;
  sphere #ball;
  box #block;
}
sphere { radius: .65; color: #F1AA55; }
#ball { translate: -1.1 0 0; }
#block {
  size: 1 1.5 .8;
  translate: 1 0 0;
  rotate-x: 20deg;
  rotate-y: 35deg;
  rotate-z: -10deg;
  color: #57B4D9;
}

The orange sphere is on the left. The blue box is on the right. The box has full extents 1 × 1.5 × .8. Its local rotation is [20,35,-10] degrees. The sphere has radius .65, so its diameter is 1.3 scene units.

The source does not specify a camera. With the default camera, the renderer looks from [0,0,6] toward negative Z. The background is dark blue. See the rendered image and exact input.

Use explicit source1 options for this input:

ts
import { compileGpu, mountScene } from '@barocss/gpu';

// source is the complete text above. canvas is an HTMLCanvasElement.
const compiled = compileGpu({ kind: 'scene', source, sourceVersion: 1 });
if (!compiled.ok) throw new Error(JSON.stringify(compiled.diagnostics));
console.log(compiled.ir.pipeline);       // 'sdf'
console.log(compiled.ir.schemaVersion);  // 2
console.log(compiled.ir.draws.length);    // 2

canvas.width = 800;
canvas.height = 500;
const renderer = mountScene(canvas, source, undefined, { sourceVersion: 1 });
// Call renderer.dispose() when the host removes this view.

Compilation needs no canvas or WebGL context. Mounting needs an available WebGL2 context.

Source versions ​

FeatureSource0Source1
Required headerversion: 0;version: 1;
Shapessphere, boxsphere, box, cylinder, capsule, rounded-box, torus
Object rotationNo rotation propertiesrotate-x, rotate-y, rotate-z
Host optionOmit source optionsPass {sourceVersion: 1}
Render IRSDF schema version1SDF schema version2

parseScene(source) and mountScene(canvas, source) use source0. A version: 1; declaration does not change the host parser mode. Pass the explicit option as well. sourceVersion: 0 is not an accepted explicit option; omit the option for source0.

ts
import { parseScene } from '@barocss/gpu';

const source0 = '@scene simple { version: 0; sphere #ball; }';
const scene0 = parseScene(source0);
console.log(scene0.version); // 0

const source1 = '@scene simple { version: 1; sphere #ball; }';
const scene1 = parseScene(source1, { sourceVersion: 1 });
console.log(scene1.objects[0].rotateDegrees); // [0, 0, 0]

Grammar and identifiers ​

The following grammar is an explanatory summary of the parser. The property tables below define each value.

text
document      = "@scene" identifier "{" scene-item* "}" style-rule*
scene-item    = "version" ":" selected-version ";"
              | "background" ":" hex-color ";"
              | shape "#" identifier ";"
style-rule    = selector "{" property-declaration* "}"
selector      = shape | "#" identifier
property-declaration = property ":" value ";"
identifier    = ASCII letter or "_", followed by letters, digits, "_" or "-"
hex-color     = #RGB | #RRGGBB

There is exactly one scene block. Style rules follow that block. Declare objects inside @scene; place property blocks after its closing brace. Each declaration requires a semicolon. Empty style blocks are allowed.

Identifiers and keywords are case sensitive. #ball and #Ball refer to different IDs. Scene names and object IDs use ASCII identifiers. An ID cannot start with a digit. IDs must be unique within the scene. A #id style selector must refer to a declared object.

Whitespace and both comment forms are accepted:

css
// This line is an annotation.
@scene notes {
  version: 0;
  /* Comments do not change diagnostic offsets. */
  sphere #ball;
}

The parser discards comment text but retains offsets into the original source. An unclosed /* comment is an error.

Declarations create objects; styles change them ​

css
@scene cascade {
  version: 0;
  sphere #small;
  sphere #large;
}
sphere { radius: 1; color: #406080; }
#small { radius: .5; }
sphere { radius: 2; }

The declarations create two objects in order: small, then large. The final radii are .5 and 2. The later type rule changes large. It does not override the more specific #small value.

The cascade is per property:

  1. A shape selector has specificity0.
  2. An ID selector has specificity1.
  3. A higher specificity wins, regardless of source order.
  4. At the same specificity, the later declaration wins.
  5. An omitted property retains its default.

Rules do not create objects. A valid shape rule can match no objects. An unknown ID selector is refused even if another rule would override it. Each declaration is validated before the cascade; a bad dimension in a losing rule is still an error.

There are no classes, lists of selectors, descendant selectors, pseudo selectors, !important, inheritance, variables or CSS units. translate: 1px 0 0; is invalid.

Default values ​

ValueDefault
Scene background#101820
Object color#E08040
Object translation[0,0,0]
Sphere radius1
Box full size[2,2,2]
Source1 rotation[0,0,0] degrees
Cylinder radius / full Y height1 / 2
Capsule radius / local start / local end1 / [0,-1,0] / [0,1,0]
Rounded-box full size / rounding radius[2,2,2] / .25
Torus major radius / minor radius1 / .25

Colors become normalized RGB arrays. For example, #57B4D9 becomes [87/255,180/255,217/255]. Color alpha and named colors are unavailable.

Property reference ​

PropertyApplies toValueMeaning
colorAll shapesOne #RGB or #RRGGBBOpaque object color
translateAll shapesThree numbersXYZ scene position
radiusSphere, cylinder, capsule, rounded-boxOne positive numberShape radius or box rounding radius
sizeBox, rounded-boxThree positive numbersFull outer XYZ extents
rotate-x/y/zAll source1 shapesOne number with degLocal axis rotation
heightCylinderOne positive numberFull local Y height
start, endCapsuleThree numbers eachLocal center segment endpoints
major-radiusTorusOne positive numberRadius of the XZ centerline circle
minor-radiusTorusOne positive numberRadius of the tube

Numbers can use signs, decimals and exponent notation. Positive dimensions must be at most1000 and remain positive in Float32. Coordinates must be finite and lie within [-1000,1000]. Values use scene units; the language does not define a millimeter unit. CAD mesh conversion has a separate mmToSceneUnit option.

Rotation and coordinate order ​

The coordinate system is right handed with Y up. Source1 applies local X rotation, then Y, then Z. Rotation uses explicit degrees in [-360,360]. Translation places the rotated object in the scene. The compiler uses the inverse rotation when evaluating the object's SDF.

rotate-y: 35deg; is valid. rotate-y: 35; and rotate-y: .6rad; are invalid. Rotating a sphere does not change its silhouette. Box, cylinder, capsule and torus rotation changes their orientation.

Source1 shapes in one complete input ​

Open GPU six shapes to compare these six shapes in one view. Each shape has its own color. The two rows make their silhouettes and extents easier to compare.

css
@scene sixShapes {
  version: 1;
  background: #101820;
  sphere #ball;
  box #block;
  cylinder #post;
  capsule #pill;
  rounded-box #case;
  torus #ring;
}
#ball { radius: .4; translate: -2 1 0; color: #F1AA55; }
#block { size: .7 .9 .6; translate: 0 1 0; rotate-y: 25deg; color: #57B4D9; }
#post { radius: .35; height: 1; translate: 2 1 0; color: #95D88B; }
#pill { radius: .25; start: 0 -.4 0; end: 0 .4 0; translate: -2 -1 0; color: #CA97E5; }
#case { size: 1 .8 .6; radius: .1; translate: 0 -1 0; color: #ED8090; }
#ring { major-radius: .55; minor-radius: .15; translate: 2 -1 0; rotate-x: 90deg; color: #F0D670; }

The accepted object order is sphere, box, cylinder, capsule, rounded-box, torus. Their IDs are ball, block, post, pill, case, ring. Translation places the first three at Y=1 and the last three at Y=-1. The example uses source1 explicitly; its render IR has schema version2 and pipeline sdf.

The cylinder is aligned to local Y before rotation. Its height includes the complete straight cylinder, not a half height. The capsule adds hemispherical ends around its center segment; its overall extent is greater than the segment length by two radii.

The rounded box's size is its outer size. The rounding radius must be positive and at most half the smallest outer extent. These checks also run in Float32. A radius equal to the smallest half extent is accepted; a larger radius is refused.

The torus axis is local Y, and its centerline is in local XZ. It requires 0 < minor-radius < major-radius and major-radius + minor-radius <= 1000. Float32 radii must retain a positive hole. Capsule endpoints must retain a usable nonzero separation after Float32 conversion.

In this preset, X rotation90deg turns the ring toward the default camera. Change its minor radius from.15 to.55 to create an intentional refusal: its major and minor radii would be equal. The compiler returns VALIDATION; a live renderer retains its accepted scene while the context is usable. See the six-shape image and edit steps.

Compile and inspect errors before drawing ​

ts
import { compileGpu } from '@barocss/gpu';

const result = compileGpu({
  kind: 'scene',
  sourceVersion: 1,
  source: '@scene broken { version: 1; box #block; } #block { radius: 1; }'
});
if (!result.ok) {
  for (const d of result.diagnostics) {
    console.log(d.code, d.message, d.range);
  }
}

This input returns an error because radius is not a box property. The diagnostic message identifies that mismatch. The range refers to the original text, with character offsets start/end and 1-based line/column. The compiler returns its first diagnostic; it does not perform recovery to find every error.

parseScene throws SceneError instead of returning ok:false. The compiler groups scene errors under SYNTAX, VERSION, DUPLICATE, REFERENCE, VALIDATION or LIMIT; lowering failures use LOWERING. Prefer the returned code and range over matching an entire message string.

EditResult
Omit versionRefused: the selected version is required
Use source1 text with no host opt-inRefused: source0 expects version: 0
Declare the same #ball twiceRefused: duplicate ID
Style #missingRefused: unknown object ID
Set radius: 0Refused: dimension must be positive
Set translate: 1 2Refused: three values required
Set rotate-y: 361degRefused: out of rotation range
Give capsule equal endpointsRefused: unusable center segment
Set torus minor radius equal to major radiusRefused: no positive ring hole

Update an existing renderer ​

ts
const before = renderer.scene;
const update = renderer.update(source.replace('radius: .65;', 'radius: 0;'));
if (!update.ok) {
  console.log(update.error.message);
  console.assert(renderer.scene === before);
}

The renderer prepares and draws a candidate before adopting it. A refused source update retains the previously accepted scene. If a candidate draw fails, it attempts to restore the previous frame. A lost WebGL context requires a new mount; frame restoration is not guaranteed after context loss.

There is one synchronous frame per accepted update. The renderer creates no animation timer. The host controls edits, scheduling, resizing and disposal. setCamera is a separate transaction; camera data does not belong in the scene text.

Budgets and supported rendering ​

BoundaryLimit at this source snapshot
Source64KiB UTF-8
Token count65,536
Objects32
Style rules128
Total style declarations512
AST nodes4,096
Generated fragment source64KiB
SDF steps per ray128
Ray travel / hit epsilon100 / .001 scene units
Canvas dimension / area4,096 per side / 4,194,304 pixels

The scene renderer chooses the nearest primitive distance. It is not a CAD Boolean solver or a triangle exporter. It has fixed SDF shading and opaque colors. Material expressions, texture coordinates, alpha, arbitrary meshes and scene hierarchy are outside this grammar. Use a mesh API or a CPU Engine API for its own stated contract.

API profile ​

This page describes source0 and source1 for local experimental @barocss/gpu version0.0.0 at the source snapshot stated above. Read the scene input rules and scene compiler contracts before constructing compiler inputs. A browser host needs WebGL2 and a TypeScript-capable bundler for the local source package.

Examples · Material language · API · Limits

Local manual snapshot. Availability is stated on each package page.