@solidrt/flux-types 0.0.40 → 0.0.41

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/gui/camera.d.ts CHANGED
@@ -3,6 +3,8 @@
3
3
  // to a bound session object, so the raw handle never leaves the runtime.
4
4
 
5
5
  declare module "flux:camera" {
6
+ import type { TextureId } from "flux:gpu"
7
+
6
8
  /** A camera device from {@link listCameras}. */
7
9
  type CameraDevice = {
8
10
  /** Device id to pass as `open({ camera })`. */
@@ -38,7 +40,7 @@ declare module "flux:camera" {
38
40
  /** An opened camera session: the frame texture plus controls bound to it. */
39
41
  type CameraSession = {
40
42
  /** GPU texture id the latest frame is uploaded into (use as a texture source). */
41
- texture: number
43
+ texture: TextureId
42
44
  /** Frame width in pixels. */
43
45
  width: number
44
46
  /** Frame height in pixels. */
@@ -52,11 +54,20 @@ declare module "flux:camera" {
52
54
  close(): void
53
55
  }
54
56
 
55
- /** List the available camera devices. */
57
+ /**
58
+ * List the available camera devices. The first call also starts the camera
59
+ * subsystem, which comes up asynchronously: expect an empty list until the
60
+ * initial cameraDeviceChange events arrive.
61
+ */
56
62
  export function listCameras(): CameraDevice[]
57
63
  /**
58
64
  * Open a camera. Opening is also the permission request: the promise rejects
59
- * if permission is denied, and resolves once the first frame is ready.
65
+ * if permission is denied, and resolves once the first frame is ready. On
66
+ * Linux a session that delivers neither within 10 seconds rejects with a
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.
60
71
  */
61
72
  export function open(options?: CameraOpenOptions): Promise<CameraSession>
62
73
  /**
package/gui/gpu.d.ts CHANGED
@@ -1,12 +1,17 @@
1
1
  // Low-level GPU textures and shaders (gui-enabled runtime only). The
2
2
  // imperative primitive; @solidrt/core's gpu helpers add reactive auto-cleanup
3
- // on top. Three id spaces, each destroyed by its own destroyer: texture ids
4
- // (the public token used as `<texture src>` and sampler inputs ->
5
- // destroyTexture), buffer ids (-> destroyBuffer), and the raw shading layer's
6
- // shader-stage ids (-> destroyShader) and program ids (-> destroyProgram).
3
+ // on top. Each id space has its own destroyer: texture ids (the public token
4
+ // used as `<texture src>` and sampler inputs -> destroyTexture), buffer ids
5
+ // (-> destroyBuffer), and the raw shading layer's shader-stage ids
6
+ // (-> destroyShader), program ids (-> destroyProgram), and render-pipeline
7
+ // ids (-> destroyRenderPipeline).
7
8
  // Layering: compileShader/linkProgram are the raw GL primitives (complete
8
- // sources, explicit header opt-in); createShader/createPipeline are fused
9
- // conveniences (compile + link + target in one call, curated preamble).
9
+ // sources, explicit header opt-in); createRenderPipeline pairs a program with
10
+ // draw state (topology, blend, depth, vertex layout - how it draws);
11
+ // createShaderTarget builds a texture-backed target over a pipeline (size,
12
+ // buffer, uniforms, clear - where it draws). createShaderTexture/
13
+ // createPipelineTexture are fused conveniences (compile + link + pipeline +
14
+ // target in one call, curated preamble) - named for what they return.
10
15
  //
11
16
  // Sampling is a per-texture property declared at creation: every create path
12
17
  // accepts `{ filter?, wrap? }` ("linear"/"nearest", "clamp"/"repeat";
@@ -21,15 +26,73 @@
21
26
  // samples both. WITHIN one pipeline draw, `blend: "add"` accumulates
22
27
  // overlapping geometry additively; anything else (a fragment target, or a
23
28
  // pipeline without the option) draws with GL blending disabled and overwrites.
29
+ //
30
+ // The render contract. A target's contents are a pure function of its inputs
31
+ // (params, bound textures, geometry): the runtime renders it whenever inputs
32
+ // change - zero, one, or many times per frame, at its discretion - so a pass
33
+ // must not depend on its own previous output or on how often it runs. When a
34
+ // pass IS state (accumulation, feedback, simulation), create the target with
35
+ // `render: "manual"`: the runtime then never renders it, only an explicit
36
+ // renderTarget(id) does, in call order - the app owns the stepping. Targets
37
+ // sampling a manual target update after each explicit render; a manual
38
+ // target's own params/geometry writes take effect at its next render.
39
+ // `loadOp: "load"` (manual-only) keeps the previous contents under each
40
+ // draw - single-target accumulation - and copyTexture(src, dst) seeds or
41
+ // snapshots a manual target GPU-side. Both compose with renderTarget in
42
+ // call order.
43
+ //
44
+ // The pixel contract. Three facts hold for every texture and target:
45
+ //
46
+ // - Clip space is y-down. `gl_Position` y = -1 is the top of the target, +1
47
+ // the bottom (GL's row 0 is clip y = -1, and Impeller samples row 0 as the
48
+ // top). A vertex stage carrying camera-up geometry must negate y, or fold
49
+ // the flip into its projection, or it draws upside down: Vulkan's
50
+ // convention, not desktop GL's. The fragment path absorbs the same flip
51
+ // already, so `vUV` is 0..1 with top-left origin and a fragment-only shader
52
+ // never sees it.
53
+ // - Color is premultiplied alpha. A target's RGB is expected already
54
+ // multiplied by its A - `vec4(rgb * a, a)`, not `vec4(rgb, a)`, which
55
+ // composites as opaque. That is what Impeller composites and what
56
+ // `<texture blendMode>` blends; `clearColor` is premultiplied too, so the
57
+ // default transparent black needs no thought.
58
+ // - Values are non-linear RGBA8, with no color-space concept. Every texture
59
+ // and target holds 8-bit RGBA UNORM exactly as written; nothing converts to
60
+ // or from linear light. `filter: "linear"` averages and `blend: "add"`
61
+ // accumulates non-linear values - the usual approximation, stated so
62
+ // shaders written today stay correct if a format vocabulary arrives.
24
63
 
25
64
  declare module "flux:gpu" {
65
+ /**
66
+ * A GPU texture handle: what every texture-producing call returns and every
67
+ * texture-consuming site takes (`<texture src>`, sampler bindings, the
68
+ * texture mutators and destroyTexture). At runtime it is a plain number;
69
+ * the brand exists so each id space is its own type and a cross-space slip
70
+ * - `destroyBuffer(textureId)`, `createShaderTarget(programId, ...)` - is a
71
+ * type error instead of an operation on an unrelated live resource (every
72
+ * space counts from 1, so a wrong id is usually a valid id in the wrong
73
+ * space). Ids widen to number freely; only number -> id is blocked.
74
+ */
75
+ export type TextureId = number & { readonly __texture: unique symbol }
76
+ /** The vertex-buffer id space ({@link createBuffer}); see {@link TextureId} for the brand model. */
77
+ export type BufferId = number & { readonly __buffer: unique symbol }
78
+ /** The compiled-stage id space ({@link compileShader}); see {@link TextureId} for the brand model. */
79
+ export type ShaderStageId = number & { readonly __shaderStage: unique symbol }
80
+ /** The linked-program id space ({@link linkProgram}); see {@link TextureId} for the brand model. */
81
+ export type ProgramId = number & { readonly __program: unique symbol }
82
+ /** The render-pipeline id space ({@link createRenderPipeline}); see {@link TextureId} for the brand model. */
83
+ export type RenderPipelineId = number & { readonly __renderPipeline: unique symbol }
26
84
  /**
27
85
  * Shader uniform values by name. A number drives a scalar uniform (`float`,
28
86
  * or `int`/`bool`, truncated); a flat number array drives a typed uniform
29
87
  * whose declared GLSL type sets the expected length: 2/3/4 for
30
88
  * `vec2`/`vec3`/`vec4`, 16 (column-major) for `mat4`. Dispatch follows the
31
- * shader's own declaration; a value whose length does not fit it is skipped
32
- * with a runtime warning, as is a name with no active uniform.
89
+ * shader's own declaration, and every write is validated against it at the
90
+ * call site: a name with no active uniform, a value whose length does not
91
+ * fit the declared type, a `sampler2D` named here (samplers bind via
92
+ * `textures`), or a value that is not a number / number array throws.
93
+ * Reflection only sees active uniforms, so a uniform that is declared but
94
+ * optimized out counts as unknown - remove the write (or use the uniform).
95
+ * An `undefined` value is skipped, so conditional spreads stay usable.
33
96
  */
34
97
  export type ShaderParams = Record<string, number | number[]>
35
98
  /** Magnification/minification filter; "linear" (default) or hard-pixel "nearest". */
@@ -44,22 +107,52 @@ declare module "flux:gpu" {
44
107
  * matters to shaders sampling outside 0..1; the display draw never tiles.
45
108
  */
46
109
  export type SamplerOptions = { filter?: FilterMode; wrap?: WrapMode }
110
+ /**
111
+ * A free-form debug name every create accepts (WebGPU's label): surfaced by
112
+ * the dev server's GPU inventory (get_gpu_resources) and in engine log
113
+ * messages, so a chain of targets reads as "bloom-h samples particle-verts"
114
+ * instead of anonymous ids. Not unique, never interpreted; set at create,
115
+ * kept across id-stable resizes ({@link resizeTexture},
116
+ * {@link setShaderSize}).
117
+ */
118
+ export type LabelOption = { label?: string }
119
+ /**
120
+ * This device's hard ceilings, queried once at startup: process constants.
121
+ * Every create and bind validates against them at the call site, so an
122
+ * oversize target throws naming the limit instead of failing later as a
123
+ * driver error, and a binding list past the unit cap throws instead of
124
+ * silently sampling garbage. Values at or below these are safe on this
125
+ * device; the GLES 3.0 floors (2048 / 16 / 16) are the portable baseline
126
+ * every device guarantees.
127
+ */
128
+ export let limits: {
129
+ /** Largest width/height of any texture or render target, in pixels (>= 2048). */
130
+ maxTextureSize: number
131
+ /**
132
+ * Sampler inputs one pass may bind (>= 16): a target's `textures`
133
+ * entries; on a window shader the runtime-filled `uSource` (and
134
+ * `uPrevious` when declared) count toward it too.
135
+ */
136
+ maxTextureUnits: number
137
+ /** Vertex attributes one pipeline may declare (>= 16). */
138
+ maxVertexAttribs: number
139
+ }
47
140
  /**
48
141
  * Create an immutable texture from an RGBA8 pixel buffer (exactly
49
142
  * width*height*4 bytes). Returns the texture id.
50
143
  */
51
- export function createTexture(data: Uint8Array, width: number, height: number, opts?: SamplerOptions): number
144
+ export function createTexture(data: Uint8Array, width: number, height: number, opts?: SamplerOptions & LabelOption): TextureId
52
145
  /**
53
146
  * Create a texture intended to be updated later via {@link uploadTexture}. The
54
147
  * seed buffer must hold at least one frame (width*height*4 bytes) and may hold
55
148
  * more (uploadTexture selects a frame by offset).
56
149
  */
57
- export function createMutableTexture(data: Uint8Array, width: number, height: number, opts?: SamplerOptions): number
150
+ export function createMutableTexture(data: Uint8Array, width: number, height: number, opts?: SamplerOptions & LabelOption): TextureId
58
151
  /**
59
152
  * Replace a mutable texture's pixels. `data` may hold several frames; `offset`
60
153
  * (default 0) selects which frame to upload.
61
154
  */
62
- export function uploadTexture(id: number, data: Uint8Array, offset?: number): void
155
+ export function uploadTexture(id: TextureId, data: Uint8Array, offset?: number): void
63
156
  /**
64
157
  * Replace a texture's storage with a new size at the same id (an id-stable
65
158
  * resize): `<texture src>` references and shader sampler bindings keep
@@ -68,7 +161,7 @@ declare module "flux:gpu" {
68
161
  * width*height*4 frame. Shader/pipeline target ids are rejected - resize
69
162
  * those with {@link setShaderSize}.
70
163
  */
71
- export function resizeTexture(id: number, data: Uint8Array, width: number, height: number): void
164
+ export function resizeTexture(id: TextureId, data: Uint8Array, width: number, height: number): void
72
165
  /**
73
166
  * Destroy a texture (immutable, mutable, or shader). Frame-safe: the id is
74
167
  * reclaimed by the runtime once the render tree no longer references it, so
@@ -77,57 +170,70 @@ declare module "flux:gpu" {
77
170
  * in. A destroyed id that stays mounted keeps drawing (and stays allocated)
78
171
  * until it is unmounted or repointed.
79
172
  */
80
- export function destroyTexture(id: number): void
173
+ export function destroyTexture(id: TextureId): void
81
174
  /**
82
175
  * Compile a GLSL ES fragment shader into an offscreen texture of the given
83
- * size. `params` sets uniforms by name (see {@link ShaderParams} for the
84
- * value shapes); `textures` binds sampler2D uniforms to texture ids - any
85
- * texture id, including another shader/pipeline target's output. Bound
86
- * targets are live dependencies: when a source re-renders (its params,
87
- * geometry, or data change), every target sampling it re-renders too,
88
- * transitively through chains, before the next frame or readback - no
89
- * per-frame uniform write is needed to keep a chain current. Returns the resulting texture id. The fused
90
- * convenience: one call compiles a program and creates a target over it,
91
- * and the program lives and dies with the target. To share one compile
92
- * across targets (or hold a program with no target yet), use the raw layer:
93
- * {@link compileShader} + {@link linkProgram} + {@link createShaderTarget}.
176
+ * size. `params` sets initial uniforms by name (see {@link ShaderParams}
177
+ * for the value shapes and the validation contract - a typo'd name throws
178
+ * here, at the create). It is its own argument, not an option, because it
179
+ * is the initial value of a live channel - the same values the `<texture
180
+ * params>` prop and {@link setShaderParams} drive later; pass `null` (or
181
+ * omit it) for a shader with none. `opts.textures` binds sampler2D
182
+ * uniforms to texture ids - any texture id, including another
183
+ * shader/pipeline target's output, under a name that must be an active
184
+ * `sampler2D` uniform. Bound targets are live dependencies: when a source
185
+ * re-renders (its params, geometry, or data change), every target sampling
186
+ * it re-renders too, transitively through chains, before the next frame or
187
+ * readback - no per-frame uniform write is needed to keep a chain current.
188
+ * Returns the resulting texture id. The fused convenience: one call
189
+ * compiles a program and creates a target over it, and the program lives
190
+ * and dies with the target. To share one compile across targets (or hold a
191
+ * program with no target yet), use the raw layer: {@link compileShader} +
192
+ * {@link linkProgram} + {@link createRenderPipeline} +
193
+ * {@link createShaderTarget}.
94
194
  *
95
- * The preamble (`#version 300 es`, precision, `vUV`, `iResolution`, `iTime`,
195
+ * The preamble (`#version 300 es`, precision, `vUV`, `iResolution`,
96
196
  * `fragColor`) is injected only into sources that do not declare their own
97
- * `#version` line. A source that starts with `#version 300 es` is compiled
98
- * exactly as written, so a shader with its own uniform names (a port from
99
- * elsewhere) needs no rewriting and no drop to the raw layer. The built-in
100
- * vertex stage still supplies `vUV` to a complete source; declare
101
- * `in vec2 vUV;` yourself to read it. Same rule on {@link createPipeline}.
102
- * A complete source may also declare `iResolution` as vec3 (the Shadertoy
103
- * shape); it is then filled as `(w, h, 1.0)`.
104
- */
105
- export function createShader(
197
+ * `#version` line, and declares exactly what the runtime provides - an
198
+ * app-driven uniform (a time value, say) is the source's own declaration,
199
+ * driven through params like any other. A source that starts with
200
+ * `#version 300 es` is compiled exactly as written, so a shader with its
201
+ * own uniform names (a port from elsewhere) needs no rewriting and no drop
202
+ * to the raw layer. The built-in vertex stage still supplies `vUV` to a
203
+ * complete source; declare `in vec2 vUV;` yourself to read it. Same rule on
204
+ * {@link createPipelineTexture}. A complete source may also declare
205
+ * `iResolution` as vec3 (the Shadertoy shape); it is then filled as
206
+ * `(w, h, 1.0)`.
207
+ */
208
+ export function createShaderTexture(
106
209
  fragmentSrc: string,
107
210
  width: number,
108
211
  height: number,
109
- params?: ShaderParams,
110
- textures?: Record<string, number>,
111
- opts?: SamplerOptions,
112
- ): number
212
+ params?: ShaderParams | null,
213
+ opts?: { textures?: Record<string, TextureId> } & SamplerOptions & LabelOption,
214
+ ): TextureId
113
215
  /**
114
216
  * Compile a single shader stage from raw GLSL ES: the primitive under
115
217
  * {@link linkProgram}, GL's own model (a "shader" is one stage; linking
116
218
  * stages yields a program). The source is complete - it declares its own
117
219
  * `#version 300 es`, precision, varyings and uniforms; nothing is injected.
118
220
  * With `header: true` the standard header is prepended explicitly: `#version
119
- * 300 es`, `precision highp float;`, `uniform vec2 iResolution;`, `uniform
120
- * float iTime;`, plus `out vec4 fragColor;` for a fragment stage (the same
121
- * text {@link createPipeline} injects). Do not combine `header` with your
221
+ * 300 es`, `precision highp float;`, `uniform vec2 iResolution;`, plus
222
+ * `out vec4 fragColor;` for a fragment stage (the same text
223
+ * {@link createPipelineTexture} injects). Do not combine `header` with your
122
224
  * own `#version` line. Returns a shader (stage) id in its own id space;
123
225
  * compile errors throw here, synchronously, at a call site the app chose.
124
226
  * Free with {@link destroyShader}.
227
+ *
228
+ * A vertex stage writes into a y-down clip space: `gl_Position` y = -1 is
229
+ * the top row of the target and +1 the bottom, so camera-up geometry must
230
+ * negate y (or fold the flip into its projection) to display up.
125
231
  */
126
232
  export function compileShader(
127
233
  stage: "vertex" | "fragment",
128
234
  source: string,
129
235
  opts?: { header?: boolean },
130
- ): number
236
+ ): ShaderStageId
131
237
  /**
132
238
  * Link a compiled vertex and fragment stage into a program, returning a
133
239
  * program id (its own id space, like buffers - not a texture id). Link
@@ -137,70 +243,127 @@ declare module "flux:gpu" {
137
243
  * Creating targets from the returned handle compiles nothing. Free with
138
244
  * {@link destroyProgram}.
139
245
  */
140
- export function linkProgram(vertexShader: number, fragmentShader: number): number
246
+ export function linkProgram(vertexShader: ShaderStageId, fragmentShader: ShaderStageId, opts?: LabelOption): ProgramId
141
247
  /**
142
248
  * Destroy a compiled stage by id. Programs linked from it are unaffected.
143
249
  */
144
- export function destroyShader(id: number): void
250
+ export function destroyShader(id: ShaderStageId): void
145
251
  /**
146
- * Create a render target over a linked program and render it once: the
147
- * target half of {@link createPipeline}. Returns a texture id exactly like
148
- * createShader/createPipeline do (drive uniforms via the `params` prop or
149
- * {@link setShaderParams}, resize with {@link setShaderSize}, destroy with
150
- * {@link destroyTexture}). Many targets may share one program. A raw-linked
151
- * program carries its own vertex stage, so the mesh options apply:
152
- * `attributes`/`buffer` for vertex input (omit for attributeless rendering
153
- * via gl_VertexID - a fullscreen pass is `vertexCount: 3` with a
154
- * covering-triangle vertex stage), `topology`, `vertexCount`, `depth`,
155
- * `clearColor`, all as in {@link createPipeline}.
252
+ * Pair a linked program with draw state, returning a render pipeline id
253
+ * (its own id space, like programs and buffers - not a texture id): the
254
+ * pipeline state object of every modern GPU API. The pipeline owns HOW its
255
+ * targets draw - `attributes` (the interleaved vertex layout; omit for
256
+ * attributeless rendering via gl_VertexID), `topology`, `blend`, `depth`,
257
+ * `depthWrite` (`false` requires `depth: true`) - while each target brings
258
+ * its own size, buffer, uniforms, and clear. Creating a pipeline compiles
259
+ * nothing, and many pipelines may share one program. The vocabulary is
260
+ * validated here, so a bad word throws at this call site. Free with
261
+ * {@link destroyRenderPipeline}; the program is yours and outlives it.
156
262
  */
157
- export function createShaderTarget(
158
- program: number,
159
- width: number,
160
- height: number,
263
+ export function createRenderPipeline(
264
+ program: ProgramId,
161
265
  opts?: {
162
- params?: ShaderParams
163
- textures?: Record<string, number>
164
266
  attributes?: VertexAttribute[]
165
- buffer?: number
166
267
  topology?: Topology
167
- vertexCount?: number
268
+ blend?: BlendMode
168
269
  depth?: boolean
169
270
  depthWrite?: boolean
170
- blend?: BlendMode
271
+ } & LabelOption,
272
+ ): RenderPipelineId
273
+ /**
274
+ * Destroy a render pipeline by id. Targets created from it are unaffected:
275
+ * each holds the pipeline until it is itself destroyed, so either
276
+ * destruction order is safe. The id stops being usable for new targets
277
+ * immediately.
278
+ */
279
+ export function destroyRenderPipeline(id: RenderPipelineId): void
280
+ /**
281
+ * Create a render target over a {@link createRenderPipeline} pipeline and
282
+ * render it once: the target half of {@link createPipelineTexture}. Returns
283
+ * a texture id exactly like the fused creates do (drive uniforms
284
+ * via the `params` prop or {@link setShaderParams}, resize with
285
+ * {@link setShaderSize}, destroy with {@link destroyTexture}). Many targets
286
+ * may share one pipeline, and creating a target compiles nothing. `buffer`
287
+ * supplies the concrete vertex buffer the pipeline's attribute layout
288
+ * describes (required when the pipeline declares attributes); the
289
+ * {@link DrawRange} keys pick what is drawn from it - `vertexCount`
290
+ * defaults to the rest of the buffer from `firstVertex` on,
291
+ * `instanceCount` repeats the range - and a vertex fetch past the
292
+ * buffer's end throws here. A fullscreen pass over an attributeless
293
+ * pipeline is `vertexCount: 3` with a covering-triangle vertex stage.
294
+ * Draw-state keys
295
+ * (`attributes`, `topology`, `blend`, `depth`, `depthWrite`) belong to the
296
+ * pipeline and throw here. `params` and `textures` are validated against
297
+ * the pipeline's program (see {@link ShaderParams}).
298
+ *
299
+ * `render: "manual"` opts the target out of runtime-driven rendering (see
300
+ * the render contract above): it starts cleared to `clearColor` and its
301
+ * pass runs only when {@link renderTarget} is called.
302
+ *
303
+ * `loadOp` chooses what each render finds in the target: `"clear"` (the
304
+ * default) clears to `clearColor` first, `"load"` keeps the previous
305
+ * contents and draws over them - single-target accumulation (with the
306
+ * pipeline's `blend: "add"`, an additive trail; without blending, draws
307
+ * simply land over old pixels). `"load"` requires `render: "manual"` and
308
+ * throws otherwise: on a runtime-rendered target the output would depend
309
+ * on how often the runtime happened to render. Depth (when the pipeline
310
+ * has it) is per-render scratch and always clears; creation, resize, and
311
+ * nothing else reset the color to `clearColor`. State that needs a
312
+ * read-modify-write of its own pixels (decay, blur, simulation) still
313
+ * ping-pongs across two manual targets - a pass can never sample the
314
+ * texture it writes.
315
+ */
316
+ export function createShaderTarget(
317
+ pipeline: RenderPipelineId,
318
+ width: number,
319
+ height: number,
320
+ params?: ShaderParams | null,
321
+ opts?: {
322
+ textures?: Record<string, TextureId>
323
+ buffer?: BufferId
171
324
  clearColor?: [number, number, number, number]
172
- } & SamplerOptions,
173
- ): number
325
+ render?: "auto" | "manual"
326
+ loadOp?: "clear" | "load"
327
+ } & DrawRange &
328
+ SamplerOptions &
329
+ LabelOption,
330
+ ): TextureId
174
331
  /**
175
- * Destroy a linked program by id. Targets created from it are unaffected:
332
+ * Destroy a linked program by id. Pipelines created from it are unaffected:
176
333
  * each holds the program until it is itself destroyed, so either
177
- * destruction order is safe. The id stops being usable for new targets
334
+ * destruction order is safe. The id stops being usable for new pipelines
178
335
  * immediately.
179
336
  */
180
- export function destroyProgram(id: number): void
337
+ export function destroyProgram(id: ProgramId): void
181
338
  /**
182
339
  * Update a shader texture's uniforms by name and re-render it (see
183
- * {@link ShaderParams} for the value shapes).
340
+ * {@link ShaderParams} for the value shapes and the validation contract -
341
+ * an unknown name or a mismatched length throws here, on the line that
342
+ * wrote it). On a manual target nothing renders here; the values apply at
343
+ * its next {@link renderTarget}.
184
344
  */
185
- export function setShaderParams(id: number, params: ShaderParams): void
345
+ export function setShaderParams(id: TextureId, params: ShaderParams): void
186
346
  /**
187
347
  * Rebind a shader texture's sampler2D inputs by uniform name and re-render
188
348
  * it with its last-applied params - the sampler analog of
189
349
  * {@link setShaderParams}. Bindings not named keep their current source, so
190
350
  * a single input can be retargeted (post-process source swap, ping-pong
191
351
  * between two data textures) without recompiling the shader. Throws if the
192
- * shader or a source texture id is unknown, or a binding would create a
193
- * sampling cycle among targets (binding a shader's own target is the
194
- * shortest case).
352
+ * shader or a source texture id is unknown, if a binding names anything
353
+ * but an active `sampler2D` uniform, if it names the shader's own target
354
+ * (same-pass feedback), or if it would close a sampling cycle among
355
+ * runtime-rendered targets. A cycle through a
356
+ * `render: "manual"` target is legal - the runtime never renders one, so
357
+ * the loop only steps when the app calls {@link renderTarget}.
195
358
  */
196
- export function setShaderTextures(id: number, textures: Record<string, number>): void
359
+ export function setShaderTextures(id: TextureId, textures: Record<string, TextureId>): void
197
360
  /**
198
361
  * Resize a shader or pipeline target texture in place and re-render it: the
199
362
  * id, compiled program, last-applied params, and sampler bindings all carry
200
- * over; only the output size changes. The setDrawCount analog for output
363
+ * over; only the output size changes. The setDraw analog for output
201
364
  * size.
202
365
  */
203
- export function setShaderSize(id: number, width: number, height: number): void
366
+ export function setShaderSize(id: TextureId, width: number, height: number): void
204
367
 
205
368
  export type Topology = "points" | "lines" | "line-strip" | "triangles" | "triangle-strip"
206
369
  /**
@@ -220,64 +383,131 @@ declare module "flux:gpu" {
220
383
  * vertex shader's `in` declarations.
221
384
  */
222
385
  export type VertexAttribute = { name: string; format: "f32" | "vec2" | "vec3" | "vec4" }
386
+ /**
387
+ * A pipeline target's draw as data, WebGPU-style: `firstVertex` +
388
+ * `vertexCount` pick the vertex range `[firstVertex, firstVertex +
389
+ * vertexCount)` of the buffer, `instanceCount` draws that range as N
390
+ * instances (`glDrawArraysInstanced`) told apart by `gl_InstanceID`. All
391
+ * keys optional: at create, `firstVertex` defaults to 0, `vertexCount` to
392
+ * the rest of the buffer and `instanceCount` to 1 (the plain draw); in
393
+ * {@link setDraw}, absent keys keep their current value. `instanceCount: 0`
394
+ * draws nothing - a cheap off switch. Two GL facts worth knowing:
395
+ * `gl_VertexID` includes `firstVertex` (as in WebGPU), and `gl_InstanceID`
396
+ * always counts from 0 - ES 3.0 has no base instance.
397
+ */
398
+ export type DrawRange = { firstVertex?: number; vertexCount?: number; instanceCount?: number }
223
399
 
224
400
  /**
225
401
  * Compile a GLSL ES vertex+fragment pipeline into an offscreen texture of
226
402
  * the given size and render it once. Sources without a `#version` line get
227
- * a 300 es preamble declaring `iResolution`/`iTime` (no vUV: varyings are
228
- * the pipeline's own). `attributes` describes one interleaved vertex in
229
- * `buffer` (a {@link createBuffer} id); omit both for attributeless
230
- * rendering via gl_VertexID. `vertexCount` defaults to the whole buffer
231
- * (buffer size / vertex stride). With `depth: true` the pipeline gets a
232
- * private depth buffer, cleared and tested on every render; `depthWrite:
233
- * false` (requires `depth: true`) keeps the test but stops the draw from
234
- * writing depth. `blend` sets the draw's own blending (see
403
+ * a 300 es preamble declaring `iResolution` (no vUV: varyings are the
404
+ * pipeline's own; app-driven uniforms are the source's own declarations).
405
+ * Clip space is y-down: `gl_Position` y = -1 is the top
406
+ * row of the target and +1 the bottom, so camera-up geometry must negate y
407
+ * (or fold the flip into its projection) to display up. `attributes`
408
+ * describes one interleaved vertex in `buffer` (a {@link createBuffer} id);
409
+ * omit both for attributeless rendering via gl_VertexID. The
410
+ * {@link DrawRange} keys pick what is drawn: `vertexCount` defaults to the
411
+ * rest of the buffer from `firstVertex` on, `instanceCount` draws the
412
+ * range as N instances told apart by `gl_InstanceID`; a vertex fetch past
413
+ * the buffer's end throws. With
414
+ * `depth: true` the pipeline gets a private depth buffer, cleared and tested
415
+ * on every render; `depthWrite: false` (requires `depth: true`) keeps the
416
+ * test but stops the draw from writing depth. `blend` sets the draw's own blending (see
235
417
  * {@link BlendMode}); an additive pass over a depth buffer is
236
418
  * `{ depth: true, blend: "add", depthWrite: false }`, stated explicitly.
237
419
  * The target is cleared to `clearColor` (default transparent black) before
238
- * each draw.
420
+ * each draw. `render: "manual"` and `loadOp` behave exactly as on
421
+ * {@link createShaderTarget}: no runtime-driven renders, step with
422
+ * {@link renderTarget}, and `loadOp: "load"` (manual-only) keeps the
423
+ * previous contents under each draw.
239
424
  * Returns a texture id: display it with `<texture src>`, drive uniforms via
240
425
  * the `params` prop or {@link setShaderParams}, destroy with
241
426
  * {@link destroyTexture}.
242
427
  */
243
- export function createPipeline(
428
+ export function createPipelineTexture(
244
429
  vertexSrc: string,
245
430
  fragmentSrc: string,
246
431
  width: number,
247
432
  height: number,
433
+ params?: ShaderParams | null,
248
434
  opts?: {
249
- params?: ShaderParams
250
- textures?: Record<string, number>
435
+ textures?: Record<string, TextureId>
251
436
  attributes?: VertexAttribute[]
252
- buffer?: number
437
+ buffer?: BufferId
253
438
  topology?: Topology
254
- vertexCount?: number
255
439
  depth?: boolean
256
440
  depthWrite?: boolean
257
441
  blend?: BlendMode
258
442
  clearColor?: [number, number, number, number]
259
- } & SamplerOptions,
260
- ): number
443
+ render?: "auto" | "manual"
444
+ loadOp?: "clear" | "load"
445
+ } & DrawRange &
446
+ SamplerOptions &
447
+ LabelOption,
448
+ ): TextureId
261
449
 
262
450
  /**
263
451
  * Create a vertex buffer from raw bytes (interleave attribute data to match
264
452
  * the pipeline's attribute list). Buffer ids are their own space, separate
265
453
  * from texture ids.
266
454
  */
267
- export function createBuffer(data: Uint8Array): number
455
+ export function createBuffer(data: Uint8Array, opts?: LabelOption): BufferId
268
456
  /**
269
457
  * Overwrite part of a vertex buffer at `byteOffset` (default 0), within the
270
458
  * size it was created with. Pipelines drawing from the buffer re-render
271
459
  * with their last-applied params.
272
460
  */
273
- export function writeBuffer(id: number, data: Uint8Array, byteOffset?: number): void
274
- /** Destroy a vertex buffer. Destroy pipelines drawing from it first. */
275
- export function destroyBuffer(id: number): void
461
+ export function writeBuffer(id: BufferId, data: Uint8Array, byteOffset?: number): void
462
+ /**
463
+ * Destroy a vertex buffer. Pipeline textures drawing from it hold their own
464
+ * reference, so destruction order does not matter; further writes to the id
465
+ * throw.
466
+ */
467
+ export function destroyBuffer(id: BufferId): void
468
+ /**
469
+ * Update a pipeline texture's draw range and re-render it: `vertexCount`
470
+ * after writing a variable amount of dynamic geometry into its buffer,
471
+ * `firstVertex` to draw a different window of a shared buffer,
472
+ * `instanceCount` to grow or shrink an instanced population. Keys absent
473
+ * from `draw` keep their current value, like params. Throws if a value is
474
+ * negative or the merged range's vertex fetch would run past the end of
475
+ * the target's buffer ((firstVertex + vertexCount) x vertex stride >
476
+ * buffer size) - the out-of-bounds draw GL itself never checks; a target
477
+ * without vertex fetch (attributeless) accepts any non-negative range.
478
+ * (On a manual target nothing renders here; the range applies at its next
479
+ * {@link renderTarget}.)
480
+ */
481
+ export function setDraw(id: TextureId, draw: DrawRange): void
482
+ /**
483
+ * Render a `render: "manual"` target once, now. Renders land in call order
484
+ * relative to every other GPU call: a `setShaderParams`/`writeBuffer`
485
+ * issued before is visible to the pass, a {@link readTexture} issued after
486
+ * observes it, and two renders run the pass twice in order. Inputs are
487
+ * fresh: pending runtime-driven renders of sampled targets resolve first.
488
+ * Targets sampling this one update after the render. Throws if the id is
489
+ * not a manual target - the runtime owns rendering the others, and a pass
490
+ * that depends on how often it runs is only well-defined when the app is
491
+ * the one counting. Ping-pong feedback is two manual targets sampling
492
+ * each other, stepped alternately from `onFrame`; binding a target to
493
+ * ITSELF still throws (same-pass GL feedback, undefined pixels regardless
494
+ * of who schedules it).
495
+ */
496
+ export function renderTarget(id: TextureId): void
276
497
  /**
277
- * Set how many vertices a pipeline texture draws and re-render it, e.g.
278
- * after writing a variable amount of dynamic geometry into its buffer.
498
+ * Overwrite a `render: "manual"` target with another texture's current
499
+ * pixels, GPU-side: the seed/history analog of {@link uploadTexture}
500
+ * (seed a `loadOp: "load"` accumulator, snapshot one ping-pong buffer
501
+ * into another, reset state to a known image). Exact and same-size only -
502
+ * content and row order are preserved, and a size mismatch throws (a
503
+ * scaling copy is an ordinary pass). Copies land in call order like
504
+ * renders: a copy after a render sees that render, a readback after a
505
+ * copy sees the copy, and targets sampling `dst` update afterwards.
506
+ * Throws if either id is unknown, `dst` is not a manual target (the
507
+ * runtime owns those contents), or `src === dst`. `src` may be any
508
+ * texture: uploaded, mutable, a camera frame, or another target's output.
279
509
  */
280
- export function setDrawCount(id: number, count: number): void
510
+ export function copyTexture(src: TextureId, dst: TextureId): void
281
511
  /**
282
512
  * Capture a render-tree node's subtree into a new GPU texture, resolving once
283
513
  * it has been rendered on the next paint pass. The node must be attached to
@@ -310,11 +540,12 @@ declare module "flux:gpu" {
310
540
  * must stay current has to come from a source that updates in place: another
311
541
  * pipeline's render target, a camera texture, a mutable texture.
312
542
  */
313
- export function captureSnapshot(nodeId: number): Promise<{ id: number; width: number; height: number }>
543
+ export function captureSnapshot(nodeId: number): Promise<{ id: TextureId; width: number; height: number }>
314
544
  /**
315
545
  * Read back a registered texture's current pixels as RGBA8 (tightly packed,
316
546
  * top-to-bottom rows), for any texture id whatever created it (createTexture,
317
- * createShader, captureSnapshot). Synchronous. Throws if the id is unknown.
547
+ * createShaderTexture, captureSnapshot). Synchronous. Throws if the id is
548
+ * unknown.
318
549
  */
319
- export function readTexture(id: number): { width: number; height: number; data: Uint8Array }
550
+ export function readTexture(id: TextureId): { width: number; height: number; data: Uint8Array }
320
551
  }
package/index.d.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  /// <reference path="./modules/fs.d.ts" />
5
5
  /// <reference path="./modules/sqlite.d.ts" />
6
6
  /// <reference path="./modules/subprocess.d.ts" />
7
+ /// <reference path="./modules/svg.d.ts" />
7
8
  /// <reference path="./modules/p2p.d.ts" />
8
9
  /// <reference path="./modules/net.d.ts" />
9
10
  /// <reference path="./modules/mdns.d.ts" />
@@ -16,6 +17,7 @@
16
17
  /// <reference path="./standards/console.d.ts" />
17
18
  /// <reference path="./standards/time.d.ts" />
18
19
  /// <reference path="./standards/text.d.ts" />
20
+ /// <reference path="./standards/base64.d.ts" />
19
21
  /// <reference path="./standards/fetch.d.ts" />
20
22
  /// <reference path="./standards/websocket.d.ts" />
21
23
 
@@ -0,0 +1,73 @@
1
+ declare module "flux:svg" {
2
+ /**
3
+ * A gradient paint from a parsed document, in the branded shape the render
4
+ * tree decodes: coordinates are absolute document-space values
5
+ * (`units: "absolute"`), `spread` is the SVG spreadMethod (absent = pad),
6
+ * and `transform` an SVG matrix(a b c d e f) sextet mapping the gradient's
7
+ * coordinates into the document space (absent = identity). Stop colors are
8
+ * packed `0xRRGGBBAA` numbers.
9
+ */
10
+ export type SvgGradient =
11
+ | {
12
+ __gradient: "linear"
13
+ units: "absolute"
14
+ x0: number
15
+ y0: number
16
+ x1: number
17
+ y1: number
18
+ stops: { offset: number; color: number }[]
19
+ spread?: "reflect" | "repeat"
20
+ transform?: [number, number, number, number, number, number]
21
+ }
22
+ | {
23
+ __gradient: "radial"
24
+ units: "absolute"
25
+ cx: number
26
+ cy: number
27
+ r: number
28
+ stops: { offset: number; color: number }[]
29
+ spread?: "reflect" | "repeat"
30
+ transform?: [number, number, number, number, number, number]
31
+ }
32
+
33
+ /**
34
+ * One resolved draw in document coordinates. The keys deliberately match
35
+ * the path element's props, so a draw spreads onto a `<d-path>` unchanged.
36
+ * A source path with both a fill and a stroke yields two draws (fill
37
+ * first). Solid colors are `#rrggbbaa` strings; stroke keys are only
38
+ * present on stroke draws, `fillRule` only on fills.
39
+ */
40
+ export type SvgDraw = {
41
+ d: string
42
+ color: string | SvgGradient
43
+ drawStyle: "fill" | "stroke"
44
+ fillRule?: "nonzero" | "evenodd"
45
+ strokeWidth?: number
46
+ strokeCap?: "butt" | "round" | "square"
47
+ strokeJoin?: "miter" | "round" | "bevel"
48
+ }
49
+
50
+ /** A parsed document: intrinsic size (viewBox/width-height) plus the flat draw list. */
51
+ export type SvgDocument = {
52
+ width: number
53
+ height: number
54
+ draws: SvgDraw[]
55
+ }
56
+
57
+ /**
58
+ * Parses an SVG document string into plain draw data: geometry is flattened
59
+ * to absolute path data (every group/element transform baked in) and paints
60
+ * are resolved to solid colors or gradients. Parsing is sandboxed: no
61
+ * network, file, or data-URI resource access. Throws on an invalid
62
+ * document.
63
+ *
64
+ * `opts.color` drives `currentColor` in the document, as a packed
65
+ * `0xRRGGBBAA` number (alpha ignored); explicit fills/strokes still win.
66
+ * The `@solidrt/core` re-export `parseSvg` accepts any CSS color string
67
+ * instead and is the surface applications normally use.
68
+ *
69
+ * Unsupported (skipped): clipPath, masks, filters, patterns, embedded
70
+ * images, and SVG text.
71
+ */
72
+ export function parseSvg(src: string, opts?: { color?: number }): SvgDocument
73
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidrt/flux-types",
3
- "version": "0.0.40",
3
+ "version": "0.0.41",
4
4
  "license": "MIT",
5
5
  "author": "Antoine van Wel",
6
6
  "types": "index.d.ts",
@@ -0,0 +1,16 @@
1
+ // atob / btoa. Base64 over "binary strings" (WHATWG): the string is a byte
2
+ // container, not text - each char code is one raw byte in 0..=255, with no
3
+ // UTF-8 step in either direction.
4
+
5
+ /**
6
+ * Base64-encode a binary string: each char code is taken as one raw byte.
7
+ * Throws if the string contains a code point above 255 (encode real text by
8
+ * taking its bytes first, e.g. via {@link TextEncoder}).
9
+ */
10
+ declare function btoa(data: string): string
11
+ /**
12
+ * Base64-decode to a binary string: each decoded byte becomes one char code
13
+ * (read the bytes back with `charCodeAt`). ASCII whitespace in the input is
14
+ * ignored; anything else that is not valid base64 throws.
15
+ */
16
+ declare function atob(data: string): string