@solidrt/core 0.0.50 → 0.0.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +102 -21
- package/README.md +1 -1
- package/agents/painting.md +61 -0
- package/agents/performance.md +216 -0
- package/docs/index.md +154 -0
- package/docs/reference/detached.md +85 -0
- package/docs/reference/drawing.md +95 -0
- package/docs/reference/elements.md +56 -0
- package/docs/reference/gpu.md +204 -0
- package/docs/reference/index.md +50 -0
- package/docs/reference/input.md +58 -0
- package/docs/reference/layout.md +44 -0
- package/docs/reference/shaders.md +46 -0
- package/docs/reference/text.md +46 -0
- package/docs/reference/transforms.md +35 -0
- package/docs/reference/types.md +34 -0
- package/examples/README.md +7 -5
- package/examples/{sound.tsx → audio.tsx} +1 -1
- package/examples/gpu-pipeline.tsx +2 -2
- package/examples/gpu-sprites.tsx +102 -0
- package/examples/line-points.tsx +145 -0
- package/examples/parse-svg.tsx +6 -6
- package/examples/responsive-grid.tsx +1 -1
- package/examples/scroll.tsx +2 -2
- package/examples/snapshot-texture.tsx +72 -0
- package/examples/{view-viewbox.tsx → view-design-size.tsx} +33 -15
- package/jsx-runtime.d.ts +15 -14
- package/package.json +11 -9
- package/src/{sound.ts → audio.ts} +68 -15
- package/src/color.ts +17 -18
- package/src/core.ts +40 -3
- package/src/data.ts +99 -0
- package/src/gpu.ts +88 -34
- package/src/index.ts +9 -3
- package/src/logo.tsx +92 -0
- package/src/renderer.ts +219 -53
- package/src/runtime-modules.d.ts +7 -2
- package/src/scroll.ts +51 -15
- package/src/svg.ts +1 -1
- package/src/text-input.ts +297 -61
- package/src/types.d.ts +291 -31
- package/src/window.ts +110 -14
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Detached elements
|
|
2
|
+
|
|
3
|
+
Every painting element has a detached twin: `d-view`, `d-rect`, `d-oval`,
|
|
4
|
+
`d-line`, `d-path`, `d-text`, `d-texture`. A detached element has no layout
|
|
5
|
+
box. It is not part of the layout at all, and instead owns its geometry in
|
|
6
|
+
paint-space pixels, positioned in the coordinate system of its parent.
|
|
7
|
+
|
|
8
|
+
That is the whole idea, and the reason to reach for one:
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
let [x, setX] = createSignal(0)
|
|
12
|
+
onFrame((now) => setX(Math.sin(now / 500) * 100))
|
|
13
|
+
|
|
14
|
+
<view width={pct(100)} height={200}>
|
|
15
|
+
<d-rect x={x()} y={20} w={40} h={40} color="tomato" />
|
|
16
|
+
</view>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Moving that rect writes one number to one native property. Nothing reflows,
|
|
20
|
+
because there is nothing in the layout to reflow. The same content as a
|
|
21
|
+
laid-out `<rect>` with `left={x()}` would put the layout engine on the path of
|
|
22
|
+
every frame.
|
|
23
|
+
|
|
24
|
+
**Reach for the detached form first for anything that moves at animation
|
|
25
|
+
frequency**, and for content authored in fixed design units - a chart, a
|
|
26
|
+
diagram, an svg drawing, a particle field. Reach for the layout form when you
|
|
27
|
+
want the element to participate in a flex or grid arrangement, which is most
|
|
28
|
+
static UI.
|
|
29
|
+
|
|
30
|
+
## What changes
|
|
31
|
+
|
|
32
|
+
A detached element composes exactly the same paint, text and pointer props as
|
|
33
|
+
its layout twin. One thing is swapped: the [layout](/core/reference/layout/)
|
|
34
|
+
props are replaced by geometry props.
|
|
35
|
+
|
|
36
|
+
| | Layout form | Detached form |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| Position | flex or grid placement, `top`/`left`, margins | `x`, `y` in the parent's coordinates |
|
|
39
|
+
| Size | `width`/`height` and the box it is given | `w`, `h`, defaulting to the inherited box |
|
|
40
|
+
| Costs a reflow | yes | no |
|
|
41
|
+
|
|
42
|
+
`d-view` is the container case: it composes `ViewOwnProps` directly (see
|
|
43
|
+
[elements](/core/reference/elements/)), so it still transforms, clips and
|
|
44
|
+
takes input, but a layout prop on it is dropped with a one-time warning
|
|
45
|
+
rather than silently ignored.
|
|
46
|
+
|
|
47
|
+
`<span>` has no detached form, since an inline run never has a box of its own
|
|
48
|
+
to detach from.
|
|
49
|
+
|
|
50
|
+
## Geometry
|
|
51
|
+
|
|
52
|
+
Detached geometry is paint-space pixels and never affects layout.
|
|
53
|
+
|
|
54
|
+
{{ decl packages/core/src/types.d.ts PositionProps }}
|
|
55
|
+
|
|
56
|
+
Most primitives add a size, defaulting to the box they inherit from their
|
|
57
|
+
ancestor, so a `d-rect` with only `x`/`y` still has something to draw:
|
|
58
|
+
|
|
59
|
+
{{ decl packages/core/src/types.d.ts GeometryProps }}
|
|
60
|
+
|
|
61
|
+
`d-oval` measures its box rather than a radius:
|
|
62
|
+
|
|
63
|
+
{{ decl packages/core/src/types.d.ts OvalGeometryProps }}
|
|
64
|
+
|
|
65
|
+
For `d-text`, width is the shaping width and height is reported bounds only,
|
|
66
|
+
since a paragraph's height always falls out of the text itself:
|
|
67
|
+
|
|
68
|
+
{{ decl packages/core/src/types.d.ts TextGeometryProps }}
|
|
69
|
+
|
|
70
|
+
`d-path` takes only a position, since its size is whatever its `d` string
|
|
71
|
+
draws. `d-line` has no width and height either: its geometry is its two
|
|
72
|
+
endpoints (or, as a polyline, its `points`), which is what makes it the
|
|
73
|
+
primitive to reach for when the geometry moves. Both report their bounds
|
|
74
|
+
(getBoundingBox, the tree, a capture) as that geometry plus the stroke, not
|
|
75
|
+
the inherited box:
|
|
76
|
+
|
|
77
|
+
{{ decl packages/core/src/types.d.ts LineGeometryProps }}
|
|
78
|
+
|
|
79
|
+
## The other x and y
|
|
80
|
+
|
|
81
|
+
`x` and `y` also appear on [transform](/core/reference/transforms/) props,
|
|
82
|
+
where they mean a post-layout subtree translation and exist on laid-out views
|
|
83
|
+
too. The two never collide on one element: on a `d-*` primitive `x`/`y` is
|
|
84
|
+
where the geometry is drawn, and on a `view` or `d-view` it is a translation
|
|
85
|
+
applied to the whole subtree. Both are cheap; neither triggers layout.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Drawing
|
|
2
|
+
|
|
3
|
+
The primitives that put pixels on the screen: `rect`, `oval`, `line`, `path`
|
|
4
|
+
and `texture`. They share one paint vocabulary and differ only in the geometry
|
|
5
|
+
they draw.
|
|
6
|
+
|
|
7
|
+
In their layout form they take no geometry props at all. A shape derives its
|
|
8
|
+
geometry from the layout box it sits in, which is why a `rect` with no props
|
|
9
|
+
fills its parent. To place one freely, use the
|
|
10
|
+
[detached form](/core/reference/detached/) instead.
|
|
11
|
+
|
|
12
|
+
## Paint
|
|
13
|
+
|
|
14
|
+
Fill, stroke and blending, shared by every drawing primitive:
|
|
15
|
+
|
|
16
|
+
{{ decl packages/core/src/types.d.ts PaintProps }}
|
|
17
|
+
|
|
18
|
+
`color` takes a CSS color string or a gradient from `createLinearGradient` /
|
|
19
|
+
`createRadialGradient`. `drawStyle` picks fill, stroke, or both, and the
|
|
20
|
+
stroke props apply to the stroked part.
|
|
21
|
+
|
|
22
|
+
Where the stroke sits relative to the geometry differs by primitive, and it is
|
|
23
|
+
deliberate: a stroked `rect` or `oval` paints *inside* its box like a CSS
|
|
24
|
+
border, so nothing bleeds past the box for a clip to cut, while `line` and
|
|
25
|
+
`path` strokes stay centered on their geometry, where the geometry is the
|
|
26
|
+
stroke rather than a box.
|
|
27
|
+
|
|
28
|
+
## Dashing
|
|
29
|
+
|
|
30
|
+
A stroke's dash pattern, on `line` and `path`:
|
|
31
|
+
|
|
32
|
+
{{ decl packages/core/src/types.d.ts DashProps }}
|
|
33
|
+
|
|
34
|
+
The pattern is walked along the geometry itself: through a polyline's
|
|
35
|
+
vertices and along a path's curves, restarting at each subpath of a path.
|
|
36
|
+
`dashOffset` slides it - write it every frame for marching ants, or
|
|
37
|
+
transition it for a one-shot slide. A dashed stroke keeps its caps on every
|
|
38
|
+
dash, and a stroke-and-fill path dashes only the stroke.
|
|
39
|
+
|
|
40
|
+
`pathLength` declares what the geometry's length counts as, so the pattern
|
|
41
|
+
can be written in fractions of it: with `pathLength={1}`, `onLength={0.77}
|
|
42
|
+
offLength={1}` draws the first 77%, and transitioning `onLength` from 0 to
|
|
43
|
+
1 draws the geometry on (the SVG line-drawing trick, without having to know
|
|
44
|
+
the length).
|
|
45
|
+
|
|
46
|
+
## rect
|
|
47
|
+
|
|
48
|
+
{{ decl packages/core/src/types.d.ts RectProps }}
|
|
49
|
+
|
|
50
|
+
## oval
|
|
51
|
+
|
|
52
|
+
{{ decl packages/core/src/types.d.ts OvalProps }}
|
|
53
|
+
|
|
54
|
+
## line
|
|
55
|
+
|
|
56
|
+
{{ decl packages/core/src/types.d.ts LineProps }}
|
|
57
|
+
|
|
58
|
+
A laid-out `<line>` without `points` draws its box's top-left-to-bottom-right
|
|
59
|
+
diagonal, so in practice it is a rule: give it a thin box. Endpoints are a
|
|
60
|
+
detached-only concept, so arbitrary angles and connectors want `d-line`.
|
|
61
|
+
`points` makes either form a polyline - a flat `[x0, y0, x1, y1, ...]` array
|
|
62
|
+
(or a `Float32Array`), optionally `closed` - the numeric middle ground between
|
|
63
|
+
a segment and a path: animate it by writing a new array, nothing is parsed.
|
|
64
|
+
Curves want a path.
|
|
65
|
+
|
|
66
|
+
## path
|
|
67
|
+
|
|
68
|
+
{{ decl packages/core/src/types.d.ts PathProps }}
|
|
69
|
+
|
|
70
|
+
`d` is an SVG path string. Reach for `line` instead when the geometry is
|
|
71
|
+
numbers that animate (endpoints, or a polyline's `points`): a path animates
|
|
72
|
+
by rebuilding its `d` string, where a line moves one number or one array.
|
|
73
|
+
A path dashes like a line (see Dashing above), the pattern restarting at
|
|
74
|
+
each subpath; the dash props are paint-only writes, the `d` is not re-parsed.
|
|
75
|
+
|
|
76
|
+
## texture
|
|
77
|
+
|
|
78
|
+
A GPU texture: a decoded image, a camera frame, a video frame, or a shader
|
|
79
|
+
render target. `src` is a texture id, never a URL, which keeps one currency
|
|
80
|
+
for every pixel source in the engine.
|
|
81
|
+
|
|
82
|
+
{{ decl packages/core/src/types.d.ts TextureProps }}
|
|
83
|
+
|
|
84
|
+
## Logo
|
|
85
|
+
|
|
86
|
+
The SolidRT brand mark, the same seven-segment puzzle the scaffold's welcome
|
|
87
|
+
screen and the launcher draw, as a component: a square `view` of `size`
|
|
88
|
+
pixels with the segments as gradient-filled `d-path`s.
|
|
89
|
+
|
|
90
|
+
{{ decl packages/core/src/logo.tsx LogoProps }}
|
|
91
|
+
|
|
92
|
+
The default is static and requests no frames; `"once"` and `"loop"` drive a
|
|
93
|
+
staggered per-segment fade through `onFrame`, so the animated forms hold a
|
|
94
|
+
frame request only while they run (`"once"` releases it when the last
|
|
95
|
+
segment is in).
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Elements
|
|
2
|
+
|
|
3
|
+
The two structural elements. Neither one paints: `window` is the root, `view`
|
|
4
|
+
is a box that lays out, clips, transforms and receives input. Everything
|
|
5
|
+
visible comes from the [drawing](/core/reference/drawing/) and
|
|
6
|
+
[text](/core/reference/text/) primitives inside them.
|
|
7
|
+
|
|
8
|
+
## window
|
|
9
|
+
|
|
10
|
+
One per app, the root of the tree. It composes
|
|
11
|
+
[layout](/core/reference/layout/) props, so the window is itself the outermost
|
|
12
|
+
flex container, and [pointer](/core/reference/input/) props, which is where
|
|
13
|
+
app-global key handling lives: key events always end their walk at the window
|
|
14
|
+
root.
|
|
15
|
+
|
|
16
|
+
{{ decl packages/core/src/types.d.ts WindowProps }}
|
|
17
|
+
|
|
18
|
+
The `shader` prop runs the finished frame through a GPU program as the last
|
|
19
|
+
step before the screen; see [shaders](/core/reference/shaders/).
|
|
20
|
+
|
|
21
|
+
## view
|
|
22
|
+
|
|
23
|
+
A `view` never paints. There is no `backgroundColor`: you put a `rect` behind
|
|
24
|
+
the content, and a shape with no geometry of its own fills the layout box it
|
|
25
|
+
sits in.
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
<view padding={16} alignItems="center">
|
|
29
|
+
<rect color="#1b2440" radius={12} />
|
|
30
|
+
<text color="white">Boxed</text>
|
|
31
|
+
</view>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Its props split in two. `ViewOwnProps` is everything a view offers besides
|
|
35
|
+
layout, and it is what the detached `d-view` composes on its own:
|
|
36
|
+
|
|
37
|
+
{{ decl packages/core/src/types.d.ts ViewOwnProps }}
|
|
38
|
+
|
|
39
|
+
The laid-out `view` is that plus the layout props:
|
|
40
|
+
|
|
41
|
+
{{ decl packages/core/src/types.d.ts ViewProps }}
|
|
42
|
+
|
|
43
|
+
Three of those props are worth knowing before you need them:
|
|
44
|
+
|
|
45
|
+
- `designSize` fits a design-space coordinate system into the element's box,
|
|
46
|
+
scaled uniformly and centered. Everything under the view - layout, paint,
|
|
47
|
+
input - happens in design units, and the view itself sizes like a
|
|
48
|
+
replaced element whose intrinsic size is the design size. It is the
|
|
49
|
+
natural wrapper for `parseSvg` output, any `d-*` subtree authored in
|
|
50
|
+
fixed units, or a whole laid-out panel that should scale rather than
|
|
51
|
+
reflow.
|
|
52
|
+
- `repaintBoundary` retains the subtree's display list, and in its
|
|
53
|
+
`"snapshot"` forms its rasterized pixels too. It is the lever for putting
|
|
54
|
+
heavy static content next to content that changes every frame.
|
|
55
|
+
- `clipRadius` rounds the clip, and only does anything when `overflow` is
|
|
56
|
+
non-visible.
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# GPU
|
|
2
|
+
|
|
3
|
+
`@solidrt/core/gpu` is the programmable half of rendering: textures you
|
|
4
|
+
upload, shader passes that render into textures, and pipelines that draw
|
|
5
|
+
your own geometry into them. Everything it produces is a texture id, and a
|
|
6
|
+
texture id goes anywhere a texture goes: `<texture src={id} />` to display
|
|
7
|
+
it, a `textures` binding to sample it from another pass, `readTexture` to
|
|
8
|
+
bake it.
|
|
9
|
+
|
|
10
|
+
The imperative primitives (`uploadTexture`, `setTargetParams`, `addDraw`,
|
|
11
|
+
`destroyTexture`, ...) are the `flux:gpu` module and are documented in the
|
|
12
|
+
[runtime reference](/runtime/gui/gpu/). This page is the reactive layer
|
|
13
|
+
over them and the contracts that hold across both.
|
|
14
|
+
|
|
15
|
+
## The model
|
|
16
|
+
|
|
17
|
+
A target is retained, not redrawn. Creating one renders it once; after that
|
|
18
|
+
it re-renders exactly when something it depends on changes - a param, a
|
|
19
|
+
bound texture, a buffer it draws from, its size - and never otherwise. A
|
|
20
|
+
static shader costs zero passes per frame.
|
|
21
|
+
|
|
22
|
+
Sampler bindings are live dependencies. A target bound as another's
|
|
23
|
+
`textures` input re-renders its consumer whenever it re-renders itself, in
|
|
24
|
+
topological order, so a chain (a plasma pass feeding a mesh pipeline) is
|
|
25
|
+
driven by writing only the first target's uniforms. A cycle throws.
|
|
26
|
+
|
|
27
|
+
Targets whose pass is state rather than a function of its inputs
|
|
28
|
+
(accumulation, feedback, simulation) opt out with `render: "manual"`: the
|
|
29
|
+
runtime never renders them, only `renderTarget(id)` does, in call order,
|
|
30
|
+
normally from `onFrame`.
|
|
31
|
+
|
|
32
|
+
## Three layers
|
|
33
|
+
|
|
34
|
+
- **Fused.** `createShaderTexture` compiles a fragment source and renders it
|
|
35
|
+
fullscreen; `createPipelineTexture` does the same over your own vertex
|
|
36
|
+
buffer. One call, one texture, program and target sharing a lifetime. The
|
|
37
|
+
shader-toy shape.
|
|
38
|
+
- **Raw.** `compileShader`, `linkProgram`, `createRenderPipeline`, then
|
|
39
|
+
`createShaderTarget` per target. A pipeline (program plus draw state:
|
|
40
|
+
attribute layout, topology, blend, cull, depth) backs any number of targets
|
|
41
|
+
and compiles nothing per target. Reach for it when programs are shared or
|
|
42
|
+
lifetimes differ.
|
|
43
|
+
- **Draw list.** `createDrawTarget` renders many entries in one pass, each
|
|
44
|
+
entry its own pipeline, buffer, params and textures, added and removed with
|
|
45
|
+
`addDraw` / `removeDraw` and sorted with `setDrawOrder`. A scene, in one
|
|
46
|
+
texture.
|
|
47
|
+
|
|
48
|
+
`@solidrt/3d` builds its scene graph on the third layer; nothing there is
|
|
49
|
+
hidden from this one.
|
|
50
|
+
|
|
51
|
+
## Creating
|
|
52
|
+
|
|
53
|
+
Every `create*` helper frees its resource when the reactive owner that
|
|
54
|
+
created it is disposed. Created outside a reactive scope (after an `await`,
|
|
55
|
+
in an event handler with no owner) nothing is registered and the matching
|
|
56
|
+
`destroy*` is yours to call; `{ autoFree: false }` opts out of the auto-free
|
|
57
|
+
inside a scope for resources rebuilt by hand. `label` names the resource in
|
|
58
|
+
the dev tooling's GPU inventory and in engine log messages.
|
|
59
|
+
|
|
60
|
+
{{ decl packages/core/src/gpu.ts createTexture }}
|
|
61
|
+
|
|
62
|
+
{{ decl packages/core/src/gpu.ts createMutableTexture }}
|
|
63
|
+
|
|
64
|
+
{{ decl packages/core/src/gpu.ts createShaderTexture }}
|
|
65
|
+
|
|
66
|
+
{{ decl packages/core/src/gpu.ts createPipelineTexture }}
|
|
67
|
+
|
|
68
|
+
{{ decl packages/core/src/gpu.ts createShaderTarget }}
|
|
69
|
+
|
|
70
|
+
{{ decl packages/core/src/gpu.ts createDrawTarget }}
|
|
71
|
+
|
|
72
|
+
{{ decl packages/core/src/gpu.ts createShaderTextureMemo }}
|
|
73
|
+
|
|
74
|
+
{{ decl packages/core/src/gpu.ts ShaderSpec }}
|
|
75
|
+
|
|
76
|
+
## Buffers
|
|
77
|
+
|
|
78
|
+
{{ decl packages/core/src/gpu.ts createBuffer }}
|
|
79
|
+
|
|
80
|
+
{{ decl packages/core/src/gpu.ts beginBufferWrite }}
|
|
81
|
+
|
|
82
|
+
{{ decl packages/core/src/gpu.ts writeBuffer }}
|
|
83
|
+
|
|
84
|
+
A buffer's size is fixed at creation, an entry's buffers are not: `setDraw`
|
|
85
|
+
(single-draw targets) and `setDrawBuffers` (draw-list entries) re-point a
|
|
86
|
+
role the entry already fills - `buffer`, `indexBuffer` + `indexFormat`,
|
|
87
|
+
`instanceBuffer` - at another buffer. That is how a population grows past
|
|
88
|
+
its reservation: create a larger buffer, write it, swap, destroy the old.
|
|
89
|
+
The swap is replace-only (roles are pipeline layout state) and keeps the
|
|
90
|
+
entry's draw range, rechecked against the new sizes.
|
|
91
|
+
|
|
92
|
+
## Uniforms
|
|
93
|
+
|
|
94
|
+
Uniforms are driven by name. Declaratively, `<texture src={id}
|
|
95
|
+
params={{ uTime: t() }} />` writes the target's params paced to the next real
|
|
96
|
+
repaint, which is the preferred form; `setTargetParams` and `setDrawParams`
|
|
97
|
+
are the imperative forms for targets no element holds. A number drives a
|
|
98
|
+
scalar, a flat number array drives the declared type (2, 3, 4 for `vec2`,
|
|
99
|
+
`vec3`, `vec4`, 16 column-major for `mat4`, element size times length for
|
|
100
|
+
arrays). `textures` binds `sampler2D` uniforms to texture ids the same way.
|
|
101
|
+
|
|
102
|
+
Every write is validated at the call site against the linked program:
|
|
103
|
+
|
|
104
|
+
- a name the program never declares throws, listing the active uniforms;
|
|
105
|
+
- a value whose length does not fit the declared type throws;
|
|
106
|
+
- a `sampler2D` named in `params` (or a non-sampler named in `textures`)
|
|
107
|
+
throws;
|
|
108
|
+
- a name the source declares but the compiler optimized out is accepted
|
|
109
|
+
with a warning and skipped, so one param object can drive several shader
|
|
110
|
+
variants that do not all read every uniform.
|
|
111
|
+
|
|
112
|
+
On a draw target, target-level `params` and `textures` are the shared set
|
|
113
|
+
every entry reads (a camera's view-projection written once per move), applied
|
|
114
|
+
before each entry's own, and a shared name only some entries declare applies
|
|
115
|
+
where declared.
|
|
116
|
+
|
|
117
|
+
The fused preamble declares exactly what the runtime provides: `#version
|
|
118
|
+
300 es`, precision, `vUV` (fragment path), `fragColor`, and `iResolution`,
|
|
119
|
+
filled with the target size in physical pixels. Anything app-driven - a time
|
|
120
|
+
uniform - is the source's own declaration, driven like any other uniform, so
|
|
121
|
+
forgetting to drive it is a compile error rather than a value stuck at zero.
|
|
122
|
+
A source starting with its own `#version` line gets no preamble and compiles
|
|
123
|
+
as written.
|
|
124
|
+
|
|
125
|
+
## Sampling
|
|
126
|
+
|
|
127
|
+
`filter` (`"linear"` default, `"nearest"`), `wrap` (`"clamp"` default,
|
|
128
|
+
`"repeat"`) and `mipmap` (`false` default) are declared at creation and are
|
|
129
|
+
a property of the texture id: `<texture>` display and shader sampling follow
|
|
130
|
+
the same state, so a nearest texture upscales with hard pixels everywhere.
|
|
131
|
+
Changing any of them means a new id.
|
|
132
|
+
|
|
133
|
+
Without a mip chain, shader sampling of a minified texture skips texels and
|
|
134
|
+
aliases (3d surfaces at distance, a target sampled at a fraction of its
|
|
135
|
+
size). `mipmap: true` keeps the chain on the id and the runtime rebuilds it
|
|
136
|
+
after every upload (data textures) and every render (targets) - there is
|
|
137
|
+
nothing to schedule. Shader sampling then minifies through it (trilinear
|
|
138
|
+
for `"linear"`, per-level nearest for `"nearest"`). The `<texture>` display
|
|
139
|
+
draw samples the full-size level only, so a supersampled target shown
|
|
140
|
+
through `<texture>` should stay at 2x. Rebuilding is one GPU pass per
|
|
141
|
+
upload or render; a per-frame texture pays it per frame.
|
|
142
|
+
|
|
143
|
+
One binding can deviate: a `textures` value may be `{ id, filter?, wrap? }`
|
|
144
|
+
instead of a bare id, sampling that texture with a different filter or
|
|
145
|
+
wrap in this binding only - blur a `"nearest"` atlas linearly, tile a
|
|
146
|
+
clamped target in one consumer. The texture's own state stays what
|
|
147
|
+
`<texture>` paints and what every other binding uses. `mipmap` is not
|
|
148
|
+
overridable: the chain either exists on the id or it does not.
|
|
149
|
+
|
|
150
|
+
## Blending
|
|
151
|
+
|
|
152
|
+
Combining passes is a render-tree job: stack `<texture>` elements and set
|
|
153
|
+
`blendMode` on them. Within one pipeline draw, `blend` on the pipeline
|
|
154
|
+
decides how overlapping geometry combines: `"add"` and `"multiply"` are
|
|
155
|
+
order-independent (glows, shadows); `"alpha"` composites over in draw-list
|
|
156
|
+
order with premultiplied output, normally after the opaques with
|
|
157
|
+
`depthWrite: false`, and nothing sorts for you (`setDrawOrder` does, or a
|
|
158
|
+
scene layer above). Anything else overwrites.
|
|
159
|
+
|
|
160
|
+
## The pixel contract
|
|
161
|
+
|
|
162
|
+
Three facts hold for every texture and target:
|
|
163
|
+
|
|
164
|
+
- **Clip space is y-down.** `gl_Position` y = -1 is the top of the target.
|
|
165
|
+
A vertex stage carrying camera-up geometry negates y or folds the flip into
|
|
166
|
+
its projection. The fragment path absorbs the flip, so `vUV` is 0..1 with
|
|
167
|
+
a top-left origin.
|
|
168
|
+
- **Color is premultiplied alpha.** A target's RGB is already multiplied by
|
|
169
|
+
its A: write `vec4(rgb * a, a)`. `vec4(rgb, a)` composites as opaque.
|
|
170
|
+
`clearColor` is premultiplied too.
|
|
171
|
+
- **Values are non-linear RGBA8** (or `"r8"` for single-channel data), with
|
|
172
|
+
no color-space conversion anywhere. Filtering and blending operate on the
|
|
173
|
+
stored values.
|
|
174
|
+
|
|
175
|
+
## UI as a texture
|
|
176
|
+
|
|
177
|
+
`snapshotTexture(ref)` (from `@solidrt/core`) is the texture id behind a
|
|
178
|
+
`repaintBoundary="snapshot"` view: the subtree's rasterized pixels, usable
|
|
179
|
+
wherever a texture id is - a `<texture>`, a shader or draw target binding,
|
|
180
|
+
a 3d material. The id is allocated on first call and stable for the node's
|
|
181
|
+
lifetime; the runtime re-points it after every re-rasterization, which
|
|
182
|
+
happens only when the subtree changes, so a static panel sampled by an
|
|
183
|
+
animated consumer costs no repaint. Premultiplied, top-left origin, cropped
|
|
184
|
+
to the layout box. Empty until the boundary's first paint. The boundary owns
|
|
185
|
+
it: `destroyTexture` on it throws, and unmounting the boundary releases it
|
|
186
|
+
on the deferred-destroy path (a consumer still sampling it keeps the last
|
|
187
|
+
pixels). A boundary showing its own texture is not a feedback loop: its
|
|
188
|
+
rasterization is the change, so it does not re-invalidate itself.
|
|
189
|
+
|
|
190
|
+
## Readback
|
|
191
|
+
|
|
192
|
+
`captureSnapshot` renders a render-tree node to pixels and `readTexture`
|
|
193
|
+
reads any texture back; both resolve `{ width, height, data }`. This is the
|
|
194
|
+
one-shot bake path (a glyph atlas, a processed image), paid for with a
|
|
195
|
+
readback stall and a paint pass of latency per call. Never per frame; to
|
|
196
|
+
feed live content into a shader, bind a texture that updates in place
|
|
197
|
+
instead - for a UI subtree, `snapshotTexture`.
|
|
198
|
+
|
|
199
|
+
## Limits
|
|
200
|
+
|
|
201
|
+
`limits` holds the device ceilings queried at startup: maximum texture and
|
|
202
|
+
target size, sampler inputs per pass, vertex attributes per pipeline. Creates
|
|
203
|
+
and binds validate against them and throw naming the limit; read them to
|
|
204
|
+
size within the device instead.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Reference
|
|
2
|
+
|
|
3
|
+
Core's API is its types. `@solidrt/core` ships `types.d.ts` and
|
|
4
|
+
`jsx-runtime.d.ts` as sources, with a doc comment on every prop that deviates
|
|
5
|
+
from the CSS or DOM meaning you already expect, so these pages show those
|
|
6
|
+
declarations directly rather than paraphrasing them.
|
|
7
|
+
|
|
8
|
+
They are grouped by subject, not by element. Most props live on several
|
|
9
|
+
elements at once (every element takes pointer handlers, every drawing
|
|
10
|
+
primitive takes paint props), so a page per element would print the same
|
|
11
|
+
interface a dozen times and still not say where a prop comes from.
|
|
12
|
+
|
|
13
|
+
- [Elements](/core/reference/elements/) - `window` and `view`, the structural
|
|
14
|
+
vocabulary
|
|
15
|
+
- [Drawing](/core/reference/drawing/) - `rect`, `oval`, `line`, `path`,
|
|
16
|
+
`texture`, and the paint props they share
|
|
17
|
+
- [Text](/core/reference/text/) - `text` and `span`
|
|
18
|
+
- [Detached elements](/core/reference/detached/) - the `d-*` forms and their
|
|
19
|
+
paint-space geometry
|
|
20
|
+
- [Layout](/core/reference/layout/) - flexbox, the grid subset, and the box
|
|
21
|
+
props
|
|
22
|
+
- [Transforms](/core/reference/transforms/) - post-layout transform, opacity,
|
|
23
|
+
and scroll offsets
|
|
24
|
+
- [Input](/core/reference/input/) - handlers and the event objects they receive
|
|
25
|
+
- [Shaders](/core/reference/shaders/) - the `shader` prop on `window` and
|
|
26
|
+
`view`
|
|
27
|
+
- [GPU](/core/reference/gpu/) - textures, shader targets, pipelines and the
|
|
28
|
+
draw list from `@solidrt/core/gpu`
|
|
29
|
+
- [Types](/core/reference/types/) - the shared aliases
|
|
30
|
+
|
|
31
|
+
## The element vocabulary
|
|
32
|
+
|
|
33
|
+
Every JSX intrinsic and the prop interfaces it composes. `ref` is available on
|
|
34
|
+
all of them and is left out here.
|
|
35
|
+
|
|
36
|
+
{{ intrinsics packages/core/jsx-runtime.d.ts }}
|
|
37
|
+
|
|
38
|
+
Read a row as the sum of its parts: `<rect>` takes paint props, pointer props
|
|
39
|
+
and layout props, while `<d-rect>` swaps the layout props for detached
|
|
40
|
+
geometry. The pages above cover the interfaces in that table.
|
|
41
|
+
|
|
42
|
+
## Clearing a prop
|
|
43
|
+
|
|
44
|
+
Writing `undefined` (or `null`) to a prop resets it to its default - the
|
|
45
|
+
value it had before anything was set, per element kind. So the reactive
|
|
46
|
+
pattern `scale={style()?.scale}` is safe: when the binding clears, the
|
|
47
|
+
element returns to its unset state instead of erroring. On a `span`,
|
|
48
|
+
clearing a prop returns the run to inheriting from its paragraph. Content
|
|
49
|
+
props are the exception: `text` on a span and `d` on a path require a value.
|
|
50
|
+
A wrong-typed value is an error either way.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Input
|
|
2
|
+
|
|
3
|
+
Pointer, wheel, key and text events are props on any element. There is no
|
|
4
|
+
`addEventListener` and no event registry: a handler prop is the subscription.
|
|
5
|
+
|
|
6
|
+
Events travel from the hit leaf up to the root, and `stopPropagation()` ends
|
|
7
|
+
the walk.
|
|
8
|
+
|
|
9
|
+
## Handlers
|
|
10
|
+
|
|
11
|
+
{{ decl packages/core/src/types.d.ts PointerProps }}
|
|
12
|
+
|
|
13
|
+
`pointerEvents="none"` takes an element out of hit testing, and the walk skips
|
|
14
|
+
it as an ancestor too, so the `parentX`/`parentY` of an event stay in the frame
|
|
15
|
+
you would expect.
|
|
16
|
+
|
|
17
|
+
Focus is explicit: `setFocus(node)` moves it, `onFocus`/`onBlur` report it,
|
|
18
|
+
and `focusable` only marks an element as a candidate for `getFocusables()`,
|
|
19
|
+
so a focus-navigation scheme (a component set's, or your own) can enumerate
|
|
20
|
+
targets without the runtime imposing one.
|
|
21
|
+
|
|
22
|
+
## Pointer events
|
|
23
|
+
|
|
24
|
+
Coordinates are logical points, so a handler reads the same numbers on a
|
|
25
|
+
high-density phone screen as on a desktop monitor. Three frames are reported,
|
|
26
|
+
which is what makes drag idioms short:
|
|
27
|
+
|
|
28
|
+
{{ decl packages/core/src/types.d.ts PointerEvent }}
|
|
29
|
+
|
|
30
|
+
The drag idiom is `x = parentX - grab offset`, taking the grab offset from
|
|
31
|
+
`localX`/`localY` at pointer down.
|
|
32
|
+
|
|
33
|
+
{{ decl packages/core/src/types.d.ts WheelEvent }}
|
|
34
|
+
|
|
35
|
+
## Key events
|
|
36
|
+
|
|
37
|
+
Key events use the W3C UI Events vocabulary: `key` is the logical,
|
|
38
|
+
layout-dependent value (`"a"`, `"!"`, `"Enter"`, `"ArrowLeft"`), and `code` is
|
|
39
|
+
the physical key position (`"KeyA"`, `"Digit1"`).
|
|
40
|
+
|
|
41
|
+
{{ decl packages/core/src/types.d.ts KeyEvent }}
|
|
42
|
+
|
|
43
|
+
Routing differs from pointer events in where the walk starts: keydown and
|
|
44
|
+
keyup dispatch along the focused node's ancestor chain and always end at the
|
|
45
|
+
window root, and with nothing focused they go to the window root alone.
|
|
46
|
+
`<window onKeyDown>` is therefore the app-global shortcut point.
|
|
47
|
+
|
|
48
|
+
## Text entry
|
|
49
|
+
|
|
50
|
+
Printable characters do not arrive as key events. Text entry goes to the
|
|
51
|
+
focused node's `onTextInput`:
|
|
52
|
+
|
|
53
|
+
{{ decl packages/core/src/types.d.ts TextEvent }}
|
|
54
|
+
|
|
55
|
+
Focusing a field never raises the on-screen keyboard by itself. A tap on the
|
|
56
|
+
field does, or an explicit `startTextInput()`, and never while a physical
|
|
57
|
+
keyboard is attached. `textInputHints` on the node is read when a session
|
|
58
|
+
starts.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Layout
|
|
2
|
+
|
|
3
|
+
Layout is flexbox plus a line-based subset of CSS grid, over the whole element
|
|
4
|
+
tree. Prop names match CSS, so `flexDirection`, `alignItems`,
|
|
5
|
+
`justifyContent`, `gap`, `padding` and `width` mean what you expect.
|
|
6
|
+
|
|
7
|
+
These props exist on `window`, `view`, and the layout form of every drawing
|
|
8
|
+
and text primitive. They do not exist on the
|
|
9
|
+
[detached](/core/reference/detached/) forms, which own their geometry instead.
|
|
10
|
+
|
|
11
|
+
## Units
|
|
12
|
+
|
|
13
|
+
A bare number is pixels. A percentage is `pct(50)`, a branded value rather
|
|
14
|
+
than a parsed string, though the `"50%"` string form is accepted so pasted CSS
|
|
15
|
+
keeps working.
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
<view flexDirection="row" gap={8} padding={16}>
|
|
19
|
+
<view width={pct(50)} />
|
|
20
|
+
</view>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
{{ decl packages/core/src/types.d.ts Dimension }}
|
|
24
|
+
|
|
25
|
+
{{ decl packages/core/src/types.d.ts LengthPercentage }}
|
|
26
|
+
|
|
27
|
+
## The box
|
|
28
|
+
|
|
29
|
+
{{ decl packages/core/src/types.d.ts LayoutProps }}
|
|
30
|
+
|
|
31
|
+
Two divergences from CSS are worth reading twice. `position` has `relative`
|
|
32
|
+
and `absolute` only, and an absolute element does not itself become a
|
|
33
|
+
containing block: it resolves against the nearest ancestor with
|
|
34
|
+
`position="relative"`. And `float` / `clear` are not page layout; they apply
|
|
35
|
+
to an element child of a `<text>`, where it becomes an inline atom the lines
|
|
36
|
+
wrap around.
|
|
37
|
+
|
|
38
|
+
## Flexbox
|
|
39
|
+
|
|
40
|
+
{{ decl packages/core/src/types.d.ts FlexboxProps }}
|
|
41
|
+
|
|
42
|
+
## Grid
|
|
43
|
+
|
|
44
|
+
{{ decl packages/core/src/types.d.ts GridProps }}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Shaders
|
|
2
|
+
|
|
3
|
+
Two elements take a `shader` prop: `window` runs a program over the finished
|
|
4
|
+
frame, and `view` runs one over its own rasterized subtree. Both are
|
|
5
|
+
declarative - the pass exists while the prop is declared and the resources go
|
|
6
|
+
away when it is removed.
|
|
7
|
+
|
|
8
|
+
Everything about the program itself (compiling, linking, lifetime) belongs to
|
|
9
|
+
the raw shading layer: `compileShader` and `linkProgram` from
|
|
10
|
+
`@solidrt/core/gpu`.
|
|
11
|
+
|
|
12
|
+
## The shared contract
|
|
13
|
+
|
|
14
|
+
Both passes bind their input as `uniform sampler2D uSource` with a top-left
|
|
15
|
+
origin, like every sampled texture in the engine, and fill `iResolution` by
|
|
16
|
+
name with the pass size in physical pixels. Both fill `params` uniforms by
|
|
17
|
+
name, paced to the next real repaint: a number drives a scalar, and a flat
|
|
18
|
+
number array drives the declared GLSL type (2, 3 or 4 for `vec2`, `vec3`,
|
|
19
|
+
`vec4`, and 16 column-major for `mat4`).
|
|
20
|
+
|
|
21
|
+
## Window
|
|
22
|
+
|
|
23
|
+
{{ decl packages/core/src/types.d.ts WindowShaderProps }}
|
|
24
|
+
|
|
25
|
+
The window pass draws attributeless triangles with `vertexCount` vertices
|
|
26
|
+
fetched via `gl_VertexID`, defaulting to a single covering triangle.
|
|
27
|
+
|
|
28
|
+
## View
|
|
29
|
+
|
|
30
|
+
A boundary shader requires a snapshot boundary (`repaintBoundary="snapshot"`
|
|
31
|
+
or `"snapshot-no-aa"`). The cost is snapshot semantics, and it is kept
|
|
32
|
+
explicit: declared without one, the shader is ignored with a warning.
|
|
33
|
+
|
|
34
|
+
{{ decl packages/core/src/types.d.ts ViewShaderProps }}
|
|
35
|
+
|
|
36
|
+
The pass is split from content invalidation, so animating `params` over a
|
|
37
|
+
static subtree re-runs only the pass against the cached snapshot and never
|
|
38
|
+
re-rasterizes it.
|
|
39
|
+
|
|
40
|
+
The effect samples the subtree's own pixels and nothing else. Grading,
|
|
41
|
+
warping and dissolving a panel work; anything that needs what is *behind* the
|
|
42
|
+
panel does not. Hit testing stays on layout geometry, so a distortion moves
|
|
43
|
+
pixels, not hit targets.
|
|
44
|
+
|
|
45
|
+
`outset` grows the rasterized canvas by a transparent margin on every side,
|
|
46
|
+
for effects that write past the edge such as a glow or a drop shadow.
|