@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.
Files changed (143) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +17 -2
  3. package/dist/commands.d.ts +67 -0
  4. package/dist/commands.d.ts.map +1 -0
  5. package/dist/commands.js +93 -0
  6. package/dist/commands.js.map +1 -0
  7. package/dist/filter-plan.d.ts +110 -0
  8. package/dist/filter-plan.d.ts.map +1 -0
  9. package/dist/filter-plan.js +480 -0
  10. package/dist/filter-plan.js.map +1 -0
  11. package/dist/filter-shaders.d.ts +63 -0
  12. package/dist/filter-shaders.d.ts.map +1 -0
  13. package/dist/filter-shaders.js +350 -0
  14. package/dist/filter-shaders.js.map +1 -0
  15. package/dist/filters.d.ts +107 -0
  16. package/dist/filters.d.ts.map +1 -0
  17. package/dist/filters.js +187 -0
  18. package/dist/filters.js.map +1 -0
  19. package/dist/geometry.d.ts +83 -0
  20. package/dist/geometry.d.ts.map +1 -0
  21. package/dist/geometry.js +93 -0
  22. package/dist/geometry.js.map +1 -0
  23. package/dist/index.d.ts +11 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +13 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/keeps.d.ts +17 -0
  28. package/dist/keeps.d.ts.map +1 -0
  29. package/dist/keeps.js +17 -0
  30. package/dist/keeps.js.map +1 -0
  31. package/dist/kernel-shaders.d.ts +74 -0
  32. package/dist/kernel-shaders.d.ts.map +1 -0
  33. package/dist/kernel-shaders.js +323 -0
  34. package/dist/kernel-shaders.js.map +1 -0
  35. package/dist/measure.d.ts +56 -0
  36. package/dist/measure.d.ts.map +1 -0
  37. package/dist/measure.js +287 -0
  38. package/dist/measure.js.map +1 -0
  39. package/dist/plan.d.ts +14 -0
  40. package/dist/plan.d.ts.map +1 -0
  41. package/dist/plan.js +247 -0
  42. package/dist/plan.js.map +1 -0
  43. package/dist/registry.d.ts +49 -0
  44. package/dist/registry.d.ts.map +1 -0
  45. package/dist/registry.js +87 -0
  46. package/dist/registry.js.map +1 -0
  47. package/dist/rows.d.ts +39 -0
  48. package/dist/rows.d.ts.map +1 -0
  49. package/dist/rows.js +60 -0
  50. package/dist/rows.js.map +1 -0
  51. package/dist/scene.d.ts +179 -0
  52. package/dist/scene.d.ts.map +1 -0
  53. package/dist/scene.js +265 -0
  54. package/dist/scene.js.map +1 -0
  55. package/dist/shelves.d.ts +23 -0
  56. package/dist/shelves.d.ts.map +1 -0
  57. package/dist/shelves.js +63 -0
  58. package/dist/shelves.js.map +1 -0
  59. package/dist/textures.d.ts +72 -0
  60. package/dist/textures.d.ts.map +1 -0
  61. package/dist/textures.js +160 -0
  62. package/dist/textures.js.map +1 -0
  63. package/dist/webgl/atlas.d.ts +63 -0
  64. package/dist/webgl/atlas.d.ts.map +1 -0
  65. package/dist/webgl/atlas.js +328 -0
  66. package/dist/webgl/atlas.js.map +1 -0
  67. package/dist/webgl/backend.d.ts +141 -0
  68. package/dist/webgl/backend.d.ts.map +1 -0
  69. package/dist/webgl/backend.js +659 -0
  70. package/dist/webgl/backend.js.map +1 -0
  71. package/dist/webgl/composite.d.ts +101 -0
  72. package/dist/webgl/composite.d.ts.map +1 -0
  73. package/dist/webgl/composite.js +293 -0
  74. package/dist/webgl/composite.js.map +1 -0
  75. package/dist/webgl/draws.d.ts +213 -0
  76. package/dist/webgl/draws.d.ts.map +1 -0
  77. package/dist/webgl/draws.js +714 -0
  78. package/dist/webgl/draws.js.map +1 -0
  79. package/dist/webgl/filters.d.ts +107 -0
  80. package/dist/webgl/filters.d.ts.map +1 -0
  81. package/dist/webgl/filters.js +351 -0
  82. package/dist/webgl/filters.js.map +1 -0
  83. package/dist/webgl/gpu-textures.d.ts +44 -0
  84. package/dist/webgl/gpu-textures.d.ts.map +1 -0
  85. package/dist/webgl/gpu-textures.js +178 -0
  86. package/dist/webgl/gpu-textures.js.map +1 -0
  87. package/dist/webgl/holds.d.ts +97 -0
  88. package/dist/webgl/holds.d.ts.map +1 -0
  89. package/dist/webgl/holds.js +453 -0
  90. package/dist/webgl/holds.js.map +1 -0
  91. package/dist/webgl/index.d.ts +2 -0
  92. package/dist/webgl/index.d.ts.map +1 -0
  93. package/dist/webgl/index.js +4 -0
  94. package/dist/webgl/index.js.map +1 -0
  95. package/dist/webgl/kernels.d.ts +64 -0
  96. package/dist/webgl/kernels.d.ts.map +1 -0
  97. package/dist/webgl/kernels.js +160 -0
  98. package/dist/webgl/kernels.js.map +1 -0
  99. package/dist/webgl/pool.d.ts +71 -0
  100. package/dist/webgl/pool.d.ts.map +1 -0
  101. package/dist/webgl/pool.js +192 -0
  102. package/dist/webgl/pool.js.map +1 -0
  103. package/dist/webgl/programs.d.ts +130 -0
  104. package/dist/webgl/programs.d.ts.map +1 -0
  105. package/dist/webgl/programs.js +582 -0
  106. package/dist/webgl/programs.js.map +1 -0
  107. package/dist/webgl/stage.d.ts +67 -0
  108. package/dist/webgl/stage.d.ts.map +1 -0
  109. package/dist/webgl/stage.js +173 -0
  110. package/dist/webgl/stage.js.map +1 -0
  111. package/dist/webgl/targets.d.ts +172 -0
  112. package/dist/webgl/targets.d.ts.map +1 -0
  113. package/dist/webgl/targets.js +323 -0
  114. package/dist/webgl/targets.js.map +1 -0
  115. package/package.json +35 -3
  116. package/src/commands.ts +98 -0
  117. package/src/filter-plan.ts +755 -0
  118. package/src/filter-shaders.ts +360 -0
  119. package/src/filters.ts +220 -0
  120. package/src/geometry.ts +143 -0
  121. package/src/index.ts +87 -0
  122. package/src/keeps.ts +21 -0
  123. package/src/kernel-shaders.ts +375 -0
  124. package/src/measure.ts +400 -0
  125. package/src/plan.ts +331 -0
  126. package/src/registry.ts +105 -0
  127. package/src/rows.ts +93 -0
  128. package/src/scene.ts +297 -0
  129. package/src/shelves.ts +70 -0
  130. package/src/textures.ts +198 -0
  131. package/src/webgl/atlas.ts +370 -0
  132. package/src/webgl/backend.ts +973 -0
  133. package/src/webgl/composite.ts +372 -0
  134. package/src/webgl/draws.ts +902 -0
  135. package/src/webgl/filters.ts +502 -0
  136. package/src/webgl/gpu-textures.ts +253 -0
  137. package/src/webgl/holds.ts +624 -0
  138. package/src/webgl/index.ts +3 -0
  139. package/src/webgl/kernels.ts +205 -0
  140. package/src/webgl/pool.ts +245 -0
  141. package/src/webgl/programs.ts +620 -0
  142. package/src/webgl/stage.ts +214 -0
  143. package/src/webgl/targets.ts +464 -0
@@ -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
+ }