@solidrt/flux-types 0.0.50 → 0.0.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -46,12 +46,12 @@ rely on must be named.
46
46
 
47
47
  - `Flux` global (`version`, `capabilities`).
48
48
  - `flux:*` modules: `flux:http`, `flux:fs`, `flux:sqlite`, `flux:subprocess`,
49
- `flux:p2p`, `flux:net`, `flux:mdns`, `flux:process`, `flux:path`, `flux:wasm`, `flux:ffi`,
49
+ `flux:p2p`, `flux:net`, `flux:mdns`, `flux:process`, `flux:tty`, `flux:path`, `flux:wasm`, `flux:ffi`,
50
50
  and (on a gui-enabled runtime)
51
- `flux:camera`, `flux:microphone`, `flux:audio`, `flux:gpu`.
51
+ `flux:camera`, `flux:microphone`, `flux:audio`, `flux:gpu`, `flux:spatial`.
52
52
  - Web-standard globals: `console`, `fetch` + `Headers`/`Request`/`Response`,
53
53
  `setTimeout`/`setInterval`/`queueMicrotask`, `performance`, `WebSocket`,
54
- `TextEncoder`/`TextDecoder`. These are deliberate subsets matching exactly what
54
+ `TextEncoder`/`TextDecoder`, `crypto.subtle.digest`. These are deliberate subsets matching exactly what
55
55
  the runtime implements.
56
56
  - GUI globals (gui-enabled runtime only): `requestAnimationFrame` /
57
57
  `cancelAnimationFrame` (web-standard names, so kept global).
package/gui/audio.d.ts CHANGED
@@ -1,7 +1,10 @@
1
- // Sound playback (gui-enabled runtime only). The imperative primitive; `play`
2
- // decodes and starts a clip in one call, while `load`/`loadPcm`/`stream` yield
3
- // a Clip that starts cheap overlapping Playbacks. Handles carry controls bound
4
- // to just that clip or playback, so raw ids never leave the runtime.
1
+ // Sound playback (gui-enabled runtime only: feature-detect with
2
+ // `Flux.capabilities.includes("audio")` before importing on a runtime that may
3
+ // lack it - a static import fails at module load there). The imperative
4
+ // primitive; `play` decodes and starts a clip in one call, while
5
+ // `load`/`loadPcm`/`stream` yield a Clip that starts cheap overlapping
6
+ // Playbacks. Handles carry controls bound to just that clip or playback, so
7
+ // raw ids never leave the runtime.
5
8
 
6
9
  declare module "flux:audio" {
7
10
  /** Options for {@link play} and {@link Clip.play}. */
@@ -16,31 +19,97 @@ declare module "flux:audio" {
16
19
  * all, which for a mono clip is about 3 dB louder than `pan: 0`.
17
20
  */
18
21
  pan?: number
22
+ /**
23
+ * Playback rate: 1.0 plays as loaded, higher is faster and higher-pitched,
24
+ * lower slower and deeper (a plain resample, no formant correction).
25
+ * Clamped to [0.01, 100]. Defaults to 1.0.
26
+ */
27
+ rate?: number
28
+ /**
29
+ * Fade in from silence over this many milliseconds (sample-accurate).
30
+ * Defaults to 0 (start at full level).
31
+ */
32
+ fadeInMs?: number
33
+ /**
34
+ * Name of the bus this playback belongs to, so `stop({ bus })` can stop
35
+ * the whole group at once. Buses are plain names created by use - no
36
+ * setup call, and (for now) no per-bus gain.
37
+ */
38
+ bus?: string
39
+ }
40
+
41
+ /** Options for the live setters ({@link Playback.setGain} and friends). */
42
+ type RampOptions = {
43
+ /**
44
+ * Reach the new value by ramping over this many milliseconds instead of
45
+ * jumping. Linear, stepped by the engine at control rate (about every
46
+ * 10 ms), so a fade stays smooth regardless of the app's frame rate. A
47
+ * later set on the same parameter takes over from the ramp's current
48
+ * value; omitted (or 0) sets immediately and cancels any ramp in flight.
49
+ */
50
+ rampMs?: number
51
+ }
52
+
53
+ /** Options for {@link Playback.stop} and the module-level {@link stop}. */
54
+ type StopOptions = {
55
+ /**
56
+ * Fade to silence over this many milliseconds before stopping
57
+ * (sample-accurate) instead of cutting immediately. The playback keeps
58
+ * playing while it fades; ended() turns true once the fade completes.
59
+ */
60
+ fadeOutMs?: number
61
+ }
62
+
63
+ /** Options for the module-level {@link stop}. */
64
+ type StopAllOptions = StopOptions & {
65
+ /**
66
+ * Stop only the playbacks on this bus (see {@link PlayOptions.bus})
67
+ * instead of everything.
68
+ */
69
+ bus?: string
19
70
  }
20
71
 
21
72
  /** One playing instance of a clip, with live controls bound to it. */
22
73
  type Playback = {
23
- /** Stop this playback. A no-op if it already finished. */
24
- stop(): void
74
+ /**
75
+ * Stop this playback. A no-op if it already finished. Stopping is not
76
+ * pausing: a stopped playback is gone for good, including a looping one -
77
+ * to silence it temporarily, ramp its gain to 0 instead.
78
+ */
79
+ stop(options?: StopOptions): void
25
80
  /**
26
81
  * Change the volume while playing. A finite number >= 0; 1.0 is the clip's
27
82
  * own level. A no-op after the playback finished.
28
83
  */
29
- setGain(gain: number): void
84
+ setGain(gain: number, options?: RampOptions): void
30
85
  /**
31
86
  * Move the stereo position while playing (see {@link PlayOptions.pan}).
87
+ * A ramped set on a never-panned playback sweeps from center. A no-op
88
+ * after the playback finished.
89
+ */
90
+ setPan(pan: number, options?: RampOptions): void
91
+ /**
92
+ * Change the playback rate while playing (see {@link PlayOptions.rate}) -
93
+ * a live rate sweep is how an engine revs or a doppler pass falls.
32
94
  * A no-op after the playback finished.
33
95
  */
34
- setPan(pan: number): void
96
+ setRate(rate: number, options?: RampOptions): void
35
97
  /** Whether playback finished, naturally or via {@link stop}. */
36
98
  ended(): boolean
37
99
  }
38
100
 
39
101
  /** A loaded clip that can be played without re-decoding. */
40
102
  type Clip = {
41
- /** Start a fresh overlapping playback of this clip. */
103
+ /**
104
+ * Start a fresh overlapping playback of this clip. Throws once 256
105
+ * playbacks are live at once - a guard that turns a runaway play() loop
106
+ * into an error instead of a saturated mixer.
107
+ */
42
108
  play(options?: PlayOptions): Playback
43
- /** Release the clip. Playbacks already running keep going. */
109
+ /**
110
+ * Release the clip. Playbacks already running keep going; `play()` after
111
+ * unloading throws.
112
+ */
44
113
  unload(): void
45
114
  }
46
115
 
@@ -80,6 +149,41 @@ declare module "flux:audio" {
80
149
  * playback; do not overlap a stream with itself. Call `unload()` when done.
81
150
  */
82
151
  export function stream(source: ReturnType<typeof import("flux:fs").file>): Clip
83
- /** Stop every playing sound. */
84
- export function stop(): void
152
+ /**
153
+ * Stop every playing sound - or just one bus with `{ bus }` - fading it
154
+ * out first if asked. Stopping is not pausing: stopped playbacks (looping
155
+ * ones included) cannot be restarted - to silence a group temporarily,
156
+ * ramp gains to 0 instead.
157
+ */
158
+ export function stop(options?: StopAllOptions): void
159
+ /**
160
+ * Scale the whole mix: every playing and future flux:audio playback, on top
161
+ * of per-playback gains (1.0 = unchanged, 0 = silence). A finite number
162
+ * >= 0; ramps like the per-playback setters. Resets to 1.0 when the app
163
+ * reloads.
164
+ */
165
+ export function setMasterGain(gain: number, options?: RampOptions): void
166
+ /**
167
+ * Scale one bus (see {@link PlayOptions.bus}): a playback's audible level
168
+ * is its own gain x its bus's gain x the master gain, each layer set
169
+ * independently - none overwrites another. Applies to live and future
170
+ * playbacks on the bus, defaults to 1.0, resets to 1.0 when the app
171
+ * reloads, and ramps like the other gain setters.
172
+ *
173
+ * NOT IMPLEMENTED YET: calling this throws. Until it lands, keep the bus
174
+ * gain in the app and fold it into each voice's setGain - one ramped
175
+ * write per change:
176
+ *
177
+ * ```ts
178
+ * let musicGain = 0.3
179
+ * for (let v of musicVoices) v.setGain(voiceGain * musicGain, { rampMs: 200 })
180
+ * ```
181
+ */
182
+ export function setBusGain(bus: string, gain: number, options?: RampOptions): void
183
+ /**
184
+ * The mixer's output sample rate in Hz. Synthesize PCM at this rate and
185
+ * {@link loadPcm} feeds it to the mixer without a resample. Opens the audio
186
+ * device on first use, like the load and play calls.
187
+ */
188
+ export function outputSampleRate(): number
85
189
  }
package/gui/camera.d.ts CHANGED
@@ -65,9 +65,10 @@ declare module "flux:camera" {
65
65
  * if permission is denied, and resolves once the first frame is ready. On
66
66
  * Linux a session that delivers neither within 10 seconds rejects with a
67
67
  * timeout error and releases the device (a wedged capture backend would
68
- * otherwise hold it and never settle). Rejects with "camera subsystem is
69
- * starting" while the subsystem is still coming up - wait for listCameras
70
- * to report a device before opening.
68
+ * otherwise hold it and never settle). The first open also starts the
69
+ * camera subsystem and waits for it (on every platform, up to 10 seconds,
70
+ * then rejects with a timeout), so there is no need to poll listCameras
71
+ * before opening.
71
72
  */
72
73
  export function open(options?: CameraOpenOptions): Promise<CameraSession>
73
74
  /**
package/gui/gpu.d.ts CHANGED
@@ -17,11 +17,12 @@
17
17
  // they return.
18
18
  //
19
19
  // Sampling is a per-texture property declared at creation: every create path
20
- // accepts `{ filter?, wrap? }` ("linear"/"nearest", "clamp"/"repeat";
21
- // defaults linear + clamp for every origin). The state follows the id
22
- // everywhere it is sampled - shader passes and `<texture>` display alike -
23
- // and survives id-stable resizes. It cannot be changed after creation. No
24
- // mipmaps exist.
20
+ // accepts `{ filter?, wrap?, mipmap? }` ("linear"/"nearest", "clamp"/"repeat",
21
+ // boolean; defaults linear + clamp + no mips for every origin). The state
22
+ // follows the id everywhere it is sampled - shader passes and `<texture>`
23
+ // display alike - and survives id-stable resizes. It cannot be changed after
24
+ // creation. `mipmap: true` keeps a mip chain on the id, regenerated after
25
+ // every upload or render, so shader sampling minifies without aliasing.
25
26
  //
26
27
  // Compositing several targets is a render-tree job, not a shader one: stack
27
28
  // `<texture>` elements and set their `blendMode` (the full Skia set, e.g.
@@ -103,8 +104,9 @@ declare module "flux:gpu" {
103
104
  * call site: a name with no active uniform, a value whose length does not
104
105
  * fit the declared type, a `sampler2D` named here (samplers bind via
105
106
  * `textures`), or a value that is not a number / number array throws.
106
- * Reflection only sees active uniforms, so a uniform that is declared but
107
- * optimized out counts as unknown - remove the write (or use the uniform).
107
+ * A uniform that is declared but optimized out by the compiler is accepted
108
+ * with a warning and the write is skipped, so one param object can drive
109
+ * shader variants that do not all use every uniform.
108
110
  * An `undefined` value is skipped, so conditional spreads stay usable.
109
111
  */
110
112
  export type ShaderParams = Record<string, number | number[]>
@@ -118,8 +120,29 @@ declare module "flux:gpu" {
118
120
  * sampler2D inputs AND `<texture src>` display (a "nearest" texture
119
121
  * upscales with hard pixels on screen - the pixel-art path). `wrap` only
120
122
  * matters to shaders sampling outside 0..1; the display draw never tiles.
121
- */
122
- export type SamplerOptions = { filter?: FilterMode; wrap?: WrapMode }
123
+ *
124
+ * `mipmap` (default false) keeps a mip chain on the id: rebuilt by the
125
+ * runtime after every upload (data textures) and every render (targets,
126
+ * automatically - nothing to schedule), and used by shader sampling when
127
+ * the texture is minified (trilinear for "linear", per-level nearest for
128
+ * "nearest"). Without it minification skips texels and aliases. Only
129
+ * shader sampling minifies through the chain; the `<texture>` display
130
+ * draw samples the full-size level. Regeneration is one GPU pass per
131
+ * upload or render, so a per-frame texture pays it per frame.
132
+ */
133
+ export type SamplerOptions = { filter?: FilterMode; wrap?: WrapMode; mipmap?: boolean }
134
+ /**
135
+ * One `textures` binding value: a texture id, sampled with the texture's
136
+ * own declared state, or `{ id, filter?, wrap? }` to sample that id with a
137
+ * different filter and/or wrap in this binding only (blur a "nearest"
138
+ * atlas linearly; tile a clamped target in one consumer). The override is
139
+ * a per-pass deviation: the texture's own state stays what `<texture>`
140
+ * paints and what every binding without an override uses. `mipmap` is
141
+ * not overridable - the chain either exists on the id or it does not.
142
+ */
143
+ export type TextureBinding = TextureId | { id: TextureId; filter?: FilterMode; wrap?: WrapMode }
144
+ /** Sampler2D inputs by uniform name; see {@link TextureBinding}. */
145
+ export type TextureBindings = Record<string, TextureBinding>
123
146
  /**
124
147
  * A free-form debug name every create accepts (WebGPU's label): surfaced by
125
148
  * the dev server's GPU inventory (get_gpu_resources) and in engine log
@@ -243,7 +266,7 @@ declare module "flux:gpu" {
243
266
  width: number,
244
267
  height: number,
245
268
  params?: ShaderParams | null,
246
- opts?: { textures?: Record<string, TextureId> } & SamplerOptions & LabelOption,
269
+ opts?: { textures?: TextureBindings } & SamplerOptions & LabelOption,
247
270
  ): TextureId
248
271
  /**
249
272
  * Compile a single shader stage from raw GLSL ES: the primitive under
@@ -310,9 +333,16 @@ declare module "flux:gpu" {
310
333
  * arithmetic. Declaring any makes `instanceBuffer` required on every
311
334
  * entry drawn with this pipeline. A mat4 per instance is its four
312
335
  * vec4 columns, reassembled in the shader (attributes have no matrix
313
- * formats, as in WebGPU).
336
+ * formats, as in WebGPU). Instance N always reads record N of the
337
+ * entry's buffer, from record 0 (ES 3.0 has no base instance), so
338
+ * several independently culled groups cannot share one buffer as
339
+ * sub-ranges: give each group its own `instanceBuffer` and entry, and
340
+ * cull it by `instanceCount`. Each attribute's `slot` (default 0)
341
+ * picks a buffer of the entry's `instanceBuffers` list (see
342
+ * {@link InstanceAttribute}); a pipeline using only slot 0 binds via
343
+ * the plain `instanceBuffer` key.
314
344
  */
315
- instanceAttributes?: VertexAttribute[]
345
+ instanceAttributes?: InstanceAttribute[]
316
346
  topology?: Topology
317
347
  blend?: BlendMode
318
348
  cull?: CullMode
@@ -364,6 +394,18 @@ declare module "flux:gpu" {
364
394
  * read-modify-write of its own pixels (decay, blur, simulation) still
365
395
  * ping-pongs across two manual targets - a pass can never sample the
366
396
  * texture it writes.
397
+ *
398
+ * `samples` (1, 2, 4 or 8; default 1) multisamples the target's storage:
399
+ * edges of filled geometry get coverage-weighted AA instead of binary
400
+ * jaggies. Storage only - the texture id still names a single-sample
401
+ * image, so display, sampling, `readTexture` and `copyTexture` are
402
+ * unaffected. The count is clamped to the device maximum and a
403
+ * configuration the driver refuses falls back to single-sample with a
404
+ * warning (read the effective value off the resource inventory). Cannot
405
+ * combine with `loadOp: "load"` (throws): multisampled storage cannot
406
+ * load the previous contents. Tiled mobile GPUs resolve in-tile
407
+ * (EXT_multisampled_render_to_texture); everywhere else a resolve blit
408
+ * follows each pass.
367
409
  */
368
410
  export function createShaderTarget(
369
411
  pipeline: RenderPipelineId,
@@ -371,12 +413,16 @@ declare module "flux:gpu" {
371
413
  height: number,
372
414
  params?: ShaderParams | null,
373
415
  opts?: {
374
- textures?: Record<string, TextureId>
416
+ textures?: TextureBindings
375
417
  buffer?: BufferId
376
418
  instanceBuffer?: BufferId
419
+ /** One buffer per instance slot of the pipeline (index = the
420
+ * attributes' `slot`); pass this OR `instanceBuffer`, not both. */
421
+ instanceBuffers?: BufferId[]
377
422
  clearColor?: [number, number, number, number]
378
423
  render?: "auto" | "manual"
379
424
  loadOp?: "clear" | "load"
425
+ samples?: 1 | 2 | 4 | 8
380
426
  } & (DrawRange | (IndexBinding & IndexRange)) &
381
427
  SamplerOptions &
382
428
  LabelOption,
@@ -388,6 +434,16 @@ declare module "flux:gpu" {
388
434
  * immediately.
389
435
  */
390
436
  export function destroyProgram(id: ProgramId): void
437
+ /**
438
+ * The vertex attributes a linked program actually reads (name and format),
439
+ * as the compiler left them: an `in` the vertex stage never uses is not
440
+ * listed, and a type no layout can feed (a matrix, an integer vector) is
441
+ * rejected at linkProgram. This is the list a pipeline over the program
442
+ * must cover between `attributes` and `instanceAttributes` - an uncovered
443
+ * or mis-formatted one throws at createRenderPipeline. Answered locally,
444
+ * no GPU round trip.
445
+ */
446
+ export function programAttributes(program: ProgramId): VertexAttribute[]
391
447
  export type Topology = "points" | "lines" | "line-strip" | "triangles" | "triangle-strip"
392
448
  /**
393
449
  * Blending for a pipeline's own draw. "none" (default) overwrites:
@@ -439,6 +495,17 @@ declare module "flux:gpu" {
439
495
  * shader's `in` declarations.
440
496
  */
441
497
  export type VertexAttribute = { name: string; format: "f32" | "vec2" | "vec3" | "vec4" }
498
+ /**
499
+ * One float attribute of a per-instance record. `slot` (default 0) picks
500
+ * which buffer of the entry's `instanceBuffers` list the attribute
501
+ * fetches from: attributes sharing a slot interleave into one record in
502
+ * list order, distinct slots are distinct buffers with their own strides
503
+ * - which is what lets two writers own instance data independently (a
504
+ * core-written pose buffer beside an app-written style buffer). Slots
505
+ * must be dense from 0, at most 4; a single-slot pipeline (every `slot`
506
+ * omitted) binds via the plain `instanceBuffer` key.
507
+ */
508
+ export type InstanceAttribute = VertexAttribute & { slot?: number }
442
509
  /**
443
510
  * A pipeline target's draw as data, WebGPU-style: `firstVertex` +
444
511
  * `vertexCount` pick the vertex range `[firstVertex, firstVertex +
@@ -453,7 +520,9 @@ declare module "flux:gpu" {
453
520
  * buffer bound, `instanceCount` is bounds-checked against it like every
454
521
  * fetch (instances 0..N-1 each read one record). Two GL facts worth
455
522
  * knowing: `gl_VertexID` includes `firstVertex` (as in WebGPU), and
456
- * `gl_InstanceID` always counts from 0 - ES 3.0 has no base instance.
523
+ * `gl_InstanceID` always counts from 0 - ES 3.0 has no base instance, so
524
+ * instance N reads record N of the entry's `instanceBuffer` and a group
525
+ * that is culled independently needs its own buffer and entry.
457
526
  */
458
527
  export type DrawRange = { firstVertex?: number; vertexCount?: number; instanceCount?: number }
459
528
  /**
@@ -486,6 +555,28 @@ declare module "flux:gpu" {
486
555
  * there is no base vertex (ES 3.0, like ES 3.0's missing base instance).
487
556
  */
488
557
  export type IndexRange = { firstIndex?: number; indexCount?: number; instanceCount?: number }
558
+ /**
559
+ * A draw entry's buffer swap: each key present re-points that role of the
560
+ * entry at another {@link createBuffer} buffer, absent keys keep their
561
+ * current buffer. Replace-only - the roles an entry fills are pipeline
562
+ * layout state (`attributes` needs a `buffer`, `instanceAttributes` an
563
+ * `instanceBuffer`) and indexing is its draw vocabulary, so naming a role
564
+ * the entry does not fill throws; `indexBuffer` travels with
565
+ * `indexFormat` as at create. The entry's current range is kept and
566
+ * rechecked against the new buffers' sizes: a swap to a buffer too small
567
+ * for the live range throws (shrink the range first); a larger buffer
568
+ * never does. This is the growth primitive: a population outgrowing its
569
+ * instance buffer creates a larger one, writes it, swaps, and destroys
570
+ * the old (the entry holds the old buffer alive until the swap lands, so
571
+ * either order is safe). `instanceBuffer` swaps slot 0;
572
+ * `instanceBuffers` swaps every slot at once and must fill exactly the
573
+ * slots the entry fills (pass one spelling or the other).
574
+ */
575
+ export type BufferUpdate = {
576
+ buffer?: BufferId
577
+ instanceBuffer?: BufferId
578
+ instanceBuffers?: BufferId[]
579
+ } & ({} | IndexBinding)
489
580
 
490
581
  /**
491
582
  * Compile a GLSL ES vertex+fragment pipeline into an offscreen texture of
@@ -525,12 +616,15 @@ declare module "flux:gpu" {
525
616
  height: number,
526
617
  params?: ShaderParams | null,
527
618
  opts?: {
528
- textures?: Record<string, TextureId>
619
+ textures?: TextureBindings
529
620
  attributes?: VertexAttribute[]
530
621
  buffer?: BufferId
531
622
  /** See {@link createRenderPipeline}'s `instanceAttributes`. */
532
- instanceAttributes?: VertexAttribute[]
623
+ instanceAttributes?: InstanceAttribute[]
533
624
  instanceBuffer?: BufferId
625
+ /** One buffer per instance slot (index = the attributes' `slot`);
626
+ * pass this OR `instanceBuffer`, not both. */
627
+ instanceBuffers?: BufferId[]
534
628
  topology?: Topology
535
629
  depth?: boolean
536
630
  depthWrite?: boolean
@@ -539,6 +633,7 @@ declare module "flux:gpu" {
539
633
  clearColor?: [number, number, number, number]
540
634
  render?: "auto" | "manual"
541
635
  loadOp?: "clear" | "load"
636
+ samples?: 1 | 2 | 4 | 8
542
637
  } & (DrawRange | (IndexBinding & IndexRange)) &
543
638
  SamplerOptions &
544
639
  LabelOption,
@@ -546,10 +641,34 @@ declare module "flux:gpu" {
546
641
 
547
642
  /**
548
643
  * Create a vertex buffer from raw bytes (interleave attribute data to match
549
- * the pipeline's attribute list). Buffer ids are their own space, separate
550
- * from texture ids.
644
+ * the pipeline's attribute list), or from a byte length alone - a zeroed
645
+ * buffer, the natural create when the contents arrive through the write
646
+ * lease ({@link beginBufferWrite}). Buffer ids are their own space,
647
+ * separate from texture ids. Size is fixed for the id's lifetime: reserve
648
+ * the maximum up front and publish a prefix.
649
+ */
650
+ export function createBuffer(data: Uint8Array | number, opts?: LabelOption): BufferId
651
+ /**
652
+ * Open a zero-copy write into a vertex buffer: returns an ArrayBuffer over
653
+ * runtime-owned memory exactly the buffer's size. Write into it in place
654
+ * (wrap it in a Float32Array or any view), then publish with
655
+ * {@link endBufferWrite} - no copy happens anywhere on the CPU path.
656
+ *
657
+ * Contents are UNSPECIFIED at begin: a recycled block holds what was
658
+ * published the time before last, so fill everything you publish. One open
659
+ * write per buffer id at a time (a second begin throws). The view is
660
+ * detached at end/destroy - a retained reference becomes zero-length, and
661
+ * writes through it are inert, never a race.
662
+ */
663
+ export function beginBufferWrite(id: BufferId): ArrayBuffer
664
+ /**
665
+ * Publish the open write's first `byteLength` bytes at offset 0 (default:
666
+ * the whole buffer) and close the lease. `byteLength` 0 cancels: the lease
667
+ * closes and nothing is published. Always closes the lease, error or not;
668
+ * throws when no write is open or `byteLength` exceeds the buffer size.
669
+ * Pipelines drawing from the buffer re-render, like {@link writeBuffer}.
551
670
  */
552
- export function createBuffer(data: Uint8Array, opts?: LabelOption): BufferId
671
+ export function endBufferWrite(id: BufferId, byteLength?: number): void
553
672
  /**
554
673
  * Overwrite part of a vertex buffer at `byteOffset` (default 0), within the
555
674
  * size it was created with. Pipelines drawing from the buffer re-render
@@ -575,9 +694,13 @@ declare module "flux:gpu" {
575
694
  * (On a manual target nothing renders here; the range applies at its next
576
695
  * {@link renderTarget}.) An indexed target takes the {@link IndexRange}
577
696
  * spelling instead, bounds-checked against its index buffer; the pair
578
- * that does not match the target's mode throws.
697
+ * that does not match the target's mode throws. Buffer keys
698
+ * ({@link BufferUpdate}) swap the target's buffers in the same call: the
699
+ * merged range is checked against the swapped buffers, so one call grows a
700
+ * buffer and extends the range into it, and a call that throws changes
701
+ * nothing (range and buffers commit together or not at all).
579
702
  */
580
- export function setDraw(id: TextureId, draw: DrawRange | IndexRange): void
703
+ export function setDraw(id: TextureId, draw: (DrawRange | IndexRange) & BufferUpdate): void
581
704
  /**
582
705
  * Create a draw target: a render target whose contents are an ordered,
583
706
  * mutable LIST of draws - one render clears once, then executes every
@@ -593,6 +716,10 @@ declare module "flux:gpu" {
593
716
  * occlusion work. It is the storage half of the depth story; whether an
594
717
  * entry tests/writes depth is its pipeline's `depth`/`depthWrite` state,
595
718
  * and adding a depth-testing pipeline to a target without storage throws.
719
+ * `depth: "texture"` is the same storage as a SAMPLEABLE depth texture
720
+ * with an id of its own - {@link depthTexture} - for shadow maps,
721
+ * depth-of-field, SSAO; it cannot combine with `samples` (a multisampled
722
+ * depth texture is not sampleable; the create throws).
596
723
  *
597
724
  * `params` seeds the target's SHARED params - the target-level values
598
725
  * every entry reads, the same live channel {@link setTargetParams} drives
@@ -608,8 +735,9 @@ declare module "flux:gpu" {
608
735
  * "auto"` target re-renders exactly when its entries or their inputs
609
736
  * change - a static scene costs zero passes, however many entries it
610
737
  * holds, and one render is ONE pass however many entries it draws.
611
- * `render: "manual"` and `loadOp: "load"` compose exactly as on
612
- * {@link createShaderTarget}. With no entries a render is the clear alone.
738
+ * `render: "manual"`, `loadOp: "load"` and `samples` compose exactly as
739
+ * on {@link createShaderTarget}. With no entries a render is the clear
740
+ * alone.
613
741
  * Returns a texture id (display, resize, destroy like any target; entries
614
742
  * die with it).
615
743
  */
@@ -618,14 +746,34 @@ declare module "flux:gpu" {
618
746
  height: number,
619
747
  params?: ShaderParams | null,
620
748
  opts?: {
621
- depth?: boolean
622
- textures?: Record<string, TextureId>
749
+ depth?: boolean | "texture"
750
+ textures?: TextureBindings
623
751
  clearColor?: [number, number, number, number]
624
752
  render?: "auto" | "manual"
625
753
  loadOp?: "clear" | "load"
754
+ samples?: 1 | 2 | 4 | 8
626
755
  } & SamplerOptions &
627
756
  LabelOption,
628
757
  ): TextureId
758
+ /**
759
+ * The depth texture of a draw target created with `depth: "texture"`: a
760
+ * texture id of its own, stable for the target's life (a
761
+ * {@link setTargetSize} follows the color), holding the target's depth
762
+ * after every render - 24-bit window depth in 0..1, read as `.r` from a
763
+ * `sampler2D`. Bind it anywhere a texture binds ({@link setTargetTextures}
764
+ * for a whole scene target, an entry's `textures`, a fragment target's
765
+ * inputs); the dependency graph treats a binding to it as a binding to its
766
+ * target, so the depth pass renders first and a target sampling its own
767
+ * depth throws. SAMPLER-ONLY: it is not an upload texture and not a
768
+ * readback source (`readTexture`/`copyTexture` throw - render it through a
769
+ * pass to read it), its sampling is fixed at `nearest`/`clamp` (a depth
770
+ * texture is only complete at nearest without a comparison mode - filter
771
+ * in the shader, e.g. a PCF loop), and it dies with its target
772
+ * (`destroyTexture` on it throws). Displaying it via `<texture src>`
773
+ * shows the depth in the red channel. Throws for a target without texture
774
+ * depth.
775
+ */
776
+ export function depthTexture(target: TextureId): TextureId
629
777
  /**
630
778
  * Append a draw entry to a draw target: `pipeline` draws `opts.buffer`
631
779
  * (required when the pipeline declares attributes) with its own `params`
@@ -660,9 +808,12 @@ declare module "flux:gpu" {
660
808
  pipeline: RenderPipelineId,
661
809
  params?: ShaderParams | null,
662
810
  opts?: {
663
- textures?: Record<string, TextureId>
811
+ textures?: TextureBindings
664
812
  buffer?: BufferId
665
813
  instanceBuffer?: BufferId
814
+ /** One buffer per instance slot of the pipeline (index = the
815
+ * attributes' `slot`); pass this OR `instanceBuffer`, not both. */
816
+ instanceBuffers?: BufferId[]
666
817
  before?: DrawId
667
818
  } & (DrawRange | (IndexBinding & IndexRange)),
668
819
  ): DrawId
@@ -730,7 +881,7 @@ declare module "flux:gpu" {
730
881
  * everywhere it is declared, and each entry's effective inputs (its own
731
882
  * plus the applicable shared ones) must fit the device's texture units.
732
883
  */
733
- export function setTargetTextures(target: TextureId, textures: Record<string, TextureId>): void
884
+ export function setTargetTextures(target: TextureId, textures: TextureBindings): void
734
885
  /**
735
886
  * Resize a render target of any kind in place and re-render it: the id,
736
887
  * compiled programs, last-applied params, sampler bindings, and draw
@@ -745,13 +896,19 @@ declare module "flux:gpu" {
745
896
  * validation, and cycle rules. Entries bind independently - two entries
746
897
  * may bind the same uniform name to different sources.
747
898
  */
748
- export function setDrawTextures(target: TextureId, draw: DrawId, textures: Record<string, TextureId>): void
899
+ export function setDrawTextures(target: TextureId, draw: DrawId, textures: TextureBindings): void
749
900
  /**
750
901
  * Update one draw entry's draw range: {@link setDraw} addressed to a
751
902
  * single entry, same partial merge, bounds validation, and vocabulary
752
903
  * rule (an indexed entry speaks {@link IndexRange}).
753
904
  */
754
905
  export function setDrawRange(target: TextureId, draw: DrawId, update: DrawRange | IndexRange): void
906
+ /**
907
+ * Swap one draw entry's buffers: the {@link BufferUpdate} half of
908
+ * {@link setDraw} addressed to a single entry, same replace-only rule and
909
+ * range recheck.
910
+ */
911
+ export function setDrawBuffers(target: TextureId, draw: DrawId, update: BufferUpdate): void
755
912
  /**
756
913
  * Reorder a draw target's list. `order` must name every current entry
757
914
  * exactly once - a full permutation of the live {@link DrawId}s; a