@solidrt/core 0.0.40 → 0.0.42

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/AGENTS.md CHANGED
@@ -55,8 +55,9 @@ you need the raw fact (e.g. `env.displayScale` for asset sizing below).
55
55
  Because the drawn size is fluid and the display DPI varies, asset format is a
56
56
  real design decision, not an afterthought:
57
57
 
58
- - Prefer VECTORS (`<svg>`, `<d-path>`) whenever the render size is fluid or DPI
59
- varies - they stay crisp at any size x `displayScale()`.
58
+ - Prefer VECTORS (`parseSvg` draws mapped to `<d-path>` in a `viewBox` view)
59
+ whenever the render size is fluid or DPI varies - they stay crisp at any
60
+ size x `displayScale()`.
60
61
  - RASTER (`<texture>` / `createImage`) needs source resolution >= displayed size
61
62
  x `env.displayScale`, or it softens on hi-DPI. Author raster at 2-3x the
62
63
  largest size you will ever draw it. A 256px PNG blown up large on a hi-DPI
@@ -139,7 +140,17 @@ Peer deps @solidjs/signals and @solidjs/universal must match (currently
139
140
  - Events: there is NO `onClick`/`onPress`. A "button" is a `<view>`/`<rect>`
140
141
  with `onPointerDown`. Handlers: onPointerDown/Up/Move/Enter/Leave, onWheel,
141
142
  onKeyDown/Up, onTextInput, onFocus/onBlur. Text entry: focus a node with an
142
- `onTextInput` handler (setFocus activates the on-screen keyboard).
143
+ `onTextInput` handler. Focus alone never raises the on-screen keyboard: a
144
+ tap on the focused node (or explicit startTextInput()) does, and never
145
+ while a physical keyboard is attached; on keyboard-equipped platforms the
146
+ session starts invisibly at focus so text flows immediately.
147
+ `textInputHints` on the node configures the IME (type/capitalize/
148
+ autocorrect) - identifier fields and terminals want
149
+ `{ capitalize: "none", autocorrect: false }` (OS default auto-capitalizes). Key
150
+ events start at the focused node and bubble leaf->root to the window (with
151
+ nothing focused, the window alone), so `<window onKeyDown>` is the
152
+ app-global shortcut point; `stopPropagation()` ends the walk. `focusable`
153
+ declares focus-navigation candidacy (enumerate via getFocusables()).
143
154
 
144
155
  - Reactivity is SolidJS 2.0 (`@solidjs/signals`), NOT Solid 1.x. `createSignal`
145
156
  is as you expect, but `createEffect` takes the 2.0 two-function shape: a
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A low-level toolkit for creating cross-platform applications.
4
4
 
5
- _SolidRT is in pre-alpha stage. Anything can and will be changed._
5
+ _SolidRT is in alpha: useful today, but APIs are still stabilizing._
6
6
 
7
7
  > LLM agents: see [AGENTS.md](./AGENTS.md) for a dense, self-contained quickstart.
8
8
 
@@ -35,11 +35,12 @@ for the element/prop model see `@solidrt/core/AGENTS.md`.
35
35
  ## Images and GPU
36
36
  - `image.tsx` - `createImage` (async value: fetch + decode + upload) read inside a `<Loading>` boundary and shown with `<texture>`.
37
37
  - `inline-image.tsx` - bytes already in memory: `decodeImage` + `createTexture` (both synchronous) show an image with no `<Loading>` boundary. The sync counterpart to `image.tsx`.
38
- - `gpu-shader.tsx` - a GLSL fragment shader rendered to a texture, animated by driving its `iTime` uniform declaratively through the `<texture params={{...}}>` prop. Shows both source dialects side by side: without a `#version` line the runtime injects the `vUV`/`iResolution`/`iTime`/`fragColor` preamble, while a source starting with `#version 300 es` is compiled exactly as written and names its own uniforms - which is what lets a shader written elsewhere run unchanged.
38
+ - `gpu-shader.tsx` - a GLSL fragment shader rendered to a texture via `createShaderTexture`, animated by driving its own `uTime` uniform declaratively through the `<texture params={{...}}>` prop. Shows both source dialects side by side: without a `#version` line the runtime injects the `vUV`/`iResolution`/`fragColor` preamble (exactly what the runtime provides - app-driven uniforms are the source's own declarations), while a source starting with `#version 300 es` is compiled exactly as written and names its own uniforms - which is what lets a shader written elsewhere run unchanged.
39
39
  - `gpu-texture-blend.tsx` - compositing two shader targets as stacked `<texture>` layers with `blendMode` ("plus" additive over a base pass), the alternative to a third shader that samples both. Click toggles against `"source-over"` to show why. The tree-level counterpart to blending within one draw (`gpu-particles.tsx`); without `blend: "add"` a target's own draw runs with GL blending disabled.
40
- - `gpu-raw-program.tsx` - the raw shading layer: compileShader/linkProgram/createShaderTarget, one vertex stage shared by two programs, with and without the standard header.
41
- - `gpu-pipeline.tsx` - `createPipeline`: a custom vertex+fragment pair drawing an interleaved vertex buffer (position and color attributes resolved by name), a depth-tested spinning cube whose `uTime` uniform is driven through `<texture params>` exactly like a fragment shader's.
40
+ - `gpu-raw-program.tsx` - the raw shading layer: compileShader/linkProgram/createRenderPipeline/createShaderTarget, one vertex stage shared by two programs, with and without the standard header.
41
+ - `gpu-pipeline.tsx` - `createPipelineTexture`: a custom vertex+fragment pair drawing an interleaved vertex buffer (position and color attributes resolved by name), a depth-tested spinning cube whose `uTime` uniform is driven through `<texture params>` exactly like a fragment shader's.
42
42
  - `gpu-particles.tsx` - an additive particle field: `topology: "points"` with `blend: "add"`, so overlapping gaussian splats (`gl_PointSize` from the vertex stage, `gl_PointCoord` falloff, premultiplied output) accumulate into a glowing volume instead of overwriting. The tints are typed vec3 uniforms filled from 3-number array params.
43
+ - `gpu-instancing.tsx` - instanced drawing: one 3-vertex triangle drawn hundreds of times via `instanceCount`, each instance placed and tinted on a phyllotaxis spiral from `gl_InstanceID` alone. `setDraw(id, { instanceCount })` merges into the draw range per frame (absent keys keep their values, like params), so the population breathes without touching the buffer.
43
44
  - `window-shader.tsx` - the `shader` prop on `<window>`: the finished frame drawn through a raw-linked warp program before present, click to toggle between warp and identity.
44
45
  - `window-shader-history.tsx` - the window shader's frame history: `previous` binds last frame as uPrevious, drawn as a one-frame motion echo behind an orbiting square; click toggles the echo term.
45
46
 
@@ -47,7 +48,7 @@ for the element/prop model see `@solidrt/core/AGENTS.md`.
47
48
  - `sound.tsx` - `createSound`: decode a clip once from bytes (here a binary import), replay cheaply; `overlap` stacking vs single-voice, `playing()` signal, release on unmount. Points to `createSoundStream` for long tracks streamed from a path.
48
49
 
49
50
  ## Vector graphics
50
- - `svg.tsx` - `<svg src={...}>` draws a whole SVG *document string* (not HTML/JSX children); multi-color fills vs a `currentColor` icon recolored by the `color` prop. This is how to use existing icon libraries (Lucide, Heroicons, etc.) - hand their SVG source to `src`.
51
+ - `parse-svg.tsx` - `parseSvg` turns a whole SVG *document string* (not HTML/JSX children) into plain draw data mapped to `<d-path>` inside a `viewBox`-fitted view; per-shape hover highlighting shows the payoff (exact-outline hit testing, recolor without re-parse), plus a `currentColor` icon recolored via the `color` option. This is how to use existing icon libraries (Lucide, Heroicons, etc.) - hand their SVG source to `parseSvg`.
51
52
 
52
53
  ## Bundling assets
53
54
  - `binary-import.tsx` - `import bytes from "./file" with { type: "binary" }` inlines a file's bytes into the bundle as a `Uint8Array` (the bytes are in memory, so `inline-image.tsx` displays them with the synchronous `decodeImage` + `createTexture` path).
@@ -0,0 +1,70 @@
1
+ // Instanced drawing: one 3-vertex triangle in the buffer, drawn hundreds of
2
+ // times with instanceCount (glDrawArraysInstanced, native ES 3.0). The vertex
3
+ // stage tells copies apart by gl_InstanceID - here each instance becomes one
4
+ // petal on a phyllotaxis spiral, placed and tinted from its index alone, so
5
+ // the geometry buffer stays 6 floats no matter the population.
6
+ //
7
+ // The draw range is data: setDraw merges partial updates like params, so the
8
+ // per-frame write below changes only instanceCount while firstVertex and
9
+ // vertexCount keep their values (the whole buffer). instanceCount 0 would
10
+ // draw nothing - the population legitimately breathes down to a single
11
+ // petal and could go dark the same way. gl_InstanceID always counts from 0
12
+ // (ES 3.0 has no base instance).
13
+ import { render, onFrame, createSignal } from "@solidrt/core"
14
+ import { createBuffer, createPipelineTexture, glsl, setDraw } from "@solidrt/core/gpu"
15
+
16
+ let MAX_PETALS = 324
17
+
18
+ let VERTEX = glsl`
19
+ in vec2 aPos;
20
+ out vec3 vTint;
21
+ uniform float uTime;
22
+
23
+ void main() {
24
+ // One instance = one petal: radius grows with sqrt(index) and the angle
25
+ // steps by the golden angle (~2.39996 rad) - the sunflower layout.
26
+ float i = float(gl_InstanceID);
27
+ float angle = i * 2.39996 + uTime * 0.3;
28
+ float radius = 0.045 * sqrt(i);
29
+ // Petals shrink toward the rim and point outward along the spiral.
30
+ float scale = mix(0.05, 0.016, sqrt(i) / 18.0);
31
+ float c = cos(angle), s = sin(angle);
32
+ vec2 p = radius * vec2(c, s) + mat2(c, s, -s, c) * (aPos * scale);
33
+ gl_Position = vec4(p, 0.0, 1.0);
34
+ vTint = 0.5 + 0.5 * cos(6.2832 * (i / 96.0) + vec3(0.0, 2.1, 4.2));
35
+ }
36
+ `
37
+
38
+ let FRAGMENT = glsl`
39
+ in vec3 vTint;
40
+
41
+ void main() {
42
+ fragColor = vec4(vTint, 1.0);
43
+ }
44
+ `
45
+
46
+ function App() {
47
+ // The whole mesh: one triangle, reused by every instance.
48
+ let bufferId = createBuffer(new Float32Array([0, 1.3, -1, -0.75, 1, -0.75]), { label: "petal-tri" })
49
+ let id = createPipelineTexture(VERTEX, FRAGMENT, 512, 512, { uTime: 0 }, {
50
+ label: "petals",
51
+ attributes: [{ name: "aPos", format: "vec2" }],
52
+ buffer: bufferId,
53
+ instanceCount: 1,
54
+ clearColor: [0.03, 0.03, 0.06, 1],
55
+ })
56
+ let [time, setTime] = createSignal(0)
57
+ onFrame((tick) => {
58
+ let t = tick / 1000
59
+ setTime(t)
60
+ setDraw(id, { instanceCount: Math.round(MAX_PETALS / 2 + (MAX_PETALS / 2 - 1) * Math.sin(t * 0.7)) })
61
+ })
62
+
63
+ return (
64
+ <window alignItems="center" justifyContent="center">
65
+ <texture src={id} params={{ uTime: time() }} width={420} height={420} />
66
+ </window>
67
+ )
68
+ }
69
+
70
+ render(() => <App />)
@@ -1,4 +1,4 @@
1
- // An additive particle field: createPipeline with topology "points" and
1
+ // An additive particle field: createPipelineTexture with topology "points" and
2
2
  // blend "add". Each vertex is one particle; the vertex stage sets
3
3
  // gl_PointSize (honored across 4..64px) and the fragment stage shapes the
4
4
  // splat from gl_PointCoord. With blend: "add" overlapping splats accumulate
@@ -16,43 +16,43 @@
16
16
  //
17
17
  // The tints are typed (vec3) uniforms driven from 3-number array params.
18
18
  import { render, onFrame, createSignal } from "@solidrt/core"
19
- import { createBuffer, createPipeline } from "@solidrt/core/gpu"
19
+ import { createBuffer, createPipelineTexture, glsl } from "@solidrt/core/gpu"
20
20
 
21
- let VERTEX = `
22
- in vec3 aPos;
23
- in float aSeed;
24
- out float vSeed;
25
- uniform float uTime;
21
+ let VERTEX = glsl`
22
+ in vec3 aPos;
23
+ in float aSeed;
24
+ out float vSeed;
25
+ uniform float uTime;
26
26
 
27
- void main() {
28
- float cy = cos(uTime * 0.4), sy = sin(uTime * 0.4);
29
- vec3 p = vec3(cy * aPos.x - sy * aPos.z, aPos.y, sy * aPos.x + cy * aPos.z);
30
- // Each particle breathes on its own phase.
31
- p *= 1.0 + 0.15 * sin(uTime * 1.7 + aSeed * 40.0);
32
- p.z += 2.2;
27
+ void main() {
28
+ float cy = cos(uTime * 0.4), sy = sin(uTime * 0.4);
29
+ vec3 p = vec3(cy * aPos.x - sy * aPos.z, aPos.y, sy * aPos.x + cy * aPos.z);
30
+ // Each particle breathes on its own phase.
31
+ p *= 1.0 + 0.15 * sin(uTime * 1.7 + aSeed * 40.0);
32
+ p.z += 2.2;
33
33
 
34
- // Same perspective mapping as gpu-pipeline.tsx (near 1, far 10), clip y
35
- // negated so camera-up displays up.
36
- float f = 2.0;
37
- gl_Position = vec4(p.x * f, -p.y * f, p.z * (11.0 / 9.0) - 20.0 / 9.0, p.z);
38
- gl_PointSize = mix(10.0, 26.0, aSeed) / p.z;
39
- vSeed = aSeed;
40
- }
34
+ // Same perspective mapping as gpu-pipeline.tsx (near 1, far 10), clip y
35
+ // negated so camera-up displays up.
36
+ float f = 2.0;
37
+ gl_Position = vec4(p.x * f, -p.y * f, p.z * (11.0 / 9.0) - 20.0 / 9.0, p.z);
38
+ gl_PointSize = mix(10.0, 26.0, aSeed) / p.z;
39
+ vSeed = aSeed;
40
+ }
41
41
  `
42
42
 
43
- let FRAGMENT = `
44
- in float vSeed;
45
- uniform vec3 uTintA;
46
- uniform vec3 uTintB;
43
+ let FRAGMENT = glsl`
44
+ in float vSeed;
45
+ uniform vec3 uTintA;
46
+ uniform vec3 uTintB;
47
47
 
48
- void main() {
49
- // Soft gaussian falloff over the point sprite; gl_PointCoord is 0..1
50
- // across the splat.
51
- vec2 d = gl_PointCoord - 0.5;
52
- float a = exp(-dot(d, d) * 14.0) * 0.35;
53
- vec3 tint = mix(uTintA, uTintB, vSeed);
54
- fragColor = vec4(tint * a, a);
55
- }
48
+ void main() {
49
+ // Soft gaussian falloff over the point sprite; gl_PointCoord is 0..1
50
+ // across the splat.
51
+ vec2 d = gl_PointCoord - 0.5;
52
+ float a = exp(-dot(d, d) * 14.0) * 0.35;
53
+ vec3 tint = mix(uTintA, uTintB, vSeed);
54
+ fragColor = vec4(tint * a, a);
55
+ }
56
56
  `
57
57
 
58
58
  // Interleaved [pos vec3, seed f32]: points on a fibonacci sphere, so the
@@ -71,9 +71,9 @@ function particles(count: number): Float32Array {
71
71
  }
72
72
 
73
73
  function App() {
74
- let bufferId = createBuffer(particles(1500))
75
- let id = createPipeline(VERTEX, FRAGMENT, 512, 512, {
76
- params: { uTime: 0, uTintA: [1.0, 0.45, 0.15], uTintB: [0.25, 0.5, 1.0] },
74
+ let bufferId = createBuffer(particles(1500), { label: "particle-verts" })
75
+ let id = createPipelineTexture(VERTEX, FRAGMENT, 512, 512, { uTime: 0, uTintA: [1.0, 0.45, 0.15], uTintB: [0.25, 0.5, 1.0] }, {
76
+ label: "particles",
77
77
  attributes: [
78
78
  { name: "aPos", format: "vec3" },
79
79
  { name: "aSeed", format: "f32" },
@@ -1,43 +1,44 @@
1
- // createPipeline compiles a custom GLSL ES 3.00 vertex+fragment pair that draws
2
- // an interleaved vertex buffer into a texture, here a spinning cube with depth
3
- // testing. The vertex shader declares `in` attributes matching the pipeline's
4
- // attribute list (locations are resolved by name) and its own varyings; the
5
- // fragment preamble provides fragColor/iResolution/iTime but no vUV. Uniforms
6
- // are driven exactly like createShader: declaratively via the <texture> params
7
- // prop, applied at the next repaint.
1
+ // createPipelineTexture compiles a custom GLSL ES 3.00 vertex+fragment pair
2
+ // that draws an interleaved vertex buffer into a texture, here a spinning
3
+ // cube with depth testing. The vertex shader declares `in` attributes
4
+ // matching the pipeline's attribute list (locations are resolved by name)
5
+ // and its own varyings; the fragment preamble provides fragColor/iResolution
6
+ // but no vUV, and app-driven uniforms (uTime below) are the source's own
7
+ // declarations. Uniforms are driven exactly like createShaderTexture:
8
+ // declaratively via the <texture> params prop, applied at the next repaint.
8
9
  import { render, onFrame, createSignal } from "@solidrt/core"
9
- import { createBuffer, createPipeline } from "@solidrt/core/gpu"
10
+ import { createBuffer, createPipelineTexture, glsl } from "@solidrt/core/gpu"
10
11
 
11
- let VERTEX = `
12
- in vec3 aPos;
13
- in vec3 aColor;
14
- out vec3 vColor;
15
- uniform float uTime;
12
+ let VERTEX = glsl`
13
+ in vec3 aPos;
14
+ in vec3 aColor;
15
+ out vec3 vColor;
16
+ uniform float uTime;
16
17
 
17
- void main() {
18
- float cy = cos(uTime), sy = sin(uTime);
19
- float cx = cos(uTime * 0.7), sx = sin(uTime * 0.7);
20
- mat3 rotY = mat3(cy, 0.0, -sy, 0.0, 1.0, 0.0, sy, 0.0, cy);
21
- mat3 rotX = mat3(1.0, 0.0, 0.0, 0.0, cx, sx, 0.0, -sx, cx);
22
- vec3 p = rotX * (rotY * aPos);
23
- p.z += 2.5;
18
+ void main() {
19
+ float cy = cos(uTime), sy = sin(uTime);
20
+ float cx = cos(uTime * 0.7), sx = sin(uTime * 0.7);
21
+ mat3 rotY = mat3(cy, 0.0, -sy, 0.0, 1.0, 0.0, sy, 0.0, cy);
22
+ mat3 rotX = mat3(1.0, 0.0, 0.0, 0.0, cx, sx, 0.0, -sx, cx);
23
+ vec3 p = rotX * (rotY * aPos);
24
+ p.z += 2.5;
24
25
 
25
- // Perspective projection (near 1, far 10). Clip y is negated: the target's
26
- // memory row 0 is clip y = -1, and Impeller samples row 0 as the top, so
27
- // camera-up needs the flip to be displayed up.
28
- float f = 2.0;
29
- float a = 11.0 / 9.0;
30
- float b = -20.0 / 9.0;
31
- gl_Position = vec4(p.x * f, -p.y * f, p.z * a + b, p.z);
32
- vColor = aColor;
33
- }
26
+ // Perspective projection (near 1, far 10). Clip y is negated: the target's
27
+ // memory row 0 is clip y = -1, and Impeller samples row 0 as the top, so
28
+ // camera-up needs the flip to be displayed up.
29
+ float f = 2.0;
30
+ float a = 11.0 / 9.0;
31
+ float b = -20.0 / 9.0;
32
+ gl_Position = vec4(p.x * f, -p.y * f, p.z * a + b, p.z);
33
+ vColor = aColor;
34
+ }
34
35
  `
35
36
 
36
- let FRAGMENT = `
37
- in vec3 vColor;
38
- void main() {
39
- fragColor = vec4(vColor, 1.0);
40
- }
37
+ let FRAGMENT = glsl`
38
+ in vec3 vColor;
39
+ void main() {
40
+ fragColor = vec4(vColor, 1.0);
41
+ }
41
42
  `
42
43
 
43
44
  // Interleaved [pos vec3, color vec3], 6 faces x 2 triangles, one color per face.
@@ -58,9 +59,11 @@ function cube(): Float32Array {
58
59
  }
59
60
 
60
61
  function App() {
61
- let bufferId = createBuffer(cube())
62
- let id = createPipeline(VERTEX, FRAGMENT, 512, 512, {
63
- params: { uTime: 0 },
62
+ // Labels name the buffer and target in the dev tooling's GPU inventory
63
+ // (and in engine log messages) - free-form, purely diagnostic.
64
+ let bufferId = createBuffer(cube(), { label: "cube-verts" })
65
+ let id = createPipelineTexture(VERTEX, FRAGMENT, 512, 512, { uTime: 0 }, {
66
+ label: "cube",
64
67
  attributes: [
65
68
  { name: "aPos", format: "vec3" },
66
69
  { name: "aColor", format: "vec3" },
@@ -1,74 +1,93 @@
1
1
  // The raw shading layer: compileShader compiles one stage from complete GLSL
2
2
  // ES (nothing injected - the source declares its own #version, precision,
3
3
  // varyings and uniforms), linkProgram links a vertex and a fragment stage
4
- // into a program, and createShaderTarget builds a render target over it. One
5
- // program can back many targets, and creating a target compiles nothing, so
6
- // swapping precompiled programs is free of compilation. Stages can be
7
- // destroyed right after linking; the program keeps its own compiled copies.
4
+ // into a program, createRenderPipeline pairs the program with draw state
5
+ // (none here: attributeless triangles are the defaults), and
6
+ // createShaderTarget builds a render target over the pipeline. One program
7
+ // can back many pipelines, one pipeline many targets, and only the compile
8
+ // step compiles, so swapping precompiled programs is free of compilation.
9
+ // Stages can be destroyed right after linking; the program keeps its own
10
+ // compiled copies.
8
11
  //
9
12
  // A raw program carries its own vertex stage, so a fullscreen pass is a
10
13
  // covering triangle from gl_VertexID with vertexCount: 3. Compare
11
- // gpu-shader.tsx, where the fused createShader does all of this in one call
12
- // with an injected preamble.
14
+ // gpu-shader.tsx, where the fused createShaderTexture does all of this in
15
+ // one call with an injected preamble.
13
16
  import { render, onFrame, createSignal } from "@solidrt/core"
14
- import { compileShader, createShaderTarget, destroyShader, linkProgram } from "@solidrt/core/gpu"
17
+ import {
18
+ compileShader,
19
+ createRenderPipeline,
20
+ createShaderTarget,
21
+ destroyShader,
22
+ glsl,
23
+ linkProgram,
24
+ } from "@solidrt/core/gpu"
15
25
 
16
- let VERTEX = `#version 300 es
17
- precision highp float;
18
- out vec2 vUV;
19
- void main() {
20
- vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
21
- vUV = p;
22
- gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
23
- }
26
+ let VERTEX = glsl`#version 300 es
27
+ precision highp float;
28
+ out vec2 vUV;
29
+ void main() {
30
+ vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
31
+ vUV = p;
32
+ gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
33
+ }
24
34
  `
25
35
 
26
36
  // Two fragment stages linked against the same vertex stage: two programs,
27
37
  // one shared compile of the vertex half.
28
- let WAVES = `#version 300 es
29
- precision highp float;
30
- in vec2 vUV;
31
- out vec4 fragColor;
32
- uniform float iTime;
33
- void main() {
34
- float t = iTime * 2.0;
35
- float a = 0.5 + 0.5 * sin(vUV.x * 10.0 + t);
36
- float b = 0.5 + 0.5 * sin(vUV.y * 10.0 - t * 1.3);
37
- fragColor = vec4(a, b, 1.0 - a * b, 1.0);
38
- }
38
+ let WAVES = glsl`#version 300 es
39
+ precision highp float;
40
+ in vec2 vUV;
41
+ out vec4 fragColor;
42
+ uniform float uTime;
43
+ void main() {
44
+ float t = uTime * 2.0;
45
+ float a = 0.5 + 0.5 * sin(vUV.x * 10.0 + t);
46
+ float b = 0.5 + 0.5 * sin(vUV.y * 10.0 - t * 1.3);
47
+ fragColor = vec4(a, b, 1.0 - a * b, 1.0);
48
+ }
39
49
  `
40
50
 
41
51
  // The standard header ({ header: true }) declares #version, precision,
42
- // iResolution/iTime and fragColor, so this source only adds its own inputs.
43
- let RINGS = `
44
- in vec2 vUV;
45
- void main() {
46
- float d = length(vUV - 0.5);
47
- float r = 0.5 + 0.5 * sin(d * 40.0 - iTime * 3.0);
48
- fragColor = vec4(r, r * 0.6, 1.0 - r, 1.0);
49
- }
52
+ // iResolution and fragColor, so this source only adds its own inputs -
53
+ // including the app-driven time uniform.
54
+ let RINGS = glsl`
55
+ in vec2 vUV;
56
+ uniform float uTime;
57
+ void main() {
58
+ float d = length(vUV - 0.5);
59
+ float r = 0.5 + 0.5 * sin(d * 40.0 - uTime * 3.0);
60
+ fragColor = vec4(r, r * 0.6, 1.0 - r, 1.0);
61
+ }
50
62
  `
51
63
 
52
64
  function App() {
53
65
  let vs = compileShader("vertex", VERTEX)
54
66
  let wavesFs = compileShader("fragment", WAVES)
55
67
  let ringsFs = compileShader("fragment", RINGS, { header: true })
56
- let waves = linkProgram(vs, wavesFs)
57
- let rings = linkProgram(vs, ringsFs)
68
+ // Labels name each object in the dev tooling's GPU inventory, which is
69
+ // what keeps a chain of programs, pipelines, and targets readable. They
70
+ // are free-form and need not be unique - one name per chain works, since
71
+ // the inventory already groups by kind.
72
+ let waves = linkProgram(vs, wavesFs, { label: "waves" })
73
+ let rings = linkProgram(vs, ringsFs, { label: "rings" })
58
74
  destroyShader(vs)
59
75
  destroyShader(wavesFs)
60
76
  destroyShader(ringsFs)
61
77
 
62
- let wavesId = createShaderTarget(waves, 512, 512, { vertexCount: 3, params: { iTime: 0 } })
63
- let ringsId = createShaderTarget(rings, 512, 512, { vertexCount: 3, params: { iTime: 0 } })
78
+ let wavesPipeline = createRenderPipeline(waves, { label: "waves" })
79
+ let ringsPipeline = createRenderPipeline(rings, { label: "rings" })
80
+
81
+ let wavesId = createShaderTarget(wavesPipeline, 512, 512, { uTime: 0 }, { vertexCount: 3, label: "waves" })
82
+ let ringsId = createShaderTarget(ringsPipeline, 512, 512, { uTime: 0 }, { vertexCount: 3, label: "rings" })
64
83
 
65
84
  let [time, setTime] = createSignal(0)
66
85
  onFrame(tick => setTime(tick / 1000))
67
86
 
68
87
  return (
69
88
  <window flexDirection="row" gap={16} alignItems="center" justifyContent="center">
70
- <texture src={wavesId} params={{ iTime: time() }} width={360} height={360} />
71
- <texture src={ringsId} params={{ iTime: time() }} width={360} height={360} />
89
+ <texture src={wavesId} params={{ uTime: time() }} width={360} height={360} />
90
+ <texture src={ringsId} params={{ uTime: time() }} width={360} height={360} />
72
91
  </window>
73
92
  )
74
93
  }
@@ -1,11 +1,12 @@
1
- // createShader compiles a GLSL ES 3.00 fragment shader and renders it into a
2
- // texture, returning a texture id you display with <texture src={id}>. The
3
- // texture is freed automatically when the reactive owner is disposed.
1
+ // createShaderTexture compiles a GLSL ES 3.00 fragment shader and renders it
2
+ // into a texture, returning a texture id you display with <texture src={id}>
3
+ // (the name says what comes back). The texture is freed automatically when
4
+ // the reactive owner is disposed.
4
5
  //
5
6
  // There are two source dialects and the source itself picks which one applies.
6
7
  // WITHOUT a #version line the runtime injects a preamble, so the body may
7
- // reference vUV (0..1, top-left origin), iResolution, iTime, and any uniform
8
- // it declares - the left square below. A source that STARTS
8
+ // reference vUV (0..1, top-left origin), iResolution, and any uniform it
9
+ // declares - the left square below. A source that STARTS
9
10
  // with #version 300 es is taken as complete and compiled exactly as written:
10
11
  // nothing is injected and it names its own uniforms - the right square.
11
12
  //
@@ -16,55 +17,61 @@
16
17
  // a complete source just has to declare `in vec2 vUV;` itself - and a uniform
17
18
  // named iResolution is filled with the target size by name in both dialects.
18
19
  //
19
- // iResolution is filled in for you, but iTime is NOT - drive it (and any other
20
- // uniform) declaratively via the <texture> element's params prop; it applies at
21
- // the next repaint, so a signal updated every frame stays paced to actual frames.
22
- // A param value is a number for a scalar uniform or a flat number array for a
23
- // typed one (2/3/4 numbers for vec2/vec3/vec4, 16 column-major for mat4),
24
- // dispatched by the shader's own declaration - uTint below is a vec3 driven
25
- // from one array value. The shader's size is baked in at creation.
20
+ // The preamble declares exactly what the runtime provides; anything the app
21
+ // drives is the shader's own declaration in both dialects - `uniform float
22
+ // uTime;` below - fed declaratively via the <texture> element's params prop.
23
+ // A param applies at the next repaint, so a signal updated every frame stays
24
+ // paced to actual frames. A param value is a number for a scalar uniform or a
25
+ // flat number array for a typed one (2/3/4 numbers for vec2/vec3/vec4, 16
26
+ // column-major for mat4), dispatched by the shader's own declaration - uTint
27
+ // below is a vec3 driven from one array value. The shader's size is baked in
28
+ // at creation.
26
29
  import { render, onFrame, createSignal } from "@solidrt/core"
27
- import { createShader } from "@solidrt/core/gpu"
30
+ import { createShaderTexture, glsl } from "@solidrt/core/gpu"
28
31
 
29
- // Injected-preamble dialect: no #version, no declarations, no main() plumbing.
30
- let FRAGMENT = `
31
- void main() {
32
- vec2 uv = vUV;
33
- float t = iTime * 2.0;
34
- float a = 0.5 + 0.5 * sin(uv.x * 10.0 + t);
35
- float b = 0.5 + 0.5 * sin(uv.y * 10.0 - t * 1.3);
36
- float c = 0.5 + 0.5 * sin((uv.x + uv.y) * 8.0 + t * 0.7);
37
- fragColor = vec4(a, b, c, 1.0);
38
- }
32
+ // Injected-preamble dialect: no #version, no main() plumbing; uniforms the
33
+ // app drives are declared here, like any other.
34
+ let FRAGMENT = glsl`
35
+ uniform float uTime;
36
+ void main() {
37
+ vec2 uv = vUV;
38
+ float t = uTime * 2.0;
39
+ float a = 0.5 + 0.5 * sin(uv.x * 10.0 + t);
40
+ float b = 0.5 + 0.5 * sin(uv.y * 10.0 - t * 1.3);
41
+ float c = 0.5 + 0.5 * sin((uv.x + uv.y) * 8.0 + t * 0.7);
42
+ fragColor = vec4(a, b, c, 1.0);
43
+ }
39
44
  `
40
45
 
41
46
  // Complete-source dialect: declares its own version, precision, varying and
42
- // output, and calls its time uniform uSpin rather than iTime. uTint is a
43
- // typed (vec3) uniform, filled from a 3-number array param.
44
- let RAW_FRAGMENT = `#version 300 es
45
- precision highp float;
46
- in vec2 vUV;
47
- out vec4 fragColor;
48
- uniform float uSpin;
49
- uniform vec3 uTint;
50
- void main() {
51
- vec2 p = vUV - 0.5;
52
- float a = atan(p.y, p.x) + uSpin;
53
- float r = length(p);
54
- float band = 0.5 + 0.5 * sin(a * 6.0 + r * 18.0);
55
- fragColor = vec4(band * uTint.r, band * uTint.g, 1.0 - band * uTint.b, 1.0);
56
- }
47
+ // output, and calls its time uniform uSpin. uTint is a typed (vec3) uniform,
48
+ // filled from a 3-number array param.
49
+ let RAW_FRAGMENT = glsl`#version 300 es
50
+ precision highp float;
51
+ in vec2 vUV;
52
+ out vec4 fragColor;
53
+ uniform float uSpin;
54
+ uniform vec3 uTint;
55
+ void main() {
56
+ vec2 p = vUV - 0.5;
57
+ float a = atan(p.y, p.x) + uSpin;
58
+ float r = length(p);
59
+ float band = 0.5 + 0.5 * sin(a * 6.0 + r * 18.0);
60
+ fragColor = vec4(band * uTint.r, band * uTint.g, 1.0 - band * uTint.b, 1.0);
61
+ }
57
62
  `
58
63
 
59
64
  function App() {
60
- let id = createShader(FRAGMENT, 512, 512, { iTime: 0 })
61
- let rawId = createShader(RAW_FRAGMENT, 512, 512, { uSpin: 0, uTint: [0.9, 0.4, 0.6] })
65
+ // The label names the target in the dev tooling's GPU inventory and in
66
+ // engine log messages - free-form, purely diagnostic, worth the habit.
67
+ let id = createShaderTexture(FRAGMENT, 512, 512, { uTime: 0 }, { label: "waves" })
68
+ let rawId = createShaderTexture(RAW_FRAGMENT, 512, 512, { uSpin: 0, uTint: [0.9, 0.4, 0.6] }, { label: "spiral" })
62
69
  let [time, setTime] = createSignal(0)
63
70
  onFrame((tick) => setTime(tick / 1000))
64
71
 
65
72
  return (
66
73
  <window alignItems="center" justifyContent="center" flexDirection="row" gap={16}>
67
- <texture src={id} params={{ iTime: time() }} width={260} height={260} />
74
+ <texture src={id} params={{ uTime: time() }} width={260} height={260} />
68
75
  <texture src={rawId} params={{ uSpin: time() }} width={260} height={260} />
69
76
  </window>
70
77
  )