@solidrt/flux-types 0.0.39 → 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,30 +1,158 @@
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.
15
+ //
16
+ // Sampling is a per-texture property declared at creation: every create path
17
+ // accepts `{ filter?, wrap? }` ("linear"/"nearest", "clamp"/"repeat";
18
+ // defaults linear + clamp for every origin). The state follows the id
19
+ // everywhere it is sampled - shader passes and `<texture>` display alike -
20
+ // and survives id-stable resizes. It cannot be changed after creation. No
21
+ // mipmaps exist.
22
+ //
23
+ // Compositing several targets is a render-tree job, not a shader one: stack
24
+ // `<texture>` elements and set their `blendMode` (the full Skia set, e.g.
25
+ // "plus" for an additive pass over a base pass) instead of writing a pass that
26
+ // samples both. WITHIN one pipeline draw, `blend: "add"` accumulates
27
+ // overlapping geometry additively; anything else (a fragment target, or a
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.
10
63
 
11
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 }
84
+ /**
85
+ * Shader uniform values by name. A number drives a scalar uniform (`float`,
86
+ * or `int`/`bool`, truncated); a flat number array drives a typed uniform
87
+ * whose declared GLSL type sets the expected length: 2/3/4 for
88
+ * `vec2`/`vec3`/`vec4`, 16 (column-major) for `mat4`. Dispatch follows the
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.
96
+ */
97
+ export type ShaderParams = Record<string, number | number[]>
98
+ /** Magnification/minification filter; "linear" (default) or hard-pixel "nearest". */
99
+ export type FilterMode = "linear" | "nearest"
100
+ /** Sampling outside 0..1: "clamp" (default, extend edge pixels) or "repeat" (tile). */
101
+ export type WrapMode = "clamp" | "repeat"
102
+ /**
103
+ * Per-texture sampling, declared at creation and fixed for the id's
104
+ * lifetime. Applies wherever the texture is sampled: shader/pipeline
105
+ * sampler2D inputs AND `<texture src>` display (a "nearest" texture
106
+ * upscales with hard pixels on screen - the pixel-art path). `wrap` only
107
+ * matters to shaders sampling outside 0..1; the display draw never tiles.
108
+ */
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
+ }
12
140
  /**
13
141
  * Create an immutable texture from an RGBA8 pixel buffer (exactly
14
142
  * width*height*4 bytes). Returns the texture id.
15
143
  */
16
- export function createTexture(data: Uint8Array, width: number, height: number): number
144
+ export function createTexture(data: Uint8Array, width: number, height: number, opts?: SamplerOptions & LabelOption): TextureId
17
145
  /**
18
146
  * Create a texture intended to be updated later via {@link uploadTexture}. The
19
147
  * seed buffer must hold at least one frame (width*height*4 bytes) and may hold
20
148
  * more (uploadTexture selects a frame by offset).
21
149
  */
22
- export function createMutableTexture(data: Uint8Array, width: number, height: number): number
150
+ export function createMutableTexture(data: Uint8Array, width: number, height: number, opts?: SamplerOptions & LabelOption): TextureId
23
151
  /**
24
152
  * Replace a mutable texture's pixels. `data` may hold several frames; `offset`
25
153
  * (default 0) selects which frame to upload.
26
154
  */
27
- export function uploadTexture(id: number, data: Uint8Array, offset?: number): void
155
+ export function uploadTexture(id: TextureId, data: Uint8Array, offset?: number): void
28
156
  /**
29
157
  * Replace a texture's storage with a new size at the same id (an id-stable
30
158
  * resize): `<texture src>` references and shader sampler bindings keep
@@ -33,7 +161,7 @@ declare module "flux:gpu" {
33
161
  * width*height*4 frame. Shader/pipeline target ids are rejected - resize
34
162
  * those with {@link setShaderSize}.
35
163
  */
36
- 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
37
165
  /**
38
166
  * Destroy a texture (immutable, mutable, or shader). Frame-safe: the id is
39
167
  * reclaimed by the runtime once the render tree no longer references it, so
@@ -42,41 +170,70 @@ declare module "flux:gpu" {
42
170
  * in. A destroyed id that stays mounted keeps drawing (and stays allocated)
43
171
  * until it is unmounted or repointed.
44
172
  */
45
- export function destroyTexture(id: number): void
173
+ export function destroyTexture(id: TextureId): void
46
174
  /**
47
175
  * Compile a GLSL ES fragment shader into an offscreen texture of the given
48
- * size. `params` sets float uniforms by name; `textures` binds sampler2D
49
- * uniforms to texture ids. Returns the resulting texture id. The fused
50
- * convenience: one call compiles a program and creates a target over it,
51
- * and the program lives and dies with the target. To share one compile
52
- * across targets (or hold a program with no target yet), use the raw layer:
53
- * {@link compileShader} + {@link linkProgram} + {@link createShaderTarget}.
54
- */
55
- export function createShader(
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}.
194
+ *
195
+ * The preamble (`#version 300 es`, precision, `vUV`, `iResolution`,
196
+ * `fragColor`) is injected only into sources that do not declare their own
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(
56
209
  fragmentSrc: string,
57
210
  width: number,
58
211
  height: number,
59
- params?: Record<string, number>,
60
- textures?: Record<string, number>,
61
- ): number
212
+ params?: ShaderParams | null,
213
+ opts?: { textures?: Record<string, TextureId> } & SamplerOptions & LabelOption,
214
+ ): TextureId
62
215
  /**
63
216
  * Compile a single shader stage from raw GLSL ES: the primitive under
64
217
  * {@link linkProgram}, GL's own model (a "shader" is one stage; linking
65
218
  * stages yields a program). The source is complete - it declares its own
66
219
  * `#version 300 es`, precision, varyings and uniforms; nothing is injected.
67
220
  * With `header: true` the standard header is prepended explicitly: `#version
68
- * 300 es`, `precision highp float;`, `uniform vec2 iResolution;`, `uniform
69
- * float iTime;`, plus `out vec4 fragColor;` for a fragment stage (the same
70
- * 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
71
224
  * own `#version` line. Returns a shader (stage) id in its own id space;
72
225
  * compile errors throw here, synchronously, at a call site the app chose.
73
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.
74
231
  */
75
232
  export function compileShader(
76
233
  stage: "vertex" | "fragment",
77
234
  source: string,
78
235
  opts?: { header?: boolean },
79
- ): number
236
+ ): ShaderStageId
80
237
  /**
81
238
  * Link a compiled vertex and fragment stage into a program, returning a
82
239
  * program id (its own id space, like buffers - not a texture id). Link
@@ -86,127 +243,280 @@ declare module "flux:gpu" {
86
243
  * Creating targets from the returned handle compiles nothing. Free with
87
244
  * {@link destroyProgram}.
88
245
  */
89
- export function linkProgram(vertexShader: number, fragmentShader: number): number
246
+ export function linkProgram(vertexShader: ShaderStageId, fragmentShader: ShaderStageId, opts?: LabelOption): ProgramId
90
247
  /**
91
248
  * Destroy a compiled stage by id. Programs linked from it are unaffected.
92
249
  */
93
- export function destroyShader(id: number): void
250
+ export function destroyShader(id: ShaderStageId): void
94
251
  /**
95
- * Create a render target over a linked program and render it once: the
96
- * target half of {@link createPipeline}. Returns a texture id exactly like
97
- * createShader/createPipeline do (drive uniforms via the `params` prop or
98
- * {@link setShaderParams}, resize with {@link setShaderSize}, destroy with
99
- * {@link destroyTexture}). Many targets may share one program. A raw-linked
100
- * program carries its own vertex stage, so the mesh options apply:
101
- * `attributes`/`buffer` for vertex input (omit for attributeless rendering
102
- * via gl_VertexID - a fullscreen pass is `vertexCount: 3` with a
103
- * covering-triangle vertex stage), `topology`, `vertexCount`, `depth`,
104
- * `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.
105
262
  */
106
- export function createShaderTarget(
107
- program: number,
108
- width: number,
109
- height: number,
263
+ export function createRenderPipeline(
264
+ program: ProgramId,
110
265
  opts?: {
111
- params?: Record<string, number>
112
- textures?: Record<string, number>
113
266
  attributes?: VertexAttribute[]
114
- buffer?: number
115
267
  topology?: Topology
116
- vertexCount?: number
268
+ blend?: BlendMode
117
269
  depth?: boolean
270
+ depthWrite?: boolean
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
118
324
  clearColor?: [number, number, number, number]
119
- },
120
- ): number
325
+ render?: "auto" | "manual"
326
+ loadOp?: "clear" | "load"
327
+ } & DrawRange &
328
+ SamplerOptions &
329
+ LabelOption,
330
+ ): TextureId
121
331
  /**
122
- * 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:
123
333
  * each holds the program until it is itself destroyed, so either
124
- * 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
125
335
  * immediately.
126
336
  */
127
- export function destroyProgram(id: number): void
128
- /** Update a shader texture's float uniforms by name and re-render it. */
129
- export function setShaderParams(id: number, params: Record<string, number>): void
337
+ export function destroyProgram(id: ProgramId): void
338
+ /**
339
+ * Update a shader texture's uniforms by name and re-render it (see
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}.
344
+ */
345
+ export function setShaderParams(id: TextureId, params: ShaderParams): void
130
346
  /**
131
347
  * Rebind a shader texture's sampler2D inputs by uniform name and re-render
132
348
  * it with its last-applied params - the sampler analog of
133
349
  * {@link setShaderParams}. Bindings not named keep their current source, so
134
350
  * a single input can be retargeted (post-process source swap, ping-pong
135
351
  * between two data textures) without recompiling the shader. Throws if the
136
- * shader or a source texture id is unknown, or a sampler would source the
137
- * shader's own target.
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}.
138
358
  */
139
- export function setShaderTextures(id: number, textures: Record<string, number>): void
359
+ export function setShaderTextures(id: TextureId, textures: Record<string, TextureId>): void
140
360
  /**
141
361
  * Resize a shader or pipeline target texture in place and re-render it: the
142
362
  * id, compiled program, last-applied params, and sampler bindings all carry
143
- * over; only the output size changes. The setDrawCount analog for output
363
+ * over; only the output size changes. The setDraw analog for output
144
364
  * size.
145
365
  */
146
- export function setShaderSize(id: number, width: number, height: number): void
366
+ export function setShaderSize(id: TextureId, width: number, height: number): void
147
367
 
148
368
  export type Topology = "points" | "lines" | "line-strip" | "triangles" | "triangle-strip"
369
+ /**
370
+ * Blending for a pipeline's own draw. "none" (default) overwrites:
371
+ * overlapping geometry resolves by depth or draw order. "add" accumulates
372
+ * (glBlendFunc(ONE, ONE)): order-independent, so geometry needs no sorting
373
+ * - the additive half of translucency (point splats, glow passes). A
374
+ * depth-tested additive pass usually pairs with `depthWrite: false`; with
375
+ * writes on, unsorted geometry depth-rejects its own later fragments and
376
+ * accumulation becomes draw-order-dependent. That pairing is the app's to
377
+ * state - neither option implies the other.
378
+ */
379
+ export type BlendMode = "none" | "add"
149
380
  /**
150
381
  * One float attribute of an interleaved vertex. The attribute list's order
151
382
  * defines the byte layout; locations are resolved by name against the
152
383
  * vertex shader's `in` declarations.
153
384
  */
154
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 }
155
399
 
156
400
  /**
157
401
  * Compile a GLSL ES vertex+fragment pipeline into an offscreen texture of
158
402
  * the given size and render it once. Sources without a `#version` line get
159
- * a 300 es preamble declaring `iResolution`/`iTime` (no vUV: varyings are
160
- * the pipeline's own). `attributes` describes one interleaved vertex in
161
- * `buffer` (a {@link createBuffer} id); omit both for attributeless
162
- * rendering via gl_VertexID. `vertexCount` defaults to the whole buffer
163
- * (buffer size / vertex stride). With `depth: true` the pipeline gets a
164
- * private depth buffer, cleared and tested on every render. The target is
165
- * cleared to `clearColor` (default transparent black) before each draw.
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
417
+ * {@link BlendMode}); an additive pass over a depth buffer is
418
+ * `{ depth: true, blend: "add", depthWrite: false }`, stated explicitly.
419
+ * The target is cleared to `clearColor` (default transparent black) before
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.
166
424
  * Returns a texture id: display it with `<texture src>`, drive uniforms via
167
425
  * the `params` prop or {@link setShaderParams}, destroy with
168
426
  * {@link destroyTexture}.
169
427
  */
170
- export function createPipeline(
428
+ export function createPipelineTexture(
171
429
  vertexSrc: string,
172
430
  fragmentSrc: string,
173
431
  width: number,
174
432
  height: number,
433
+ params?: ShaderParams | null,
175
434
  opts?: {
176
- params?: Record<string, number>
177
- textures?: Record<string, number>
435
+ textures?: Record<string, TextureId>
178
436
  attributes?: VertexAttribute[]
179
- buffer?: number
437
+ buffer?: BufferId
180
438
  topology?: Topology
181
- vertexCount?: number
182
439
  depth?: boolean
440
+ depthWrite?: boolean
441
+ blend?: BlendMode
183
442
  clearColor?: [number, number, number, number]
184
- },
185
- ): number
443
+ render?: "auto" | "manual"
444
+ loadOp?: "clear" | "load"
445
+ } & DrawRange &
446
+ SamplerOptions &
447
+ LabelOption,
448
+ ): TextureId
186
449
 
187
450
  /**
188
451
  * Create a vertex buffer from raw bytes (interleave attribute data to match
189
452
  * the pipeline's attribute list). Buffer ids are their own space, separate
190
453
  * from texture ids.
191
454
  */
192
- export function createBuffer(data: Uint8Array): number
455
+ export function createBuffer(data: Uint8Array, opts?: LabelOption): BufferId
193
456
  /**
194
457
  * Overwrite part of a vertex buffer at `byteOffset` (default 0), within the
195
458
  * size it was created with. Pipelines drawing from the buffer re-render
196
459
  * with their last-applied params.
197
460
  */
198
- export function writeBuffer(id: number, data: Uint8Array, byteOffset?: number): void
199
- /** Destroy a vertex buffer. Destroy pipelines drawing from it first. */
200
- 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
201
497
  /**
202
- * Set how many vertices a pipeline texture draws and re-render it, e.g.
203
- * 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.
204
509
  */
205
- export function setDrawCount(id: number, count: number): void
510
+ export function copyTexture(src: TextureId, dst: TextureId): void
206
511
  /**
207
512
  * Capture a render-tree node's subtree into a new GPU texture, resolving once
208
513
  * it has been rendered on the next paint pass. The node must be attached to
209
- * the live tree (a detached node is never painted, so its capture rejects).
514
+ * the live tree (an unmounted node is never painted, so its capture rejects)
515
+ * and paint a non-zero box. A laid-out node captures its layout box. A `d-*`
516
+ * node has no layout box - that is what detached means - so it captures its
517
+ * painted box instead: its own `w`/`h` when set, else the nearest laid-out
518
+ * ancestor's box (the same box the render tree reports for it), with its
519
+ * `x`/`y` paint offset mapped to the texture origin.
210
520
  * Rendered at the current display scale, so `width`/`height` are the texture's
211
521
  * actual pixel dimensions (ceil(logicalSize * displayScale)), not logical
212
522
  * points. Each call returns an independent id you must {@link destroyTexture}
@@ -230,11 +540,12 @@ declare module "flux:gpu" {
230
540
  * must stay current has to come from a source that updates in place: another
231
541
  * pipeline's render target, a camera texture, a mutable texture.
232
542
  */
233
- 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 }>
234
544
  /**
235
545
  * Read back a registered texture's current pixels as RGBA8 (tightly packed,
236
546
  * top-to-bottom rows), for any texture id whatever created it (createTexture,
237
- * createShader, captureSnapshot). Synchronous. Throws if the id is unknown.
547
+ * createShaderTexture, captureSnapshot). Synchronous. Throws if the id is
548
+ * unknown.
238
549
  */
239
- export function readTexture(id: number): { width: number; height: number; data: Uint8Array }
550
+ export function readTexture(id: TextureId): { width: number; height: number; data: Uint8Array }
240
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.39",
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