@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
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import { type GlState, linkProgram } from "@swf2es/context3d/device";
|
|
2
|
+
import {
|
|
3
|
+
BEVEL_PASS,
|
|
4
|
+
BOX_PASS,
|
|
5
|
+
CONVOLUTION_PASS,
|
|
6
|
+
DISPLACE_PASS,
|
|
7
|
+
FilterPlan,
|
|
8
|
+
FLOATS,
|
|
9
|
+
GLOW_PASS,
|
|
10
|
+
GRADIENT_PASS,
|
|
11
|
+
IVEC4S,
|
|
12
|
+
KERNEL_PASS,
|
|
13
|
+
type KernelObject,
|
|
14
|
+
MATRIX_PASS,
|
|
15
|
+
MATRIX4,
|
|
16
|
+
NO_BLUR,
|
|
17
|
+
planChain,
|
|
18
|
+
READ_BACK_PASS,
|
|
19
|
+
SAMPLER,
|
|
20
|
+
SHRINK_PASS,
|
|
21
|
+
VEC4S,
|
|
22
|
+
} from "../filter-plan.js";
|
|
23
|
+
import * as shaders from "../filter-shaders.js";
|
|
24
|
+
import type { Filters } from "../filters.js";
|
|
25
|
+
import { CLAMP, SMOOTH } from "../scene.js";
|
|
26
|
+
import type { KernelPrograms } from "./kernels.js";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A texture a pass draws into through its framebuffer, and reads: at least
|
|
30
|
+
* its frame's size; or a rectangle of one, `width` by `height` from texel
|
|
31
|
+
* (x, y), read as if it were a texture of its own, its edges clamped.
|
|
32
|
+
*/
|
|
33
|
+
export interface FilterTarget {
|
|
34
|
+
framebuffer: WebGLFramebuffer;
|
|
35
|
+
texture: WebGLTexture;
|
|
36
|
+
width: number;
|
|
37
|
+
height: number;
|
|
38
|
+
x?: number;
|
|
39
|
+
y?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Where the runner takes its targets from and gives them back, and finds textures and samplers. */
|
|
43
|
+
export interface FilterHost<T extends FilterTarget> {
|
|
44
|
+
/**
|
|
45
|
+
* A target for a frame of `width` by `height`, of the one size the host
|
|
46
|
+
* gives every such frame: two targets for one frame are alike, and a
|
|
47
|
+
* filter that reads two at one coordinate reads them alike.
|
|
48
|
+
*/
|
|
49
|
+
take(width: number, height: number): T;
|
|
50
|
+
give(target: T): void;
|
|
51
|
+
texture(id: number): WebGLTexture | null;
|
|
52
|
+
sampler(bits: number): WebGLSampler;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The units a pass reads its input, the blur it combines with, and a
|
|
57
|
+
* gradient from; a kernel's images take KernelPrograms' own (KERNEL_UNIT).
|
|
58
|
+
*/
|
|
59
|
+
const INPUT_UNIT = 4;
|
|
60
|
+
const BLURRED_UNIT = 5;
|
|
61
|
+
const EXTRA_UNIT = 6;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A triangle over the output's frame, the viewport, reading its input
|
|
65
|
+
* from 0 to uCoord: the input's frame over its texture's size, or more of
|
|
66
|
+
* it for a shrink.
|
|
67
|
+
*/
|
|
68
|
+
const VERTEX = `#version 300 es
|
|
69
|
+
uniform vec2 uCoord;
|
|
70
|
+
out vec2 vTextureCoord;
|
|
71
|
+
void main(void) {
|
|
72
|
+
vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
|
|
73
|
+
gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
|
|
74
|
+
vTextureCoord = p * uCoord;
|
|
75
|
+
}
|
|
76
|
+
`;
|
|
77
|
+
|
|
78
|
+
const HEADER = "#version 300 es\nprecision highp float;\n";
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The input and the blur read within their rectangles (FilterTarget), as a
|
|
82
|
+
* texture of their own size clamped to its edges reads, linearly: a texel
|
|
83
|
+
* coordinate kept half a texel in from each edge reads the edge texel, as
|
|
84
|
+
* the edge's clamp does.
|
|
85
|
+
*/
|
|
86
|
+
const READS = `uniform vec4 uTextureRect;
|
|
87
|
+
uniform vec4 uBlurredRect;
|
|
88
|
+
vec4 within(sampler2D t, vec4 rect, vec2 at) {
|
|
89
|
+
vec2 texel = clamp(at * rect.zw, vec2(0.5), rect.zw - 0.5);
|
|
90
|
+
return texture(t, (rect.xy + texel) / vec2(textureSize(t, 0)));
|
|
91
|
+
}
|
|
92
|
+
`;
|
|
93
|
+
|
|
94
|
+
/** A filter's fragment shader reading its input and its blur within their rectangles (READS). */
|
|
95
|
+
function withinRects(fragment: string): string {
|
|
96
|
+
const reads = fragment
|
|
97
|
+
.replaceAll("texture(uTexture,", "within(uTexture, uTextureRect,")
|
|
98
|
+
.replaceAll("texture(uBlurred,", "within(uBlurred, uBlurredRect,");
|
|
99
|
+
// A read left as it was would read past its rectangle, into another's on a page: refused.
|
|
100
|
+
if (/\b(texture\w*|texelFetch\w*)\s*\(\s*u(Texture|Blurred)\b/.test(reads)) {
|
|
101
|
+
throw new Error("a filter shader reads its input other than as texture(uTexture, at)");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// After the samplers' declarations, which READS's function takes.
|
|
105
|
+
const at = reads.lastIndexOf("uniform sampler2D");
|
|
106
|
+
const end = reads.indexOf("\n", at) + 1;
|
|
107
|
+
return reads.slice(0, end) + READS + reads.slice(end);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The input as it is, read where the vertex shader says: halved, at a corner four texels share. */
|
|
111
|
+
const COPY = `in vec2 vTextureCoord;
|
|
112
|
+
out vec4 finalColor;
|
|
113
|
+
uniform sampler2D uTexture;
|
|
114
|
+
void main(void) {
|
|
115
|
+
finalColor = texture(uTexture, vTextureCoord);
|
|
116
|
+
}`;
|
|
117
|
+
|
|
118
|
+
interface Pass {
|
|
119
|
+
program: WebGLProgram;
|
|
120
|
+
uniforms: Map<string, WebGLUniformLocation | null>;
|
|
121
|
+
/** The values of up to four floats it was last told for each uniform, by name (set). */
|
|
122
|
+
told: Map<string, Float32Array>;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Runs a layer's chain of Flash's filters, each a pass or a few over
|
|
127
|
+
* targets the host lends: the shaders of filter-shaders.ts, reading their
|
|
128
|
+
* input's size in the target's units, uInputSize, and its texels,
|
|
129
|
+
* uInputPixel. Every target's rows run down, so the shaders' offsets keep
|
|
130
|
+
* their sense. Each pass clears its whole output, which its
|
|
131
|
+
* successor may read past the frame.
|
|
132
|
+
*/
|
|
133
|
+
export class FilterRunner<T extends FilterTarget> {
|
|
134
|
+
private readonly gl: WebGL2RenderingContext;
|
|
135
|
+
private readonly state: GlState;
|
|
136
|
+
private readonly host: FilterHost<T>;
|
|
137
|
+
private readonly passes = new Map<string, Pass>();
|
|
138
|
+
/** The kernel image units a kernel's pass has bound so far. */
|
|
139
|
+
private samplers = 0;
|
|
140
|
+
/** Whether a kernel's fragment shader links (planChain's `links`), linking it now if not yet. */
|
|
141
|
+
readonly links = (fragment: string) => this.kernel(fragment) !== null;
|
|
142
|
+
/** The chain being run, planned (filter-plan.ts), each slot's target and the last pass using it. */
|
|
143
|
+
private readonly plan = new FilterPlan();
|
|
144
|
+
private readonly targets: (T | null)[] = [];
|
|
145
|
+
private readonly lastUse: number[] = [];
|
|
146
|
+
private readonly hasTexture = (id: number) => id >= 0 && this.host.texture(id) !== null;
|
|
147
|
+
/** The target's pixels a unit of its own, for the chain running. */
|
|
148
|
+
private resolution = 1;
|
|
149
|
+
|
|
150
|
+
constructor(
|
|
151
|
+
gl: WebGL2RenderingContext,
|
|
152
|
+
state: GlState,
|
|
153
|
+
host: FilterHost<T>,
|
|
154
|
+
private readonly kernels: KernelPrograms,
|
|
155
|
+
) {
|
|
156
|
+
this.gl = gl;
|
|
157
|
+
this.state = state;
|
|
158
|
+
this.host = host;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Chain `chain` of `filters` run over `input`'s first `width` by `height`
|
|
163
|
+
* texels: the target its last pass drew into, `input` itself where none
|
|
164
|
+
* changed it. Every other target, `input` too where it was replaced, goes
|
|
165
|
+
* back to the host. `object` is where the layer's object lies in it
|
|
166
|
+
* (planChain).
|
|
167
|
+
*/
|
|
168
|
+
run(
|
|
169
|
+
filters: Filters,
|
|
170
|
+
chain: number,
|
|
171
|
+
input: T,
|
|
172
|
+
width: number,
|
|
173
|
+
height: number,
|
|
174
|
+
resolution: number,
|
|
175
|
+
object: KernelObject | null = null,
|
|
176
|
+
): T {
|
|
177
|
+
const plan = this.plan;
|
|
178
|
+
planChain(plan, filters, chain, width, height, resolution, this.hasTexture, this.links, object);
|
|
179
|
+
return this.execute(plan, input);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* `input`'s first `width` by `height` texels at half the size each way,
|
|
184
|
+
* each texel the mean of the four it covers, read once linearly at the
|
|
185
|
+
* corner they share: a target the host lends, which the caller gives
|
|
186
|
+
* back. Not a filter's pass, so at a texel a unit.
|
|
187
|
+
*/
|
|
188
|
+
halve(input: T, width: number, height: number): T {
|
|
189
|
+
this.resolution = 1;
|
|
190
|
+
const out = this.host.take(width, height);
|
|
191
|
+
this.draw(this.use("copy", COPY), input, width * 2, height * 2, out, width, height);
|
|
192
|
+
return out;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* `plan`'s passes drawn in order, its slot 0 `input`: each slot's target
|
|
197
|
+
* taken as a pass first draws into it and given back after the last pass
|
|
198
|
+
* that reads it, but the one the plan leaves its output in.
|
|
199
|
+
*/
|
|
200
|
+
private execute(plan: FilterPlan, input: T): T {
|
|
201
|
+
const targets = this.targets;
|
|
202
|
+
const last = this.lastUse;
|
|
203
|
+
targets.length = plan.slots;
|
|
204
|
+
last.length = plan.slots;
|
|
205
|
+
targets.fill(null);
|
|
206
|
+
last.fill(-1);
|
|
207
|
+
targets[0] = input;
|
|
208
|
+
for (let k = 0; k < plan.count; k++) {
|
|
209
|
+
const pass = plan.passes[k];
|
|
210
|
+
last[pass.input] = k;
|
|
211
|
+
last[pass.output] = k;
|
|
212
|
+
if (pass.blurred >= 0) {
|
|
213
|
+
last[pass.blurred] = k;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
for (let k = 0; k < plan.count; k++) {
|
|
218
|
+
const pass = plan.passes[k];
|
|
219
|
+
if (!targets[pass.output]) {
|
|
220
|
+
targets[pass.output] = this.host.take(
|
|
221
|
+
plan.slotWidth[pass.output],
|
|
222
|
+
plan.slotHeight[pass.output],
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
this.drawPass(plan, k, targets, true);
|
|
227
|
+
for (let slot = 0; slot < plan.slots; slot++) {
|
|
228
|
+
const target = targets[slot];
|
|
229
|
+
if (target && last[slot] === k && slot !== plan.result) {
|
|
230
|
+
this.host.give(target);
|
|
231
|
+
targets[slot] = null;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return targets[plan.result] as T;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Pass `k` of `plan` drawn from and into `targets`, by slot, which hold
|
|
241
|
+
* every one it reads and draws into; its output's rectangle cleared first
|
|
242
|
+
* where `clear`, else left as it is, as a rectangle no pass drew into yet is.
|
|
243
|
+
*/
|
|
244
|
+
drawPass(
|
|
245
|
+
plan: FilterPlan,
|
|
246
|
+
k: number,
|
|
247
|
+
targets: readonly (FilterTarget | null)[],
|
|
248
|
+
clear: boolean,
|
|
249
|
+
): void {
|
|
250
|
+
this.resolution = plan.resolution;
|
|
251
|
+
const pass = plan.passes[k];
|
|
252
|
+
const from = targets[pass.input] as FilterTarget;
|
|
253
|
+
const to = targets[pass.output] as FilterTarget;
|
|
254
|
+
// A kernel that will not link was left out as the chain was planned (links).
|
|
255
|
+
const program =
|
|
256
|
+
pass.program === KERNEL_PASS
|
|
257
|
+
? (this.kernel(pass.kernel as string) ?? this.use("copy", COPY))
|
|
258
|
+
: this.program(pass.program);
|
|
259
|
+
this.samplers = 0;
|
|
260
|
+
for (let u = pass.first; u < pass.end; u++) {
|
|
261
|
+
this.setUniform(program, plan, u);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (pass.texture >= 0) {
|
|
265
|
+
this.state.bindTexture(EXTRA_UNIT, this.host.texture(pass.texture));
|
|
266
|
+
this.state.bindSampler(EXTRA_UNIT, this.host.sampler(CLAMP));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (pass.blurred !== NO_BLUR) {
|
|
270
|
+
// The blur, or the input where nothing blurred it, with the scale between them.
|
|
271
|
+
const blurred = pass.blurred >= 0 ? (targets[pass.blurred] as FilterTarget) : from;
|
|
272
|
+
this.state.bindTexture(BLURRED_UNIT, blurred.texture);
|
|
273
|
+
this.state.bindSampler(BLURRED_UNIT, this.host.sampler(SMOOTH | CLAMP));
|
|
274
|
+
this.set(
|
|
275
|
+
program,
|
|
276
|
+
"uBlurredScale",
|
|
277
|
+
2,
|
|
278
|
+
from.width / (blurred.width * pass.across),
|
|
279
|
+
from.height / (blurred.height * pass.down),
|
|
280
|
+
);
|
|
281
|
+
this.set(
|
|
282
|
+
program,
|
|
283
|
+
"uBlurredRect",
|
|
284
|
+
4,
|
|
285
|
+
blurred.x ?? 0,
|
|
286
|
+
blurred.y ?? 0,
|
|
287
|
+
blurred.width,
|
|
288
|
+
blurred.height,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
this.draw(program, from, pass.readWidth, pass.readHeight, to, pass.width, pass.height, clear);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* `pass`'s uniform `name`, of `count` floats, set to `x` and those after,
|
|
297
|
+
* unless it was told them last: a frame's glows alike tell their passes
|
|
298
|
+
* the same values over and over.
|
|
299
|
+
*/
|
|
300
|
+
private set(pass: Pass, name: string, count: number, x: number, y = 0, z = 0, w = 0): void {
|
|
301
|
+
let told = pass.told.get(name);
|
|
302
|
+
if (!told) {
|
|
303
|
+
told = new Float32Array(4).fill(Number.NaN);
|
|
304
|
+
pass.told.set(name, told);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Compared as the floats the uniform holds.
|
|
308
|
+
if (
|
|
309
|
+
told[0] === Math.fround(x) &&
|
|
310
|
+
told[1] === Math.fround(y) &&
|
|
311
|
+
told[2] === Math.fround(z) &&
|
|
312
|
+
told[3] === Math.fround(w)
|
|
313
|
+
) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
told[0] = x;
|
|
318
|
+
told[1] = y;
|
|
319
|
+
told[2] = z;
|
|
320
|
+
told[3] = w;
|
|
321
|
+
const gl = this.gl;
|
|
322
|
+
const location = this.at(pass, name);
|
|
323
|
+
switch (count) {
|
|
324
|
+
case 1:
|
|
325
|
+
gl.uniform1f(location, x);
|
|
326
|
+
break;
|
|
327
|
+
case 2:
|
|
328
|
+
gl.uniform2f(location, x, y);
|
|
329
|
+
break;
|
|
330
|
+
case 3:
|
|
331
|
+
gl.uniform3f(location, x, y, z);
|
|
332
|
+
break;
|
|
333
|
+
default:
|
|
334
|
+
gl.uniform4f(location, x, y, z, w);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Uniform `u` of `plan` set on `pass`, in use. */
|
|
339
|
+
private setUniform(pass: Pass, plan: FilterPlan, u: number): void {
|
|
340
|
+
const gl = this.gl;
|
|
341
|
+
const location = this.at(pass, plan.uniformName[u]);
|
|
342
|
+
const values = plan.values;
|
|
343
|
+
const at = plan.uniformAt[u];
|
|
344
|
+
const count = plan.uniformCount[u];
|
|
345
|
+
switch (plan.uniformKind[u]) {
|
|
346
|
+
case MATRIX4:
|
|
347
|
+
gl.uniformMatrix4fv(location, false, values, at, 16);
|
|
348
|
+
break;
|
|
349
|
+
case VEC4S:
|
|
350
|
+
gl.uniform4fv(location, values, at, count);
|
|
351
|
+
break;
|
|
352
|
+
case IVEC4S:
|
|
353
|
+
this.kernels.setInts(location, plan.ints, at, count);
|
|
354
|
+
break;
|
|
355
|
+
case SAMPLER:
|
|
356
|
+
// As many as KERNEL_IMAGES, which the plan holds a kernel to.
|
|
357
|
+
this.kernels.bindImage(location, this.samplers++, values[at], this.host);
|
|
358
|
+
break;
|
|
359
|
+
case FLOATS:
|
|
360
|
+
this.set(
|
|
361
|
+
pass,
|
|
362
|
+
plan.uniformName[u],
|
|
363
|
+
count,
|
|
364
|
+
values[at],
|
|
365
|
+
count > 1 ? values[at + 1] : 0,
|
|
366
|
+
count > 2 ? values[at + 2] : 0,
|
|
367
|
+
count > 3 ? values[at + 3] : 0,
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** The pass of program `id` (filter-plan.ts), in use. */
|
|
373
|
+
private program(id: number): Pass {
|
|
374
|
+
switch (id) {
|
|
375
|
+
case BOX_PASS:
|
|
376
|
+
return this.use("box", shaders.BOX);
|
|
377
|
+
case SHRINK_PASS:
|
|
378
|
+
return this.use("shrink", shaders.SHRINK);
|
|
379
|
+
case READ_BACK_PASS:
|
|
380
|
+
return this.use("readBack", shaders.READ_BACK);
|
|
381
|
+
case GLOW_PASS:
|
|
382
|
+
return this.use("glow", shaders.GLOW);
|
|
383
|
+
case BEVEL_PASS:
|
|
384
|
+
return this.use("bevel", shaders.BEVEL);
|
|
385
|
+
case GRADIENT_PASS:
|
|
386
|
+
return this.use("gradient", shaders.GRADIENT);
|
|
387
|
+
case MATRIX_PASS:
|
|
388
|
+
return this.use("colorMatrix", shaders.MATRIX);
|
|
389
|
+
case CONVOLUTION_PASS:
|
|
390
|
+
return this.use("convolution", shaders.CONVOLUTION);
|
|
391
|
+
case DISPLACE_PASS:
|
|
392
|
+
return this.use("displacement", shaders.DISPLACE);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
throw new Error(`no filter pass ${id}`);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* `pass` drawn into `output`'s first `width` by `height` texels, the rest
|
|
400
|
+
* of it cleared, reading `input` from its corner over `readWidth` by
|
|
401
|
+
* `readHeight` texels.
|
|
402
|
+
*/
|
|
403
|
+
private draw(
|
|
404
|
+
pass: Pass,
|
|
405
|
+
input: FilterTarget,
|
|
406
|
+
readWidth: number,
|
|
407
|
+
readHeight: number,
|
|
408
|
+
output: FilterTarget,
|
|
409
|
+
width: number,
|
|
410
|
+
height: number,
|
|
411
|
+
clear = true,
|
|
412
|
+
): void {
|
|
413
|
+
const gl = this.gl;
|
|
414
|
+
const state = this.state;
|
|
415
|
+
const x = output.x ?? 0;
|
|
416
|
+
const y = output.y ?? 0;
|
|
417
|
+
state.bindFramebuffer(output.framebuffer);
|
|
418
|
+
state.viewport(x, y, width, height);
|
|
419
|
+
state.colorMask(15);
|
|
420
|
+
state.stencil(null);
|
|
421
|
+
state.setBlend(null);
|
|
422
|
+
if (clear) {
|
|
423
|
+
state.scissor([x, y, output.width, output.height]);
|
|
424
|
+
gl.clearColor(0, 0, 0, 0);
|
|
425
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
state.scissor(null);
|
|
429
|
+
state.useProgram(pass.program);
|
|
430
|
+
state.bindVertexArray(null);
|
|
431
|
+
state.bindTexture(INPUT_UNIT, input.texture);
|
|
432
|
+
state.bindSampler(INPUT_UNIT, this.host.sampler(SMOOTH | CLAMP));
|
|
433
|
+
const res = this.resolution;
|
|
434
|
+
this.set(
|
|
435
|
+
pass,
|
|
436
|
+
"uInputSize",
|
|
437
|
+
4,
|
|
438
|
+
input.width / res,
|
|
439
|
+
input.height / res,
|
|
440
|
+
res / input.width,
|
|
441
|
+
res / input.height,
|
|
442
|
+
);
|
|
443
|
+
this.set(pass, "uInputPixel", 4, input.width, input.height, 1 / input.width, 1 / input.height);
|
|
444
|
+
this.set(pass, "uCoord", 2, readWidth / input.width, readHeight / input.height);
|
|
445
|
+
this.set(pass, "uTextureRect", 4, input.x ?? 0, input.y ?? 0, input.width, input.height);
|
|
446
|
+
// A kernel's alone: the others need not be told.
|
|
447
|
+
if (this.at(pass, "uOutputAt")) {
|
|
448
|
+
this.set(pass, "uOutputAt", 2, x, y);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/** The pass of `fragment`, linked as first used, in use. */
|
|
455
|
+
private use(name: string, fragment: string): Pass {
|
|
456
|
+
const gl = this.gl;
|
|
457
|
+
let pass = this.passes.get(name);
|
|
458
|
+
if (!pass) {
|
|
459
|
+
const program = linkProgram(gl, VERTEX, HEADER + withinRects(fragment));
|
|
460
|
+
pass = {
|
|
461
|
+
program,
|
|
462
|
+
uniforms: new Map(),
|
|
463
|
+
told: new Map(),
|
|
464
|
+
};
|
|
465
|
+
this.passes.set(name, pass);
|
|
466
|
+
this.state.useProgram(program);
|
|
467
|
+
gl.uniform1i(gl.getUniformLocation(program, "uTexture"), INPUT_UNIT);
|
|
468
|
+
gl.uniform1i(gl.getUniformLocation(program, "uBlurred"), BLURRED_UNIT);
|
|
469
|
+
gl.uniform1i(gl.getUniformLocation(program, "uGradient"), EXTRA_UNIT);
|
|
470
|
+
gl.uniform1i(gl.getUniformLocation(program, "uMap"), EXTRA_UNIT);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
this.state.useProgram(pass.program);
|
|
474
|
+
return pass;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/** The pass of a kernel's fragment shader (KernelPrograms), in use; null where it would not link. */
|
|
478
|
+
private kernel(fragment: string): Pass | null {
|
|
479
|
+
return this.kernels.use(VERTEX, fragment, (program) => {
|
|
480
|
+
this.gl.uniform1i(this.gl.getUniformLocation(program, "uTexture"), INPUT_UNIT);
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/** Where `pass` takes uniform `name`, asked once. */
|
|
485
|
+
private at(pass: Pass, name: string): WebGLUniformLocation | null {
|
|
486
|
+
let location = pass.uniforms.get(name);
|
|
487
|
+
if (location === undefined) {
|
|
488
|
+
location = this.gl.getUniformLocation(pass.program, name);
|
|
489
|
+
pass.uniforms.set(name, location);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return location;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
destroy(): void {
|
|
496
|
+
for (const pass of this.passes.values()) {
|
|
497
|
+
this.gl.deleteProgram(pass.program);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
this.passes.clear();
|
|
501
|
+
}
|
|
502
|
+
}
|