References
PNGine SJON (the S-expression source language) uses bare identifiers to
reference resources. The validator resolves
each identifier document-wide against the declared forms. A name that doesn’t
resolve to a declaration of the expected kind is a not_cross_ref diagnostic.
Bare Identifier Syntax
Section titled “Bare Identifier Syntax”Reference resources by their bare name, with no # prefix and no quotes:
:pipeline mainPipeline:buffer vertexData:module shaderCodeThe slot determines which kind of declaration the name must resolve to. When you
write :module code, the validator requires a (shader-module …) named code.
Reference Slots
Section titled “Reference Slots”| Slot | Resolves to |
|---|---|
:module (pipeline stage) |
(shader-module …) |
:pipeline (render pass, render bundle) |
(render-pipeline …) |
:pipeline (compute pass) |
(compute-pipeline …) |
:layout (pipeline) |
auto, or a (pipeline-layout …) |
:layout (bind group) |
(render-pipeline …), (compute-pipeline …) or (bind-group-layout …) |
:bind-group-layouts |
(bind-group-layout …) |
:buffer, :vertex-buffers, :index-buffer |
(buffer …) |
:texture |
(texture …); a copy endpoint’s :texture also takes context-current-texture |
:texture-view |
(texture-view …) |
:view (color attachment) |
context-current-texture or a (texture …) |
:view (depth-stencil attachment) |
(texture …) |
:sampler |
(sampler …) |
:data (buffer, image-bitmap) |
(data …) |
:data (write-buffer) |
a runtime source, a (data …), or a (wasm-call …) |
:image (image copy source) |
(image-bitmap …) |
:query-set, :occlusion-query-set |
(query-set …) |
:bind-groups |
(bind-group …) |
:execute-bundles |
(render-bundle …) |
:init |
(compute-pass …), or (init …), which lowers to one |
:before |
(queue …) |
:perform |
(render-pass …), (compute-pass …) or (queue …) |
numeric slots (:size, :offset, :vertex-count, …) |
(define …) |
A slot admits one target group, and every :name in the document lives in one
namespace, so a reference has exactly one possible match. Where a group spans
several kinds (a bind group’s :layout, a frame step), the one namespace is
what keeps the choice unambiguous.
Examples
Section titled “Examples”Pipeline References
Section titled “Pipeline References”(shader-module :name code :code """ @vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f { return vec4f(f32(i) - 1.0, f32(i & 1u) * 2.0 - 1.0, 0.0, 1.0); }
@fragment fn fs() -> @location(0) vec4f { return vec4f(1.0, 0.4, 0.1, 1.0); }""")
(render-pipeline :name mainPipeline :layout auto (vertex :module code :entry vs) (fragment :module code :entry fs (target :format preferred-canvas-format)))
(render-pass :name pass (color-attachment :view context-current-texture :clear-value [0 0 0 1] :load-op clear :store-op store) :pipeline mainPipeline ; resolves to the (render-pipeline …) (draw :vertex-count 3))Shader Module References
Section titled “Shader Module References”(shader-module :name vertexShader :code """ @vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f { return vec4f(f32(i) - 1.0, f32(i & 1u) * 2.0 - 1.0, 0.0, 1.0); }""")
(shader-module :name fragmentCode :code """ @fragment fn fs() -> @location(0) vec4f { return vec4f(0.2, 0.7, 1.0, 1.0); }""")
(render-pipeline :name render :layout auto (vertex :module vertexShader :entry vs) ; (shader-module …) (fragment :module fragmentCode :entry fs ; (shader-module …) (target :format preferred-canvas-format)))A stage’s :entry is optional when the module holds exactly one entry point of
that kind: (vertex :module vertexShader) picks vs on its own. Zero or
several, and the compiler asks you to name one.
Buffer References
Section titled “Buffer References”(buffer :name positions :size 1024 :usage [vertex])(buffer :name indices :size 256 :usage [index])
(render-pass :name draw (color-attachment :view context-current-texture :clear-value [0 0 0 1] :load-op clear :store-op store) :pipeline meshPipeline :vertex-buffers [positions] ; resolves to the (buffer …) :index-buffer indices ; resolves to the (buffer …) (draw-indexed :index-count 100))Bind Group References
Section titled “Bind Group References”A bind group states one :layout, naming either a pipeline (whose auto-derived
layout it uses, selected by :group) or an explicit (bind-group-layout …):
(buffer :name settings :size 16 :usage [uniform copy-dst])(texture :name noise :size [64 64] :format rgba8unorm :usage [texture-binding])
(bind-group :name uniforms :layout drawPipeline :group 0 (entry :binding 0 :buffer settings))(bind-group :name textures :layout drawPipeline :group 1 (entry :binding 0 :texture noise))
(render-pass :name render (color-attachment :view context-current-texture :clear-value [0 0 0 1] :load-op clear :store-op store) :pipeline drawPipeline :bind-groups [uniforms textures] ; each resolves to a (bind-group …) (draw :vertex-count 3))Pass References in Frame
Section titled “Pass References in Frame”; declared elsewhere in the document:; (render-pass :name shadowPass …) (render-pass :name mainPass …); (compute-pass :name physics …) (queue :name writeUniforms …)
(frame :name game :before [writeUniforms] ; (queue …) :perform [physics shadowPass mainPass]) ; (compute-pass …), (render-pass …)A frame step is one name across three kinds, so the pass and queue names in
:perform are drawn from the same namespace as everything else.
Built-in Symbols
Section titled “Built-in Symbols”Some identifiers are built-in symbols rather than cross-references: the validator accepts them as enum members or runtime sources, not as names of declared forms.
Layout
Section titled “Layout”auto: auto-derive the bind-group layouts from the shaders, in a pipeline’s required:layoutkey
:layout autoTexture Sizes
Section titled “Texture Sizes”canvas: a texture that tracks the canvas size at runtime
:size canvasTexture Views
Section titled “Texture Views”context-current-texture: the current canvas texture
:view context-current-textureLoad / Store Operations
Section titled “Load / Store Operations”clear,load: load operationsstore,discard: store operations
:load-op clear:store-op storeColor Target / Texture Formats
Section titled “Color Target / Texture Formats”preferred-canvas-format: the browser’s preferred format- WebGPU formats:
bgra8unorm,rgba8unorm,depth24plus, etc.
:format preferred-canvas-format:format rgba8unormPrimitive Topology
Section titled “Primitive Topology”triangle-list,triangle-strip,line-list,line-strip,point-list
:topology triangle-listCull Mode and Front Face
Section titled “Cull Mode and Front Face”none,front,back: cull modesccw,cw: front-face winding
:cull-mode back:front-face ccwFilter and Address Modes
Section titled “Filter and Address Modes”nearest,linear: texture filtersclamp-to-edge,repeat,mirror-repeat: address modes
:mag-filter linear:address-mode repeatRuntime Data Sources
Section titled “Runtime Data Sources”pngine-inputs(time and canvas size),scene-time-inputs,pointer-inputs: built-inwrite-bufferdata sources
(write-buffer :buffer uniforms :data pngine-inputs)One Namespace
Section titled “One Namespace”Every :name in a document lives in one namespace, whatever form kind
declares it. Two buffers cannot share a name:
; invalid: duplicate_cross_ref_target(buffer :name myResource :size 16 :usage [uniform])(buffer :name myResource :size 32 :usage [storage]) ; ERROR: duplicate_cross_ref_targetand neither can a buffer and a sampler:
; invalid: already declared as a (buffer …)(buffer :name noise :size 256 :usage [storage])(sampler :name noise :mag-filter linear) ; ERROR: the name is takenThe second declaration is the one reported, by line, naming the kind that
declared it first: duplicate name ‘noise’: already declared as a (buffer …) at
line 1. The one namespace is what lets a slot admit several kinds at once, a
bind group’s :layout or a frame step, without the reference becoming
ambiguous.
The built-in spellings are reserved on the same principle: canvas,
context-current-texture, preferred-canvas-format, pngine-inputs,
scene-time-inputs, pointer-inputs, auto and the enum members cannot name a
form. PNGine reads them as the built-in wherever they appear, so such a
declaration could never be referenced, and it is rejected instead.
Validation Errors
Section titled “Validation Errors”| Error | Cause |
|---|---|
not_cross_ref |
No declaration of a kind the slot admits has that name (a misspelt (define …) in a numeric slot reports here too) |
duplicate_cross_ref_target |
Two forms of the same kind declare that name |
not_member |
A symbol is not a member of the slot’s enum |
missing_required_key |
A required :key is absent |
unknown_key |
The form has no such key |
positional_missing |
A required sub-form is absent (a compute pass with no (dispatch …)) |
required_one_of_missing |
An exclusive group has no alternative present (a bind-group (entry …) with no resource) |
dependent_key_missing |
A key that :requires another was written without it (:offset without :buffer) |
Two name rules are the compiler’s rather than the validator’s, so they carry a located message and no code: a name already declared by another kind (duplicate name ‘x’: already declared as a (buffer …) at line L) and a built-in spelling used as a name (‘canvas’ is a builtin symbol and cannot name a (buffer …)).
Related
Section titled “Related”- Expressions - Compile-time arithmetic
(define …)- Define constants- SJON language docs - Cross-reference resolution is a SJON feature