(define …)
Defines a compile-time numeric constant collected into the emitter’s expression environment, referenced throughout the document.
Syntax
Section titled “Syntax”(define :name NAME :value 2048)| Key | Type | Required | Description |
|---|---|---|---|
:name |
symbol | Yes | Constant name |
:value |
number | Yes | A real (negative and fractional allowed), an expression over other constants, or another constant’s name |
The value is usually a literal. An expression works too, and so does another
constant’s name, declared before or after: constants resolve document-wide, and
the one shape that cannot resolve is a cycle, refused on the first constant in
it. Nothing about the define says “integer”: the slot that reads it decides,
so (define :name HALF :value 0.5) is fine in :clear-value and a located
refusal in :vertex-count.
Simple Values
Section titled “Simple Values”(define :name NUM_PARTICLES :value 2048)(define :name SAMPLE_COUNT :value 4)(define :name WORKGROUP_SIZE :value 64)A defined constant is a value in every numeric slot: on its own, or inside a compile-time expression.
(define :name NUM_PARTICLES :value 4096)(define :name PARTICLE_SIZE :value 16)(define :name WORKGROUP_SIZE :value 64)
(buffer :name particles :size (* NUM_PARTICLES PARTICLE_SIZE) :usage [storage])
(compute-pass :name simulate :pipeline simPipeline (dispatch :workgroups [(ceil (/ NUM_PARTICLES WORKGROUP_SIZE))]))The expression (* NUM_PARTICLES PARTICLE_SIZE) evaluates at compile time over
the (define …) constants. See Expressions for the
full function and operator set.
Where a bare name works
Section titled “Where a bare name works”Every numeric slot takes one: a byte size or offset, a binding or group index, a
pool size, a device limit, a sampler’s :max-anisotropy, a vector element, as
well as the draw and dispatch counts.
(define :name SIZE :value 32768)(define :name ANISO :value 4)(define :name WG :value 64)
(buffer :name particles :size SIZE :usage [storage])(sampler :name smooth :max-anisotropy ANISO :mag-filter linear :min-filter linear :mipmap-filter linear)
(compute-pass :name step :pipeline simPipeline (dispatch :workgroups [WG 1 1]))The name is checked against the document’s (define …) forms, so a misspelling
is a not_cross_ref diagnostic on that slot rather than a silent default, and a
literal that misses the slot’s own bounds still reports those bounds.
Two slots are the exception, for the same reason: their symbol spelling is
already taken by a member set, so a bare name is not recognised as a constant
and the constant is written as arithmetic instead. :write-mask (member all)
reports a bare name as not_member; write :write-mask (* MASK 1). A
(wasm-call :args […]) element (members canvas-width, canvas-height,
time-total, time-delta) reports it as union_no_branch_matched; write
(* SCALE 1).
Examples
Section titled “Examples”MSAA Configuration
Section titled “MSAA Configuration”One constant keeps the render target and the pipeline in step: the same name
goes in the texture’s :sample-count and in the pipeline’s
(multisample :count …).
(define :name SAMPLE_COUNT :value 4)
(texture :name msaaTarget :size canvas :format preferred-canvas-format :usage [render-attachment] :sample-count SAMPLE_COUNT)Compute Configuration
Section titled “Compute Configuration”(define :name NUM_PARTICLES :value 8192)(define :name PARTICLE_STRIDE :value 32)(define :name WORKGROUP_SIZE :value 256)
(buffer :name particleBuffer :size (* NUM_PARTICLES PARTICLE_STRIDE) :usage [storage vertex])
(compute-pass :name update :pipeline simPipeline (dispatch :workgroups [(ceil (/ NUM_PARTICLES WORKGROUP_SIZE))]))
(render-pass :name render (color-attachment :view context-current-texture :clear-value [0 0 0 1] :load-op clear :store-op store) :pipeline renderPipeline (draw :vertex-count 6 :instance-count NUM_PARTICLES))Size Calculations
Section titled “Size Calculations”(define :name VERTEX_COUNT :value 1000)(define :name FLOATS_PER_VERTEX :value 8)(define :name BYTES_PER_FLOAT :value 4)
(buffer :name vertices :size (* VERTEX_COUNT FLOATS_PER_VERTEX BYTES_PER_FLOAT) :usage [vertex])Defines are document-wide and can be referenced:
- Inside any compile-time expression,
(* NAME 16)or(ceil (/ NAME 64)) - As a bare name in any numeric slot,
:size NAME - From another
(define …), before or after it, whose:valuemay be an expression over the rest
Order never matters: a (define …) may sit below the form or the constant that
uses it, because the whole document’s constants are resolved before lowering
and emission run. A cycle is the one refusal.
Validation Rules
Section titled “Validation Rules”| Rule | Error |
|---|---|
Two (define …) forms may not share a name |
duplicate_cross_ref_target |
| A name already taken by another form is a clash: names are one namespace | duplicate name 'N': already declared as a (buffer …) at line L |
| A constant that depends on itself, directly or through another | (define :name A) depends on itself (located) |
| A fractional or negative constant in an integer slot | :vertex-count evaluates to 1.5, and this slot takes an integer (located on the slot) |
| A referenced constant must be declared | not_cross_ref |
:write-mask and a (wasm-call :args …) element take arithmetic, not a bare name |
not_member / union_no_branch_matched |
Comparison with WGSL
Section titled “Comparison with WGSL”Unlike WGSL const, a (define …) is resolved at PNGine compile time (not
shader compile time). Use it for values that affect resource creation.
; PNGine compile time(define :name SIZE :value 256)
(buffer :name buf :size SIZE :usage [uniform]) ; 256-byte buffer
; For shader constants, use WGSL const inside the module:(shader-module :name code :code """ const SIZE: u32 = 256; // WGSL compile time @compute @workgroup_size(64) fn main(@builtin(global_invocation_id) id: vec3u) { if (id.x >= SIZE) { return; } }""")Related
Section titled “Related”- Expressions - Full expression syntax
(buffer …)- Use in size calculations(compute-pass …)- Use in dispatch