@swf2es/render 0.0.0-stage → 0.1.0
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/LICENSE +202 -0
- package/README.md +17 -2
- package/dist/commands.d.ts +67 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +93 -0
- package/dist/commands.js.map +1 -0
- package/dist/filter-plan.d.ts +110 -0
- package/dist/filter-plan.d.ts.map +1 -0
- package/dist/filter-plan.js +480 -0
- package/dist/filter-plan.js.map +1 -0
- package/dist/filter-shaders.d.ts +63 -0
- package/dist/filter-shaders.d.ts.map +1 -0
- package/dist/filter-shaders.js +350 -0
- package/dist/filter-shaders.js.map +1 -0
- package/dist/filters.d.ts +107 -0
- package/dist/filters.d.ts.map +1 -0
- package/dist/filters.js +187 -0
- package/dist/filters.js.map +1 -0
- package/dist/geometry.d.ts +83 -0
- package/dist/geometry.d.ts.map +1 -0
- package/dist/geometry.js +93 -0
- package/dist/geometry.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/keeps.d.ts +17 -0
- package/dist/keeps.d.ts.map +1 -0
- package/dist/keeps.js +17 -0
- package/dist/keeps.js.map +1 -0
- package/dist/kernel-shaders.d.ts +74 -0
- package/dist/kernel-shaders.d.ts.map +1 -0
- package/dist/kernel-shaders.js +323 -0
- package/dist/kernel-shaders.js.map +1 -0
- package/dist/measure.d.ts +56 -0
- package/dist/measure.d.ts.map +1 -0
- package/dist/measure.js +287 -0
- package/dist/measure.js.map +1 -0
- package/dist/plan.d.ts +14 -0
- package/dist/plan.d.ts.map +1 -0
- package/dist/plan.js +247 -0
- package/dist/plan.js.map +1 -0
- package/dist/registry.d.ts +49 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +87 -0
- package/dist/registry.js.map +1 -0
- package/dist/rows.d.ts +39 -0
- package/dist/rows.d.ts.map +1 -0
- package/dist/rows.js +60 -0
- package/dist/rows.js.map +1 -0
- package/dist/scene.d.ts +179 -0
- package/dist/scene.d.ts.map +1 -0
- package/dist/scene.js +265 -0
- package/dist/scene.js.map +1 -0
- package/dist/shelves.d.ts +23 -0
- package/dist/shelves.d.ts.map +1 -0
- package/dist/shelves.js +63 -0
- package/dist/shelves.js.map +1 -0
- package/dist/textures.d.ts +72 -0
- package/dist/textures.d.ts.map +1 -0
- package/dist/textures.js +160 -0
- package/dist/textures.js.map +1 -0
- package/dist/webgl/atlas.d.ts +63 -0
- package/dist/webgl/atlas.d.ts.map +1 -0
- package/dist/webgl/atlas.js +328 -0
- package/dist/webgl/atlas.js.map +1 -0
- package/dist/webgl/backend.d.ts +141 -0
- package/dist/webgl/backend.d.ts.map +1 -0
- package/dist/webgl/backend.js +659 -0
- package/dist/webgl/backend.js.map +1 -0
- package/dist/webgl/composite.d.ts +101 -0
- package/dist/webgl/composite.d.ts.map +1 -0
- package/dist/webgl/composite.js +293 -0
- package/dist/webgl/composite.js.map +1 -0
- package/dist/webgl/draws.d.ts +213 -0
- package/dist/webgl/draws.d.ts.map +1 -0
- package/dist/webgl/draws.js +714 -0
- package/dist/webgl/draws.js.map +1 -0
- package/dist/webgl/filters.d.ts +107 -0
- package/dist/webgl/filters.d.ts.map +1 -0
- package/dist/webgl/filters.js +351 -0
- package/dist/webgl/filters.js.map +1 -0
- package/dist/webgl/gpu-textures.d.ts +44 -0
- package/dist/webgl/gpu-textures.d.ts.map +1 -0
- package/dist/webgl/gpu-textures.js +178 -0
- package/dist/webgl/gpu-textures.js.map +1 -0
- package/dist/webgl/holds.d.ts +97 -0
- package/dist/webgl/holds.d.ts.map +1 -0
- package/dist/webgl/holds.js +453 -0
- package/dist/webgl/holds.js.map +1 -0
- package/dist/webgl/index.d.ts +2 -0
- package/dist/webgl/index.d.ts.map +1 -0
- package/dist/webgl/index.js +4 -0
- package/dist/webgl/index.js.map +1 -0
- package/dist/webgl/kernels.d.ts +64 -0
- package/dist/webgl/kernels.d.ts.map +1 -0
- package/dist/webgl/kernels.js +160 -0
- package/dist/webgl/kernels.js.map +1 -0
- package/dist/webgl/pool.d.ts +71 -0
- package/dist/webgl/pool.d.ts.map +1 -0
- package/dist/webgl/pool.js +192 -0
- package/dist/webgl/pool.js.map +1 -0
- package/dist/webgl/programs.d.ts +130 -0
- package/dist/webgl/programs.d.ts.map +1 -0
- package/dist/webgl/programs.js +582 -0
- package/dist/webgl/programs.js.map +1 -0
- package/dist/webgl/stage.d.ts +67 -0
- package/dist/webgl/stage.d.ts.map +1 -0
- package/dist/webgl/stage.js +173 -0
- package/dist/webgl/stage.js.map +1 -0
- package/dist/webgl/targets.d.ts +172 -0
- package/dist/webgl/targets.d.ts.map +1 -0
- package/dist/webgl/targets.js +323 -0
- package/dist/webgl/targets.js.map +1 -0
- package/package.json +35 -3
- package/src/commands.ts +98 -0
- package/src/filter-plan.ts +755 -0
- package/src/filter-shaders.ts +360 -0
- package/src/filters.ts +220 -0
- package/src/geometry.ts +143 -0
- package/src/index.ts +87 -0
- package/src/keeps.ts +21 -0
- package/src/kernel-shaders.ts +375 -0
- package/src/measure.ts +400 -0
- package/src/plan.ts +331 -0
- package/src/registry.ts +105 -0
- package/src/rows.ts +93 -0
- package/src/scene.ts +297 -0
- package/src/shelves.ts +70 -0
- package/src/textures.ts +198 -0
- package/src/webgl/atlas.ts +370 -0
- package/src/webgl/backend.ts +973 -0
- package/src/webgl/composite.ts +372 -0
- package/src/webgl/draws.ts +902 -0
- package/src/webgl/filters.ts +502 -0
- package/src/webgl/gpu-textures.ts +253 -0
- package/src/webgl/holds.ts +624 -0
- package/src/webgl/index.ts +3 -0
- package/src/webgl/kernels.ts +205 -0
- package/src/webgl/pool.ts +245 -0
- package/src/webgl/programs.ts +620 -0
- package/src/webgl/stage.ts +214 -0
- package/src/webgl/targets.ts +464 -0
package/src/geometry.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { type Handle, Registry } from "./registry.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* How many frames a geometry may go undrawn before it is let go of: a
|
|
5
|
+
* pool's object or a loop's rarer frame comes back within a few seconds.
|
|
6
|
+
*/
|
|
7
|
+
export const IDLE_FRAMES = 300;
|
|
8
|
+
|
|
9
|
+
/** Floats a vertex of advanced text's glyph quads (GeometryData.glyphs). */
|
|
10
|
+
export const GLYPH_FLOATS = 18;
|
|
11
|
+
|
|
12
|
+
/** A geometry as its owner holds it: its id, and the generation the id had, which a reused id moves on. */
|
|
13
|
+
export type GeometryHandle = Handle;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Triangles to draw under a row's transform: positions on the stage in
|
|
17
|
+
* pixels, two floats a vertex; each vertex' straight colour, its bytes red,
|
|
18
|
+
* green, blue and alpha from the lowest; where it samples a texture, where
|
|
19
|
+
* in texels, two floats a vertex, which a draw divides by the texture's
|
|
20
|
+
* size; and indices into the vertices, three a triangle. The backend copies
|
|
21
|
+
* them into its atlas once.
|
|
22
|
+
*/
|
|
23
|
+
export interface GeometryData {
|
|
24
|
+
positions: Float32Array;
|
|
25
|
+
/** None for a mesh, whose depths take their place. */
|
|
26
|
+
colors: Uint32Array;
|
|
27
|
+
/** A mesh's (MESH) are its u and v from 0 to 1, which its program reads as they are. */
|
|
28
|
+
texels?: Float32Array;
|
|
29
|
+
/** A mesh's depth at each vertex, its t, in place of its colour. */
|
|
30
|
+
depths?: Float32Array;
|
|
31
|
+
/**
|
|
32
|
+
* Advanced text's glyph quads: GLYPH_FLOATS floats a vertex, its position
|
|
33
|
+
* first, as its positions have it, then its glyph's pen and tables,
|
|
34
|
+
* outline bounds and corner, segments and size, and colour; and the
|
|
35
|
+
* text's mode and cutoff tables, which its draw takes as uniforms. Drawn
|
|
36
|
+
* by the glyph program (webgl/programs.ts, glyphsVertex), each pixel of a
|
|
37
|
+
* quad covered by its distance to the glyph's outline, in the outlines'
|
|
38
|
+
* segments (WebGLBackend.uploadOutlines), as adl covers it.
|
|
39
|
+
*/
|
|
40
|
+
glyphs?: { vertices: Float32Array; mode: Float32Array; table: Float32Array };
|
|
41
|
+
indices: Uint32Array;
|
|
42
|
+
/**
|
|
43
|
+
* No part of it over another: a bitmap's quad, or one fill's triangles.
|
|
44
|
+
* Alone in a blend mode's layer, such a geometry is drawn straight onto
|
|
45
|
+
* what is below it, as blending it pixel by pixel blends each pixel once.
|
|
46
|
+
*/
|
|
47
|
+
flat?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Every geometry a scene list may name, by id. What goes undrawn for
|
|
52
|
+
* IDLE_FRAMES is let go of; its owner finds it gone by its handle and adds
|
|
53
|
+
* it again. The backend takes what was added and removed since it last
|
|
54
|
+
* looked (taken), and keeps its own copies.
|
|
55
|
+
*/
|
|
56
|
+
export class Geometries extends Registry {
|
|
57
|
+
/** Changes only when a handle can have become stale, including expiry and context loss. */
|
|
58
|
+
removalVersion = 0;
|
|
59
|
+
private readonly data: (GeometryData | null)[] = [];
|
|
60
|
+
/** Each geometry's box: its positions' least x and y, then their greatest. */
|
|
61
|
+
private boxes = new Float32Array(1024);
|
|
62
|
+
/** Added since the backend last took them. */
|
|
63
|
+
added: number[] = [];
|
|
64
|
+
/** Changed in place since the backend last took them (update). */
|
|
65
|
+
updated: number[] = [];
|
|
66
|
+
|
|
67
|
+
add(data: GeometryData): GeometryHandle {
|
|
68
|
+
const handle = this.hold(IDLE_FRAMES);
|
|
69
|
+
const id = handle.id;
|
|
70
|
+
this.data[id] = data;
|
|
71
|
+
this.added.push(id);
|
|
72
|
+
this.measure(id);
|
|
73
|
+
return handle;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Geometry `id`'s arrays changed in place, their lengths as they were: its
|
|
78
|
+
* box measured again, and its vertices copied again where they lie.
|
|
79
|
+
*/
|
|
80
|
+
update(id: number): void {
|
|
81
|
+
if (this.data[id]) {
|
|
82
|
+
this.measure(id);
|
|
83
|
+
this.updated.push(id);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private measure(id: number): void {
|
|
88
|
+
const data = this.data[id] as GeometryData;
|
|
89
|
+
if (id * 4 >= this.boxes.length) {
|
|
90
|
+
const grown = new Float32Array(Math.max(this.boxes.length * 2, id * 4 + 4));
|
|
91
|
+
grown.set(this.boxes);
|
|
92
|
+
this.boxes = grown;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const p = data.positions;
|
|
96
|
+
let x0 = Number.POSITIVE_INFINITY;
|
|
97
|
+
let y0 = Number.POSITIVE_INFINITY;
|
|
98
|
+
let x1 = Number.NEGATIVE_INFINITY;
|
|
99
|
+
let y1 = Number.NEGATIVE_INFINITY;
|
|
100
|
+
for (let i = 0; i < p.length; i += 2) {
|
|
101
|
+
x0 = Math.min(x0, p[i]);
|
|
102
|
+
y0 = Math.min(y0, p[i + 1]);
|
|
103
|
+
x1 = Math.max(x1, p[i]);
|
|
104
|
+
y1 = Math.max(y1, p[i + 1]);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const box = this.boxes;
|
|
108
|
+
box[id * 4] = x0;
|
|
109
|
+
box[id * 4 + 1] = y0;
|
|
110
|
+
box[id * 4 + 2] = x1;
|
|
111
|
+
box[id * 4 + 3] = y1;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The boxes of every geometry, four floats from `id * 4`: least x and y,
|
|
116
|
+
* greatest x and y; a geometry of no vertices has its least past its
|
|
117
|
+
* greatest.
|
|
118
|
+
*/
|
|
119
|
+
get box(): Float32Array {
|
|
120
|
+
return this.boxes;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
get(id: number): GeometryData | null {
|
|
124
|
+
return this.data[id] ?? null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** The ids of every geometry held, in order. */
|
|
128
|
+
live(): number[] {
|
|
129
|
+
const ids: number[] = [];
|
|
130
|
+
for (let id = 0; id < this.data.length; id++) {
|
|
131
|
+
if (this.data[id]) {
|
|
132
|
+
ids.push(id);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return ids;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
protected override dropped(id: number): void {
|
|
140
|
+
this.removalVersion++;
|
|
141
|
+
this.data[id] = null;
|
|
142
|
+
}
|
|
143
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// The renderer's core (docs/renderer.md): what a frame draws, as typed
|
|
2
|
+
// arrays, and the planner that turns it into commands. No GPU, no DOM.
|
|
3
|
+
export {
|
|
4
|
+
CLEAR,
|
|
5
|
+
COMPOSITE,
|
|
6
|
+
Commands,
|
|
7
|
+
FILTER,
|
|
8
|
+
HELD,
|
|
9
|
+
HOLD_POP,
|
|
10
|
+
HOLD_PUSH,
|
|
11
|
+
LAYER_POP,
|
|
12
|
+
LAYER_PUSH,
|
|
13
|
+
RUN,
|
|
14
|
+
STENCIL_POP,
|
|
15
|
+
STENCIL_PUSH,
|
|
16
|
+
} from "./commands.js";
|
|
17
|
+
export * as filterShaders from "./filter-shaders.js";
|
|
18
|
+
export {
|
|
19
|
+
BEVEL,
|
|
20
|
+
BLEND,
|
|
21
|
+
BLUR,
|
|
22
|
+
blurShrink,
|
|
23
|
+
COLOR_MATRIX,
|
|
24
|
+
CONVOLUTION,
|
|
25
|
+
DISPLACEMENT,
|
|
26
|
+
FILL_SHADER,
|
|
27
|
+
Filters,
|
|
28
|
+
GLOW,
|
|
29
|
+
GRADIENT,
|
|
30
|
+
SHADER,
|
|
31
|
+
} from "./filters.js";
|
|
32
|
+
export {
|
|
33
|
+
Geometries,
|
|
34
|
+
type GeometryData,
|
|
35
|
+
type GeometryHandle,
|
|
36
|
+
GLYPH_FLOATS,
|
|
37
|
+
IDLE_FRAMES,
|
|
38
|
+
} from "./geometry.js";
|
|
39
|
+
export { KEEP_IDLE_FRAMES, type KeepHandle, Keeps } from "./keeps.js";
|
|
40
|
+
export {
|
|
41
|
+
drawnArea,
|
|
42
|
+
filteredRect,
|
|
43
|
+
type MeasureTarget,
|
|
44
|
+
measureLayers,
|
|
45
|
+
wholeBitmap,
|
|
46
|
+
} from "./measure.js";
|
|
47
|
+
export { plan } from "./plan.js";
|
|
48
|
+
export { type ColorRow, ROW_FLOATS, ROWS_A_LINE, Rows } from "./rows.js";
|
|
49
|
+
export {
|
|
50
|
+
ADD,
|
|
51
|
+
ALPHA,
|
|
52
|
+
ALPHA_MASK,
|
|
53
|
+
BLEND_NAMES,
|
|
54
|
+
CLAMP,
|
|
55
|
+
DARKEN,
|
|
56
|
+
DIFFERENCE,
|
|
57
|
+
DRAW,
|
|
58
|
+
ERASE,
|
|
59
|
+
HARDLIGHT,
|
|
60
|
+
INVERT,
|
|
61
|
+
KEPT,
|
|
62
|
+
KERNEL,
|
|
63
|
+
LAYER,
|
|
64
|
+
LAYER_BEGIN,
|
|
65
|
+
LAYER_END,
|
|
66
|
+
LIGHTEN,
|
|
67
|
+
MASK_BEGIN,
|
|
68
|
+
MASK_POP,
|
|
69
|
+
MASK_USE,
|
|
70
|
+
MESH,
|
|
71
|
+
MIRROR,
|
|
72
|
+
MULTIPLY,
|
|
73
|
+
NO_WHOLE,
|
|
74
|
+
NORMAL,
|
|
75
|
+
OVERLAY,
|
|
76
|
+
PLANE,
|
|
77
|
+
REPEAT,
|
|
78
|
+
SCREEN,
|
|
79
|
+
SceneList,
|
|
80
|
+
SHADED,
|
|
81
|
+
SMOOTH,
|
|
82
|
+
SNAP,
|
|
83
|
+
SUBTRACT,
|
|
84
|
+
WHOLE_ALIASED,
|
|
85
|
+
WHOLE_SMOOTH,
|
|
86
|
+
} from "./scene.js";
|
|
87
|
+
export { KEEP_FRAMES, type TextureData, type TextureHandle, Textures } from "./textures.js";
|
package/src/keeps.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type Handle, Registry } from "./registry.js";
|
|
2
|
+
|
|
3
|
+
/** A kept output as its owner holds it: its id, and the generation the id had. */
|
|
4
|
+
export type KeepHandle = Handle;
|
|
5
|
+
|
|
6
|
+
/** Frames a kept output may go undrawn before it is let go of, unless its owner says otherwise. */
|
|
7
|
+
export const KEEP_IDLE_FRAMES = 60;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The layers kept from one frame to the next, as Flash keeps a filtered
|
|
11
|
+
* object's bitmap while nothing in it changed: an owner adds one, names it
|
|
12
|
+
* on the layer whose output it keeps, and composites it again while it
|
|
13
|
+
* stands (SceneList.keptLayer). The backend holds their targets, and lets
|
|
14
|
+
* go of those removed; what goes undrawn for KEEP_IDLE_FRAMES is removed.
|
|
15
|
+
*/
|
|
16
|
+
export class Keeps extends Registry {
|
|
17
|
+
/** A kept output's id, let go of once undrawn for `idle` frames. */
|
|
18
|
+
add(idle = KEEP_IDLE_FRAMES): KeepHandle {
|
|
19
|
+
return this.hold(idle);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
// Pixel Bender kernels where Flash draws with them, as fragment shaders
|
|
2
|
+
// around a kernel's GLSL (the player's scene/pixel-bender-glsl.ts, which
|
|
3
|
+
// defines `vec4 pbKernel(vec2 outCoord)` and calls `pbNearest<t>` and
|
|
4
|
+
// `pbLinear<t>` for each texture `t` it samples). What is here is how
|
|
5
|
+
// each use reads its images and writes what the kernel gives, as adl
|
|
6
|
+
// does (docs/architecture/bitmaps.md, "Pixel Bender"): an image read as
|
|
7
|
+
// its premultiplied bytes times the float nearest 1/255, linearly in
|
|
8
|
+
// 256ths with each step's byte cut down; floats as they are, linearly in
|
|
9
|
+
// the same 256ths; as the CPU interpreter reads them. GLSL ES 3, each a
|
|
10
|
+
// whole fragment shader.
|
|
11
|
+
|
|
12
|
+
/** A byte's float as adl samples one: times the float nearest 1/255, not divided. */
|
|
13
|
+
const BYTE = "0.003921568859368563";
|
|
14
|
+
|
|
15
|
+
/** Each fragment shader's first lines, so that one is the whole source a program cache keys by. */
|
|
16
|
+
const HEADER = "#version 300 es\nprecision highp float;\n";
|
|
17
|
+
|
|
18
|
+
/** The most images a kernel's program binds besides its layer's: one with more is not drawn. */
|
|
19
|
+
export const KERNEL_IMAGES = 8;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* An image a kernel samples: its texture number; whether it is the
|
|
23
|
+
* filtered layer, else a texture of its own; and whether its texels are
|
|
24
|
+
* floats (RGBA32F), else bytes. For a blend, a layer's image is what is
|
|
25
|
+
* below where `back`, else the layer blended.
|
|
26
|
+
*/
|
|
27
|
+
export interface KernelImage {
|
|
28
|
+
texture: number;
|
|
29
|
+
layer: boolean;
|
|
30
|
+
float: boolean;
|
|
31
|
+
back?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Whether `images` bind no more textures of their own than a program takes (KERNEL_IMAGES). */
|
|
35
|
+
export function kernelImagesFit(images: readonly KernelImage[]): boolean {
|
|
36
|
+
let own = 0;
|
|
37
|
+
for (let k = 0; k < images.length; k++) {
|
|
38
|
+
if (!images[k].layer) {
|
|
39
|
+
own++;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return own <= KERNEL_IMAGES;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Precision, as ints default to mediump in a fragment shader; a texel
|
|
48
|
+
* coordinate as a texel's index, NaN as 0 and far past the edges kept in
|
|
49
|
+
* int's range; a texel's bytes from 0 to 255, read exactly from the 8-bit
|
|
50
|
+
* texture; and a kernel's output as adl writes it into a bitmap: clamped,
|
|
51
|
+
* NaN as 0, rounded to a byte a half to the even, each colour at most its
|
|
52
|
+
* alpha.
|
|
53
|
+
*/
|
|
54
|
+
const PRELUDE = `precision highp int;
|
|
55
|
+
precision highp sampler2D;
|
|
56
|
+
ivec2 pbFloor(vec2 x) {
|
|
57
|
+
x = mix(x, vec2(0.0), isnan(x));
|
|
58
|
+
return ivec2(clamp(floor(x), -1.0e9, 1.0e9));
|
|
59
|
+
}
|
|
60
|
+
vec4 pbBytes(vec4 v) {
|
|
61
|
+
return floor(v * 255.0 + 0.5);
|
|
62
|
+
}
|
|
63
|
+
vec4 pbOutput(vec4 c) {
|
|
64
|
+
c = mix(vec4(0.0), min(c, 1.0), greaterThan(c, vec4(0.0)));
|
|
65
|
+
c = roundEven(c * 255.0) / 255.0;
|
|
66
|
+
return vec4(min(c.rgb, c.a), c.a);
|
|
67
|
+
}
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Image `t`'s sampling from `pbTexel<t>`, its texel at an index (bytes from
|
|
72
|
+
* 0 to 255, or floats), at the texel coordinates `at` gives for a kernel's.
|
|
73
|
+
*/
|
|
74
|
+
function sampling(t: number, at: string, float: boolean): string {
|
|
75
|
+
if (float) {
|
|
76
|
+
// The interpreter's floats: their texels as they are, linearly in 256ths, nothing cut.
|
|
77
|
+
return `vec4 pbNearest${t}(vec2 p) {
|
|
78
|
+
return pbTexel${t}(pbFloor(${at}));
|
|
79
|
+
}
|
|
80
|
+
vec4 pbLinear${t}(vec2 p) {
|
|
81
|
+
vec2 f = ${at} - 0.5;
|
|
82
|
+
vec2 below = floor(f);
|
|
83
|
+
vec2 w = floor((f - below) * 256.0 + 0.5) / 256.0;
|
|
84
|
+
w = mix(w, vec2(0.0), isnan(w));
|
|
85
|
+
ivec2 a = pbFloor(below);
|
|
86
|
+
vec4 row0 = pbTexel${t}(a) * (1.0 - w.x) + pbTexel${t}(a + ivec2(1, 0)) * w.x;
|
|
87
|
+
vec4 row1 = pbTexel${t}(a + ivec2(0, 1)) * (1.0 - w.x) + pbTexel${t}(a + 1) * w.x;
|
|
88
|
+
return row0 * (1.0 - w.y) + row1 * w.y;
|
|
89
|
+
}
|
|
90
|
+
`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return `vec4 pbNearest${t}(vec2 p) {
|
|
94
|
+
return pbTexel${t}(pbFloor(${at})) * ${BYTE};
|
|
95
|
+
}
|
|
96
|
+
vec4 pbLinear${t}(vec2 p) {
|
|
97
|
+
vec2 f = ${at} - 0.5;
|
|
98
|
+
vec2 below = floor(f);
|
|
99
|
+
vec2 w = floor((f - below) * 256.0 + 0.5);
|
|
100
|
+
w = mix(w, vec2(0.0), isnan(w));
|
|
101
|
+
ivec2 a = pbFloor(below);
|
|
102
|
+
vec4 row0 = floor((pbTexel${t}(a) * (256.0 - w.x) + pbTexel${t}(a + ivec2(1, 0)) * w.x) / 256.0);
|
|
103
|
+
vec4 row1 = floor((pbTexel${t}(a + ivec2(0, 1)) * (256.0 - w.x) + pbTexel${t}(a + 1) * w.x) / 256.0);
|
|
104
|
+
return floor((row0 * (256.0 - w.y) + row1 * w.y) / 256.0) * ${BYTE};
|
|
105
|
+
}
|
|
106
|
+
`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Image `t`, a texture of its own (`pbImage<t>`), clamped to its edges as
|
|
111
|
+
* adl clamps a kernel's input: its sampler, its texels and its sampling, in
|
|
112
|
+
* its own pixels.
|
|
113
|
+
*/
|
|
114
|
+
function ownImage(t: number, float: boolean): string {
|
|
115
|
+
const texel = `texelFetch(pbImage${t}, clamp(at, ivec2(0), size - 1), 0)`;
|
|
116
|
+
return `uniform sampler2D pbImage${t};
|
|
117
|
+
vec4 pbTexel${t}(ivec2 at) {
|
|
118
|
+
ivec2 size = textureSize(pbImage${t}, 0);
|
|
119
|
+
return ${float ? texel : `pbBytes(${texel})`};
|
|
120
|
+
}
|
|
121
|
+
${sampling(t, "p", float)}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The kernel's GLSL with what every use puts round it: PRELUDE, then each
|
|
126
|
+
* image, by `layerTexel` for the layer's, its texel at an index, and
|
|
127
|
+
* ownImage for the others; `layerAt` takes a kernel's coordinate to the
|
|
128
|
+
* layer's texels.
|
|
129
|
+
*/
|
|
130
|
+
export function withImages(
|
|
131
|
+
kernel: string,
|
|
132
|
+
images: readonly KernelImage[],
|
|
133
|
+
layerTexel: (t: number, back: boolean) => string,
|
|
134
|
+
layerAt: string,
|
|
135
|
+
): string {
|
|
136
|
+
const parts = [PRELUDE];
|
|
137
|
+
for (const { texture: t, layer, float, back } of images) {
|
|
138
|
+
parts.push(
|
|
139
|
+
layer
|
|
140
|
+
? `${layerTexel(t, back ?? false)}\n${sampling(t, layerAt, false)}`
|
|
141
|
+
: ownImage(t, float),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
parts.push(kernel);
|
|
146
|
+
return parts.join("\n");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** The most kernels whose shaders a use keeps made (madeOnce), emptied when more come. */
|
|
150
|
+
const MADE = 64;
|
|
151
|
+
|
|
152
|
+
/** A kernel's shader for its images, made once: a frame asks for it again on each of its uses. */
|
|
153
|
+
export function madeOnce(
|
|
154
|
+
made: Map<string, Map<string, string>>,
|
|
155
|
+
kernel: string,
|
|
156
|
+
images: readonly KernelImage[],
|
|
157
|
+
make: () => string,
|
|
158
|
+
): string {
|
|
159
|
+
let key = "";
|
|
160
|
+
for (let k = 0; k < images.length; k++) {
|
|
161
|
+
const i = images[k];
|
|
162
|
+
key += `${i.texture}${i.layer ? (i.back ? "B" : "L") : ""}${i.float ? "F" : ""},`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let byImages = made.get(kernel);
|
|
166
|
+
if (!byImages) {
|
|
167
|
+
if (made.size >= MADE) {
|
|
168
|
+
made.clear();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
byImages = new Map();
|
|
172
|
+
made.set(kernel, byImages);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let source = byImages.get(key);
|
|
176
|
+
if (source === undefined) {
|
|
177
|
+
source = make();
|
|
178
|
+
byImages.set(key, source);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return source;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const madeFilters = new Map<string, Map<string, string>>();
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* A ShaderFilter's pass (filter-plan.ts, KERNEL_PASS), run as the
|
|
188
|
+
* filters' other passes are (webgl/filters.ts): the kernel's output over
|
|
189
|
+
* the input's frame, `outCoord` its pixel's centre in stage pixels from
|
|
190
|
+
* the corner of the bitmap the filter reads, `uKernelAt.xy` texels in from
|
|
191
|
+
* the frame's, which starts at `uOutputAt` in the target drawn into, at
|
|
192
|
+
* `uKernelAt.z` texels a stage pixel. The layer's image is the input
|
|
193
|
+
* (uTexture, within uTextureRect), transparent past the frame,
|
|
194
|
+
* `uKernelFrame` texels, as adl reads a filter's source; the others clamp
|
|
195
|
+
* to their edges. The output is as adl writes it (pbOutput), and
|
|
196
|
+
* transparent outside `uKernelRegion`, that bitmap grown by the filter's
|
|
197
|
+
* extensions, in stage pixels.
|
|
198
|
+
*/
|
|
199
|
+
export function filterKernel(kernel: string, images: readonly KernelImage[]): string {
|
|
200
|
+
return madeOnce(madeFilters, kernel, images, () => filterSource(kernel, images));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function filterSource(kernel: string, images: readonly KernelImage[]): string {
|
|
204
|
+
const layer = (t: number) => `vec4 pbTexel${t}(ivec2 at) {
|
|
205
|
+
if (any(lessThan(at, ivec2(0))) || any(greaterThanEqual(at, ivec2(uKernelFrame)))) {
|
|
206
|
+
return vec4(0.0);
|
|
207
|
+
}
|
|
208
|
+
return pbBytes(texelFetch(uTexture, ivec2(uTextureRect.xy) + at, 0));
|
|
209
|
+
}`;
|
|
210
|
+
return `${HEADER}in vec2 vTextureCoord;
|
|
211
|
+
out vec4 finalColor;
|
|
212
|
+
uniform sampler2D uTexture;
|
|
213
|
+
uniform highp vec4 uInputPixel;
|
|
214
|
+
uniform vec4 uTextureRect;
|
|
215
|
+
uniform vec3 uKernelAt;
|
|
216
|
+
uniform vec2 uKernelFrame;
|
|
217
|
+
uniform vec4 uKernelRegion;
|
|
218
|
+
uniform vec2 uOutputAt;
|
|
219
|
+
${withImages(kernel, images, layer, "p * uKernelAt.z + uKernelAt.xy")}
|
|
220
|
+
void main(void) {
|
|
221
|
+
// From the fragment's centre, exact, where an interpolated coordinate is a hair off a half.
|
|
222
|
+
vec2 outCoord = (gl_FragCoord.xy - uOutputAt - uKernelAt.xy) / uKernelAt.z;
|
|
223
|
+
if (any(lessThan(outCoord, uKernelRegion.xy)) || any(greaterThanEqual(outCoord, uKernelRegion.zw))) {
|
|
224
|
+
finalColor = vec4(0.0);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
finalColor = pbOutput(pbKernel(outCoord));
|
|
228
|
+
}
|
|
229
|
+
`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const madeBlends = new Map<string, Map<string, string>>();
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* A blend shader's composite (webgl/composite.ts), drawn as the composite
|
|
236
|
+
* program is (programs.ts, compositeFragment), its uniforms the same: as
|
|
237
|
+
* adl runs one, a pixel at a time, each where the layer covers anything
|
|
238
|
+
* and nothing where it does not, or over its whole rectangle (uWhole),
|
|
239
|
+
* the kernel's coordinate (0.5, 0.5) and its first two inputs that pixel
|
|
240
|
+
* of what is below and of the layer, coloured, whatever coordinate it
|
|
241
|
+
* samples them at; its result in place of what was below, as adl writes
|
|
242
|
+
* it (pbOutput). `rows` is the colour transform's reading of uRows
|
|
243
|
+
* (programs.ts, LAYER_COLOR).
|
|
244
|
+
*/
|
|
245
|
+
export function blendKernel(kernel: string, images: readonly KernelImage[], rows: string): string {
|
|
246
|
+
return madeOnce(madeBlends, kernel, images, () => blendSource(kernel, images, rows));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function blendSource(kernel: string, images: readonly KernelImage[], rows: string): string {
|
|
250
|
+
const pixel = (t: number, back: boolean) => `vec4 pbTexel${t}(ivec2 at) {
|
|
251
|
+
return pbBytes(${back ? "pbBack" : "pbFront"});
|
|
252
|
+
}`;
|
|
253
|
+
return `${HEADER}uniform sampler2D uLayer;
|
|
254
|
+
uniform sampler2D uBack;
|
|
255
|
+
uniform highp sampler2D uRows;
|
|
256
|
+
uniform int uRow;
|
|
257
|
+
uniform ivec2 uOffset;
|
|
258
|
+
uniform bool uFlip;
|
|
259
|
+
uniform ivec2 uBackOffset;
|
|
260
|
+
uniform bool uWhole;
|
|
261
|
+
out vec4 finalColor;
|
|
262
|
+
vec4 pbFront;
|
|
263
|
+
vec4 pbBack;
|
|
264
|
+
${withImages(kernel, images, pixel, "p")}
|
|
265
|
+
void main(void) {
|
|
266
|
+
ivec2 frag = ivec2(gl_FragCoord.xy);
|
|
267
|
+
ivec2 at = ivec2(frag.x - uOffset.x, uFlip ? uOffset.y - frag.y : frag.y - uOffset.y);
|
|
268
|
+
vec4 front = texelFetch(uLayer, at, 0);
|
|
269
|
+
${rows}
|
|
270
|
+
pbBack = texelFetch(uBack, frag - uBackOffset, 0);
|
|
271
|
+
if (!uWhole && pbBytes(front).a == 0.0) {
|
|
272
|
+
finalColor = pbBack;
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
pbFront = front;
|
|
276
|
+
finalColor = pbOutput(pbKernel(vec2(0.5)));
|
|
277
|
+
}
|
|
278
|
+
`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* The layer shrunk to a texel a stage pixel for a kernel run once a pixel
|
|
283
|
+
* (filter-plan.ts): each output texel the mean of the `uDown.x` by
|
|
284
|
+
* `uDown.x` texels of the pixel it stands for, the grid from `uDown.yz`
|
|
285
|
+
* texels past the input's corner, which may be less than 0; the texels of
|
|
286
|
+
* a pixel past the frame, `uKernelFrame`, left out of the mean.
|
|
287
|
+
*/
|
|
288
|
+
export const PIXELS_DOWN = `${HEADER}precision highp int;
|
|
289
|
+
in vec2 vTextureCoord;
|
|
290
|
+
out vec4 finalColor;
|
|
291
|
+
uniform sampler2D uTexture;
|
|
292
|
+
uniform vec4 uTextureRect;
|
|
293
|
+
uniform vec3 uDown;
|
|
294
|
+
uniform vec2 uKernelFrame;
|
|
295
|
+
uniform vec2 uOutputAt;
|
|
296
|
+
void main(void) {
|
|
297
|
+
int n = int(uDown.x);
|
|
298
|
+
ivec2 from = ivec2(gl_FragCoord.xy - uOutputAt) * n + ivec2(uDown.yz);
|
|
299
|
+
ivec2 frame = ivec2(uKernelFrame);
|
|
300
|
+
vec4 sum = vec4(0.0);
|
|
301
|
+
float count = 0.0;
|
|
302
|
+
for (int j = 0; j < 16; j++) {
|
|
303
|
+
if (j >= n) { break; }
|
|
304
|
+
for (int i = 0; i < 16; i++) {
|
|
305
|
+
if (i >= n) { break; }
|
|
306
|
+
ivec2 at = from + ivec2(i, j);
|
|
307
|
+
if (all(greaterThanEqual(at, ivec2(0))) && all(lessThan(at, frame))) {
|
|
308
|
+
sum += texelFetch(uTexture, ivec2(uTextureRect.xy) + at, 0);
|
|
309
|
+
count += 1.0;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
finalColor = count > 0.0 ? sum / count : vec4(0.0);
|
|
314
|
+
}
|
|
315
|
+
`;
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* A kernel's output, run a texel a stage pixel, drawn back over the
|
|
319
|
+
* layer's frame: each texel its pixel's, the grid as PIXELS_DOWN had it.
|
|
320
|
+
*/
|
|
321
|
+
export const PIXELS_BACK = `${HEADER}precision highp int;
|
|
322
|
+
in vec2 vTextureCoord;
|
|
323
|
+
out vec4 finalColor;
|
|
324
|
+
uniform sampler2D uTexture;
|
|
325
|
+
uniform vec4 uTextureRect;
|
|
326
|
+
uniform vec3 uDown;
|
|
327
|
+
uniform vec2 uOutputAt;
|
|
328
|
+
void main(void) {
|
|
329
|
+
ivec2 at = (ivec2(gl_FragCoord.xy - uOutputAt) - ivec2(uDown.yz)) / int(uDown.x);
|
|
330
|
+
at = clamp(at, ivec2(0), ivec2(uTextureRect.zw) - 1);
|
|
331
|
+
finalColor = texelFetch(uTexture, ivec2(uTextureRect.xy) + at, 0);
|
|
332
|
+
}
|
|
333
|
+
`;
|
|
334
|
+
|
|
335
|
+
const madeFills = new Map<string, Map<string, string>>();
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* A shader fill's program, after the mesh's vertex shader (programs.ts,
|
|
339
|
+
* meshVertex), which carries the kernel's coordinates in vUVT with a depth
|
|
340
|
+
* of 1: the kernel run once a pixel of Flash's, at its centre (uFrag as the
|
|
341
|
+
* mesh's has it), its images clamped to their edges; its colours left
|
|
342
|
+
* past its alpha, as adl composites them, but under a colour transform,
|
|
343
|
+
* which takes them straight.
|
|
344
|
+
*/
|
|
345
|
+
export function fillKernel(kernel: string, images: readonly KernelImage[]): string {
|
|
346
|
+
return madeOnce(madeFills, kernel, images, () => fillSource(kernel, images));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function fillSource(kernel: string, images: readonly KernelImage[]): string {
|
|
350
|
+
return `${HEADER}in vec3 vUVT;
|
|
351
|
+
flat in vec4 vMul;
|
|
352
|
+
flat in vec4 vAdd;
|
|
353
|
+
uniform vec4 uFrag;
|
|
354
|
+
out vec4 finalColor;
|
|
355
|
+
${withImages(kernel, images, () => "", "p")}
|
|
356
|
+
void main(void) {
|
|
357
|
+
float pixel = uFrag.w;
|
|
358
|
+
vec2 f = vec2(gl_FragCoord.x + uFrag.x, uFrag.y + uFrag.z * gl_FragCoord.y);
|
|
359
|
+
vec2 centre = (floor(f / pixel) + 0.5) * pixel;
|
|
360
|
+
vec3 at = vUVT + dFdx(vUVT) * (centre.x - f.x) + dFdy(vUVT) * ((centre.y - f.y) * uFrag.z);
|
|
361
|
+
vec4 c = pbKernel(at.xy / at.z);
|
|
362
|
+
c = mix(vec4(0.0), min(c, 1.0), greaterThan(c, vec4(0.0)));
|
|
363
|
+
if (vMul == vec4(1.0) && vAdd == vec4(0.0)) {
|
|
364
|
+
finalColor = c;
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (c.a == 0.0) {
|
|
368
|
+
finalColor = vec4(0.0);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
vec4 s = clamp(vec4(c.rgb / c.a, c.a) * vMul + vAdd, 0.0, 1.0);
|
|
372
|
+
finalColor = vec4(s.rgb * s.a, s.a);
|
|
373
|
+
}
|
|
374
|
+
`;
|
|
375
|
+
}
|