@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 +14 -3
- package/README.md +1 -1
- package/examples/README.md +5 -4
- package/examples/gpu-instancing.tsx +70 -0
- package/examples/gpu-particles.tsx +35 -35
- package/examples/gpu-pipeline.tsx +40 -37
- package/examples/gpu-raw-program.tsx +59 -40
- package/examples/gpu-shader.tsx +48 -41
- package/examples/gpu-texture-blend.tsx +20 -19
- package/examples/inline-image.tsx +1 -1
- package/examples/parse-svg.tsx +94 -0
- package/examples/text-import.tsx +2 -2
- package/examples/wave.glsl +5 -3
- package/examples/window-shader-history.tsx +23 -23
- package/examples/window-shader.tsx +30 -28
- package/jsx-runtime.d.ts +17 -9
- package/package.json +2 -2
- package/src/camera.ts +8 -4
- package/src/color.ts +14 -2
- package/src/core.ts +254 -15
- package/src/environment.ts +16 -3
- package/src/gpu.ts +176 -75
- package/src/image.ts +12 -11
- package/src/index.ts +6 -2
- package/src/renderer.ts +32 -11
- package/src/runtime-modules.d.ts +2 -2
- package/src/speech-recognition.ts +2 -1
- package/src/svg.ts +71 -0
- package/src/types.d.ts +111 -38
- package/src/window.ts +39 -18
- package/examples/svg.tsx +0 -49
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 (
|
|
59
|
-
varies - they stay crisp at any
|
|
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
|
|
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
|
|
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
|
|
package/examples/README.md
CHANGED
|
@@ -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
|
|
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` - `
|
|
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` -
|
|
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:
|
|
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,
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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 =
|
|
76
|
-
|
|
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
|
-
//
|
|
2
|
-
// an interleaved vertex buffer into a texture, here a spinning
|
|
3
|
-
// testing. The vertex shader declares `in` attributes
|
|
4
|
-
// attribute list (locations are resolved by name)
|
|
5
|
-
// fragment preamble provides fragColor/iResolution
|
|
6
|
-
//
|
|
7
|
-
//
|
|
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,
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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,
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
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
|
|
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 {
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
33
|
-
void main() {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
float
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
|
63
|
-
let
|
|
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={{
|
|
71
|
-
<texture src={ringsId} params={{
|
|
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
|
}
|
package/examples/gpu-shader.tsx
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
//
|
|
2
|
-
// texture, returning a texture id you display with <texture src={id}
|
|
3
|
-
//
|
|
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,
|
|
8
|
-
//
|
|
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
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// A param
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
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 {
|
|
30
|
+
import { createShaderTexture, glsl } from "@solidrt/core/gpu"
|
|
28
31
|
|
|
29
|
-
// Injected-preamble dialect: no #version, no
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
43
|
-
//
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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={{
|
|
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
|
)
|