@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,973 @@
|
|
|
1
|
+
import { GlState, linkProgram } from "@swf2es/context3d/device";
|
|
2
|
+
import {
|
|
3
|
+
CLEAR,
|
|
4
|
+
COMPOSITE,
|
|
5
|
+
type Commands,
|
|
6
|
+
FILTER,
|
|
7
|
+
HELD,
|
|
8
|
+
HOLD_POP,
|
|
9
|
+
HOLD_PUSH,
|
|
10
|
+
LAYER_POP,
|
|
11
|
+
LAYER_PUSH,
|
|
12
|
+
RUN,
|
|
13
|
+
STENCIL_POP,
|
|
14
|
+
STENCIL_PUSH,
|
|
15
|
+
} from "../commands.js";
|
|
16
|
+
import type { Filters } from "../filters.js";
|
|
17
|
+
import type { Geometries } from "../geometry.js";
|
|
18
|
+
import type { Keeps } from "../keeps.js";
|
|
19
|
+
import type { Rows } from "../rows.js";
|
|
20
|
+
import {
|
|
21
|
+
ADD,
|
|
22
|
+
DARKEN,
|
|
23
|
+
DIFFERENCE,
|
|
24
|
+
HARDLIGHT,
|
|
25
|
+
INVERT,
|
|
26
|
+
LIGHTEN,
|
|
27
|
+
MULTIPLY,
|
|
28
|
+
NORMAL,
|
|
29
|
+
OVERLAY,
|
|
30
|
+
SCREEN,
|
|
31
|
+
type SceneList,
|
|
32
|
+
} from "../scene.js";
|
|
33
|
+
import type { Textures } from "../textures.js";
|
|
34
|
+
import { GeometryAtlas } from "./atlas.js";
|
|
35
|
+
import { Compositor, readsBelow } from "./composite.js";
|
|
36
|
+
import { Draws, type Masks } from "./draws.js";
|
|
37
|
+
import { FilterRunner } from "./filters.js";
|
|
38
|
+
import { GpuTextures } from "./gpu-textures.js";
|
|
39
|
+
import { HeldLayers } from "./holds.js";
|
|
40
|
+
import { KernelPrograms } from "./kernels.js";
|
|
41
|
+
import { LAYER_UNIT, TEXTURE_UNIT } from "./programs.js";
|
|
42
|
+
import { Stage } from "./stage.js";
|
|
43
|
+
import { type Layer, type Open, objectOf, type Target, TargetPool } from "./targets.js";
|
|
44
|
+
|
|
45
|
+
export type { Target } from "./targets.js";
|
|
46
|
+
|
|
47
|
+
/** The blend modes a flat fill may be drawn in straight onto what is below (blendDirect). */
|
|
48
|
+
function direct(mode: number): boolean {
|
|
49
|
+
switch (mode) {
|
|
50
|
+
case ADD:
|
|
51
|
+
case SCREEN:
|
|
52
|
+
case MULTIPLY:
|
|
53
|
+
case LIGHTEN:
|
|
54
|
+
case DARKEN:
|
|
55
|
+
case DIFFERENCE:
|
|
56
|
+
case OVERLAY:
|
|
57
|
+
case HARDLIGHT:
|
|
58
|
+
case INVERT:
|
|
59
|
+
return true;
|
|
60
|
+
default:
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Executes a frame's commands on a WebGL 2 context it owns, through one
|
|
67
|
+
* cache of its state. Every geometry is copied once into an atlas of a
|
|
68
|
+
* vertex and an index buffer, and drawn from it a call an entry, the
|
|
69
|
+
* buffers bound once and the entry told its row; rows go up once a frame
|
|
70
|
+
* into a float texture. A geometry let go of leaves a hole, and the atlas
|
|
71
|
+
* is packed again from the geometries' own arrays once the holes outweigh
|
|
72
|
+
* what is live. Its parts, which it owns and makes again with the context:
|
|
73
|
+
* the geometry atlas (atlas.ts), the textures and rows (gpu-textures.ts),
|
|
74
|
+
* the entries' draws (draws.ts), the render targets (targets.ts), the held
|
|
75
|
+
* layers (holds.ts), the composites (composite.ts) and the stage's way onto
|
|
76
|
+
* the canvas (stage.ts).
|
|
77
|
+
*/
|
|
78
|
+
/** compositeOver's program and its uniforms. */
|
|
79
|
+
interface OverProgram {
|
|
80
|
+
program: WebGLProgram;
|
|
81
|
+
at: WebGLUniformLocation | null;
|
|
82
|
+
opaque: WebGLUniformLocation | null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** A triangle over the viewport, of no attributes. */
|
|
86
|
+
const OVER_VERTEX = `#version 300 es
|
|
87
|
+
void main() {
|
|
88
|
+
vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
|
|
89
|
+
gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
|
|
90
|
+
}`;
|
|
91
|
+
|
|
92
|
+
/** Flash's source over on whole bytes (WebGLBackend.compositeOver), both textures read at the pixel's own texel. */
|
|
93
|
+
const OVER_FRAGMENT = `#version 300 es
|
|
94
|
+
precision highp float;
|
|
95
|
+
precision highp int;
|
|
96
|
+
uniform sampler2D uSource;
|
|
97
|
+
uniform sampler2D uBelow;
|
|
98
|
+
uniform ivec2 uAt;
|
|
99
|
+
uniform bool uOpaque;
|
|
100
|
+
out vec4 finalColor;
|
|
101
|
+
void main() {
|
|
102
|
+
ivec2 p = ivec2(gl_FragCoord.xy) - uAt;
|
|
103
|
+
ivec4 s = ivec4(round(texelFetch(uSource, p, 0) * 255.0));
|
|
104
|
+
ivec4 d = ivec4(round(texelFetch(uBelow, p, 0) * 255.0));
|
|
105
|
+
ivec4 o = s.a == 255 ? s : s.a == 0 ? d : min(ivec4(255), s + ((d * (256 - s.a)) >> 8));
|
|
106
|
+
if (uOpaque) {
|
|
107
|
+
o.a = 255;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
finalColor = vec4(o) / 255.0;
|
|
111
|
+
}`;
|
|
112
|
+
|
|
113
|
+
export class WebGLBackend {
|
|
114
|
+
readonly gl: WebGL2RenderingContext;
|
|
115
|
+
readonly state: GlState;
|
|
116
|
+
private readonly pool: TargetPool;
|
|
117
|
+
private readonly stage: Stage;
|
|
118
|
+
private readonly gpu: GpuTextures;
|
|
119
|
+
private readonly atlas: GeometryAtlas;
|
|
120
|
+
private readonly compositor: Compositor;
|
|
121
|
+
private readonly draws: Draws;
|
|
122
|
+
private readonly holds: HeldLayers;
|
|
123
|
+
/** Pixel Bender kernels' programs, which the filters, the compositor and the draws share. */
|
|
124
|
+
private readonly kernels: KernelPrograms;
|
|
125
|
+
/** The filters' passes, their programs made as first run. */
|
|
126
|
+
private filters!: FilterRunner<Layer>;
|
|
127
|
+
/** The layers drawn into now, innermost last. */
|
|
128
|
+
private layers: Open[] = [];
|
|
129
|
+
/** The frame's filters being drawn, whose chains blend shaders' modes name (KERNEL). */
|
|
130
|
+
private frameFilters: Filters | null = null;
|
|
131
|
+
/**
|
|
132
|
+
* Samples a pixel the stage and its layers are drawn with, 1 for none:
|
|
133
|
+
* Flash's anti-aliasing by the quality, as the host sets it.
|
|
134
|
+
*/
|
|
135
|
+
samples = 1;
|
|
136
|
+
/** The masks pushed on the target drawn into now (execute). */
|
|
137
|
+
private readonly masks: Masks = { depth: 0, past: 0 };
|
|
138
|
+
/** A layer's rectangle, as blendDirect reads it. */
|
|
139
|
+
private readonly rect = { x: 0, y: 0, width: 0, height: 0 };
|
|
140
|
+
/** The mask counts of the targets below the layers drawn into now, two numbers each. */
|
|
141
|
+
private readonly below: number[] = [];
|
|
142
|
+
/** The program `compositeOver` draws with, linked as first used; null till then and after a lost context. */
|
|
143
|
+
private overProgram: OverProgram | null = null;
|
|
144
|
+
/** Framebuffers onto registry textures, drawn into or read, by texture id, with the texture they were made for. */
|
|
145
|
+
private textureFramebuffers: ({ texture: WebGLTexture; framebuffer: WebGLFramebuffer } | null)[] =
|
|
146
|
+
[];
|
|
147
|
+
|
|
148
|
+
constructor(gl: WebGL2RenderingContext) {
|
|
149
|
+
this.gl = gl;
|
|
150
|
+
this.state = new GlState(gl);
|
|
151
|
+
this.kernels = new KernelPrograms(gl, this.state);
|
|
152
|
+
this.pool = new TargetPool(gl, this.state);
|
|
153
|
+
this.stage = new Stage(gl, this.state, this.pool);
|
|
154
|
+
this.gpu = new GpuTextures(gl, this.state);
|
|
155
|
+
this.atlas = new GeometryAtlas(gl, this.state);
|
|
156
|
+
this.compositor = new Compositor(
|
|
157
|
+
gl,
|
|
158
|
+
this.state,
|
|
159
|
+
{
|
|
160
|
+
filters: () => this.frameFilters,
|
|
161
|
+
texture: (id) => this.gpu.textures[id] ?? null,
|
|
162
|
+
sampler: (bits) => this.gpu.sampler(bits),
|
|
163
|
+
},
|
|
164
|
+
this.kernels,
|
|
165
|
+
);
|
|
166
|
+
this.draws = new Draws(
|
|
167
|
+
gl,
|
|
168
|
+
this.state,
|
|
169
|
+
this.atlas,
|
|
170
|
+
this.gpu,
|
|
171
|
+
this.pool,
|
|
172
|
+
{
|
|
173
|
+
filters: () => this.frameFilters,
|
|
174
|
+
texture: (id) => this.gpu.textures[id] ?? null,
|
|
175
|
+
sampler: (bits) => this.gpu.sampler(bits),
|
|
176
|
+
},
|
|
177
|
+
this.kernels,
|
|
178
|
+
);
|
|
179
|
+
this.holds = new HeldLayers(gl, this.state, this.pool, this.draws, this.compositor, this.gpu);
|
|
180
|
+
this.setUp();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The context came back after it was lost: everything made again, every
|
|
185
|
+
* geometry copied in again from the registry's arrays. The textures were
|
|
186
|
+
* lost with it, which their owners add again (Textures.lost).
|
|
187
|
+
*/
|
|
188
|
+
restore(geometries: Geometries): void {
|
|
189
|
+
this.setUp();
|
|
190
|
+
geometries.added.length = 0;
|
|
191
|
+
geometries.removed.length = 0;
|
|
192
|
+
geometries.updated.length = 0;
|
|
193
|
+
this.atlas.repack(geometries);
|
|
194
|
+
this.atlas.repackGlyphs(geometries);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Programs, buffers and textures made, as the context comes or comes back. */
|
|
198
|
+
private setUp(): void {
|
|
199
|
+
const gl = this.gl;
|
|
200
|
+
this.state.forget();
|
|
201
|
+
// Kernels' programs made again as first used, the context's own gone with it.
|
|
202
|
+
this.kernels.clear();
|
|
203
|
+
const textureMost = gl.getParameter(gl.MAX_TEXTURE_SIZE) as number;
|
|
204
|
+
this.draws.setUp(textureMost);
|
|
205
|
+
this.compositor.setUp();
|
|
206
|
+
this.filters = new FilterRunner(
|
|
207
|
+
gl,
|
|
208
|
+
this.state,
|
|
209
|
+
{
|
|
210
|
+
take: (width, height) => this.pool.takeLayer(width, height, true),
|
|
211
|
+
give: (layer) => this.pool.giveLayer(layer),
|
|
212
|
+
texture: (id) => this.gpu.textures[id] ?? null,
|
|
213
|
+
sampler: (bits) => this.gpu.sampler(bits),
|
|
214
|
+
},
|
|
215
|
+
this.kernels,
|
|
216
|
+
);
|
|
217
|
+
this.holds.setUp(this.filters, textureMost);
|
|
218
|
+
this.pool.setUp();
|
|
219
|
+
this.layers = [];
|
|
220
|
+
this.stage.setUp();
|
|
221
|
+
this.atlas.setUp();
|
|
222
|
+
this.gpu.setUp();
|
|
223
|
+
// The context's own, gone with it.
|
|
224
|
+
this.textureFramebuffers = [];
|
|
225
|
+
this.overProgram = null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Draw a frame: what the geometries added and removed first, then the
|
|
230
|
+
* rows, then the commands; onto the canvas, or into `root`, a layer the
|
|
231
|
+
* target's size, as BitmapData.draw's frames go.
|
|
232
|
+
*/
|
|
233
|
+
execute(
|
|
234
|
+
commands: Commands,
|
|
235
|
+
scene: SceneList,
|
|
236
|
+
rows: Rows,
|
|
237
|
+
geometries: Geometries,
|
|
238
|
+
textures: Textures,
|
|
239
|
+
filters: Filters,
|
|
240
|
+
keeps: Keeps,
|
|
241
|
+
target: Target,
|
|
242
|
+
root: Layer | null = null,
|
|
243
|
+
): void {
|
|
244
|
+
// A frame drawn into a layer, for BitmapData, is no frame of the canvas': idle layers are
|
|
245
|
+
// counted in the canvas' frames.
|
|
246
|
+
const counted = root === null;
|
|
247
|
+
const gl = this.gl;
|
|
248
|
+
if (gl.isContextLost()) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
this.frameFilters = filters;
|
|
253
|
+
this.atlas.place(geometries);
|
|
254
|
+
this.gpu.uploadTextures(textures);
|
|
255
|
+
for (const id of keeps.removed) {
|
|
256
|
+
this.pool.release(id);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
keeps.removed.length = 0;
|
|
260
|
+
this.gpu.uploadRows(rows);
|
|
261
|
+
this.draws.prepare(commands, scene);
|
|
262
|
+
const state = this.state;
|
|
263
|
+
state.scissor(null);
|
|
264
|
+
state.colorMask(15);
|
|
265
|
+
state.useProgram(this.draws.program);
|
|
266
|
+
state.bindVertexArray(this.atlas.vertexArray);
|
|
267
|
+
state.bindTexture(0, this.gpu.rowsTexture);
|
|
268
|
+
// As many samples as the GPU takes; a BitmapData's frame supersamples instead, as Flash's draw
|
|
269
|
+
// does. Its layers take the stage's, so that their edges are the stage's.
|
|
270
|
+
const stage = root ? null : this.stage.stageBuffers(target.width, target.height, this.samples);
|
|
271
|
+
// An erase or alpha blend outside any layer clears what it covers on the stage, as adl does,
|
|
272
|
+
// which the blends after it read: an opaque canvas keeps no alpha, so such a frame is drawn
|
|
273
|
+
// into a layer the canvas' size, then copied onto it. Multisampled buffers keep it already.
|
|
274
|
+
const cleared =
|
|
275
|
+
!root && !stage && !this.stage.canvasAlpha && commands.clearsTarget
|
|
276
|
+
? this.stage.stageAlpha(target.width, target.height)
|
|
277
|
+
: null;
|
|
278
|
+
const into = root ?? cleared;
|
|
279
|
+
if (into) {
|
|
280
|
+
// Its own stencil, for the masks drawn into it.
|
|
281
|
+
this.pool.layerMsaa(into, 1);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
this.pool.beginFrame(stage?.samples ?? 1);
|
|
285
|
+
this.holds.drawHolds(commands, scene, filters, target);
|
|
286
|
+
const canvas: Open = {
|
|
287
|
+
layer: into,
|
|
288
|
+
msaa: stage,
|
|
289
|
+
x: 0,
|
|
290
|
+
y: 0,
|
|
291
|
+
width: target.width,
|
|
292
|
+
height: target.height,
|
|
293
|
+
opaque: commands.opaqueStage,
|
|
294
|
+
};
|
|
295
|
+
this.layers.length = 0;
|
|
296
|
+
this.draws.drawInto(canvas, target);
|
|
297
|
+
|
|
298
|
+
this.draws.forgetRow();
|
|
299
|
+
// The masks pushed on the target drawn into. A layer has a stencil of its own, and the
|
|
300
|
+
// target below keeps its count meanwhile.
|
|
301
|
+
const masks = this.masks;
|
|
302
|
+
masks.depth = 0;
|
|
303
|
+
masks.past = 0;
|
|
304
|
+
const below = this.below;
|
|
305
|
+
below.length = 0;
|
|
306
|
+
state.stencil(null);
|
|
307
|
+
const ops = commands.ops;
|
|
308
|
+
for (let k = 0; k < commands.length; k++) {
|
|
309
|
+
const at = k * 4;
|
|
310
|
+
switch (ops[at]) {
|
|
311
|
+
case CLEAR: {
|
|
312
|
+
const p = commands.params;
|
|
313
|
+
const from = ops[at + 1];
|
|
314
|
+
gl.clearColor(p[from], p[from + 1], p[from + 2], p[from + 3]);
|
|
315
|
+
gl.clearStencil(0);
|
|
316
|
+
gl.stencilMask(0xff);
|
|
317
|
+
gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
case RUN:
|
|
322
|
+
this.draws.drawRun(scene, ops[at + 1], ops[at + 1] + ops[at + 2], k);
|
|
323
|
+
break;
|
|
324
|
+
case STENCIL_PUSH:
|
|
325
|
+
case STENCIL_POP:
|
|
326
|
+
this.draws.drawMask(
|
|
327
|
+
scene,
|
|
328
|
+
ops[at + 1],
|
|
329
|
+
ops[at + 1] + ops[at + 2],
|
|
330
|
+
ops[at] === STENCIL_PUSH,
|
|
331
|
+
masks,
|
|
332
|
+
k,
|
|
333
|
+
);
|
|
334
|
+
break;
|
|
335
|
+
|
|
336
|
+
case HOLD_PUSH:
|
|
337
|
+
// Drawn ahead of the stage (drawHolds).
|
|
338
|
+
while (ops[k * 4] !== HOLD_POP) {
|
|
339
|
+
k++;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
break;
|
|
343
|
+
case HELD:
|
|
344
|
+
this.draws.maskBy(masks);
|
|
345
|
+
this.holds.compositeHeld(
|
|
346
|
+
ops[at + 1],
|
|
347
|
+
this.layers.at(-1) ?? canvas,
|
|
348
|
+
ops[at + 2],
|
|
349
|
+
ops[at + 3],
|
|
350
|
+
);
|
|
351
|
+
break;
|
|
352
|
+
|
|
353
|
+
case LAYER_PUSH: {
|
|
354
|
+
if (this.blendDirect(commands, k, scene, canvas)) {
|
|
355
|
+
// Its run and its pop with it.
|
|
356
|
+
k += 2;
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
below.push(masks.depth, masks.past);
|
|
361
|
+
masks.depth = 0;
|
|
362
|
+
masks.past = 0;
|
|
363
|
+
const p = commands.params;
|
|
364
|
+
const from = ops[at + 1];
|
|
365
|
+
const width = p[from + 2];
|
|
366
|
+
const height = p[from + 3];
|
|
367
|
+
const filtered = ops[at + 2] === 1;
|
|
368
|
+
// A filtered one of the size its filters' targets take for it, which they read alike.
|
|
369
|
+
const layer = this.pool.takeLayer(width, height, filtered);
|
|
370
|
+
const open: Open = {
|
|
371
|
+
layer,
|
|
372
|
+
msaa: this.pool.layerMsaa(layer, this.pool.frameSamples),
|
|
373
|
+
x: p[from],
|
|
374
|
+
y: p[from + 1],
|
|
375
|
+
width,
|
|
376
|
+
height,
|
|
377
|
+
object: objectOf(p, from + 4),
|
|
378
|
+
};
|
|
379
|
+
this.layers.push(open);
|
|
380
|
+
this.draws.drawInto(open, target);
|
|
381
|
+
state.stencil(null);
|
|
382
|
+
state.colorMask(15);
|
|
383
|
+
gl.clearColor(0, 0, 0, 0);
|
|
384
|
+
gl.clearStencil(0);
|
|
385
|
+
gl.stencilMask(0xff);
|
|
386
|
+
// Its rectangle alone, as a layer taken may be larger, unless filters read past it, as far
|
|
387
|
+
// as they read.
|
|
388
|
+
state.scissor(filtered ? [0, 0, layer.width, layer.height] : [0, 0, width, height]);
|
|
389
|
+
gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
|
|
390
|
+
state.scissor(null);
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
case FILTER: {
|
|
395
|
+
const open = this.layers.at(-1);
|
|
396
|
+
if (open?.layer) {
|
|
397
|
+
state.stencil(null);
|
|
398
|
+
// Whole, as the filters read past its rectangle.
|
|
399
|
+
this.pool.resolve(open, open.layer.width, open.layer.height);
|
|
400
|
+
open.layer = this.filters.run(
|
|
401
|
+
filters,
|
|
402
|
+
ops[at + 1],
|
|
403
|
+
open.layer,
|
|
404
|
+
open.width,
|
|
405
|
+
open.height,
|
|
406
|
+
target.resolution,
|
|
407
|
+
open.object,
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
case LAYER_POP: {
|
|
415
|
+
const open = this.layers.pop() as Open;
|
|
416
|
+
masks.past = below.pop() as number;
|
|
417
|
+
masks.depth = below.pop() as number;
|
|
418
|
+
this.pool.resolve(open, open.width, open.height);
|
|
419
|
+
const under = this.layers.at(-1) ?? canvas;
|
|
420
|
+
this.draws.drawInto(under, target);
|
|
421
|
+
// Composited where the masks on the target below let it be.
|
|
422
|
+
this.draws.maskBy(masks);
|
|
423
|
+
this.compositor.compositeLayer(open, under, ops[at + 1], ops[at + 2]);
|
|
424
|
+
const keep = ops[at + 3];
|
|
425
|
+
if (keep >= 0) {
|
|
426
|
+
// Kept for the frames after, the output kept before given back.
|
|
427
|
+
this.pool.keep(keep, open.layer as Layer);
|
|
428
|
+
} else {
|
|
429
|
+
this.pool.giveLayer(open.layer as Layer);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
case COMPOSITE: {
|
|
436
|
+
const p = commands.params;
|
|
437
|
+
const from = ops[at + 1];
|
|
438
|
+
const layer = this.pool.keptLayer(p[from + 4]);
|
|
439
|
+
if (layer) {
|
|
440
|
+
const kept: Open = {
|
|
441
|
+
layer,
|
|
442
|
+
msaa: null,
|
|
443
|
+
x: p[from],
|
|
444
|
+
y: p[from + 1],
|
|
445
|
+
width: p[from + 2],
|
|
446
|
+
height: p[from + 3],
|
|
447
|
+
};
|
|
448
|
+
this.draws.maskBy(masks);
|
|
449
|
+
this.compositor.compositeLayer(
|
|
450
|
+
kept,
|
|
451
|
+
this.layers.at(-1) ?? canvas,
|
|
452
|
+
ops[at + 2],
|
|
453
|
+
ops[at + 3],
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
state.stencil(null);
|
|
463
|
+
if (canvas.msaa) {
|
|
464
|
+
this.stage.present(canvas.msaa);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (cleared) {
|
|
468
|
+
// Its rows run down, the canvas' up.
|
|
469
|
+
this.stage.toCanvas(cleared.framebuffer, target.width, target.height, true);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
this.holds.givePages();
|
|
473
|
+
if (counted) {
|
|
474
|
+
// The free layers, and the stage's (stageAlpha), undrawn into for LAYER_IDLE_FRAMES let go of.
|
|
475
|
+
this.stage.dropIdleLayer();
|
|
476
|
+
this.pool.endFrame();
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* A frame drawn into a layer `samples` times the size of a `width` by
|
|
482
|
+
* `height` bitmap a side, as `execute` draws, then halved till a sample a
|
|
483
|
+
* pixel, and read back: premultiplied RGBA bytes, a row at a time from
|
|
484
|
+
* the top, as BitmapData.draw takes them.
|
|
485
|
+
*/
|
|
486
|
+
snapshot(
|
|
487
|
+
commands: Commands,
|
|
488
|
+
scene: SceneList,
|
|
489
|
+
rows: Rows,
|
|
490
|
+
geometries: Geometries,
|
|
491
|
+
textures: Textures,
|
|
492
|
+
filters: Filters,
|
|
493
|
+
keeps: Keeps,
|
|
494
|
+
target: Target,
|
|
495
|
+
width: number,
|
|
496
|
+
height: number,
|
|
497
|
+
samples: number,
|
|
498
|
+
): Uint8Array {
|
|
499
|
+
const gl = this.gl;
|
|
500
|
+
const bytes = new Uint8Array(width * height * 4);
|
|
501
|
+
if (gl.isContextLost()) {
|
|
502
|
+
return bytes;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const layer = this.drawSampled(
|
|
506
|
+
commands,
|
|
507
|
+
scene,
|
|
508
|
+
rows,
|
|
509
|
+
geometries,
|
|
510
|
+
textures,
|
|
511
|
+
filters,
|
|
512
|
+
keeps,
|
|
513
|
+
target,
|
|
514
|
+
width,
|
|
515
|
+
height,
|
|
516
|
+
samples,
|
|
517
|
+
);
|
|
518
|
+
this.state.bindFramebuffer(layer.framebuffer);
|
|
519
|
+
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, bytes);
|
|
520
|
+
this.pool.giveLayer(layer);
|
|
521
|
+
return bytes;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* BitmapData.draw on the GPU: the commands drawn as `snapshot` draws
|
|
526
|
+
* them, into a layer of their own (the object may show the texture
|
|
527
|
+
* itself), then over texture `id` of the registry at (x, y), as Flash's
|
|
528
|
+
* store composites source over (compositeOver), where it stays: nothing
|
|
529
|
+
* is read back; `opaque` for an opaque store's texture. Pending
|
|
530
|
+
* textures, the one drawn into among them, are uploaded first. Whether
|
|
531
|
+
* it was drawn; not on a lost context or without the texture.
|
|
532
|
+
*/
|
|
533
|
+
drawIntoTexture(
|
|
534
|
+
commands: Commands,
|
|
535
|
+
scene: SceneList,
|
|
536
|
+
rows: Rows,
|
|
537
|
+
geometries: Geometries,
|
|
538
|
+
textures: Textures,
|
|
539
|
+
filters: Filters,
|
|
540
|
+
keeps: Keeps,
|
|
541
|
+
target: Target,
|
|
542
|
+
width: number,
|
|
543
|
+
height: number,
|
|
544
|
+
samples: number,
|
|
545
|
+
id: number,
|
|
546
|
+
x: number,
|
|
547
|
+
y: number,
|
|
548
|
+
opaque: boolean,
|
|
549
|
+
): boolean {
|
|
550
|
+
const gl = this.gl;
|
|
551
|
+
if (gl.isContextLost()) {
|
|
552
|
+
return false;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const layer = this.drawSampled(
|
|
556
|
+
commands,
|
|
557
|
+
scene,
|
|
558
|
+
rows,
|
|
559
|
+
geometries,
|
|
560
|
+
textures,
|
|
561
|
+
filters,
|
|
562
|
+
keeps,
|
|
563
|
+
target,
|
|
564
|
+
width,
|
|
565
|
+
height,
|
|
566
|
+
samples,
|
|
567
|
+
);
|
|
568
|
+
const into = this.textureFramebuffer(id);
|
|
569
|
+
if (into) {
|
|
570
|
+
this.compositeOver(layer, into, x, y, width, height, opaque);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
this.pool.giveLayer(layer);
|
|
574
|
+
return into !== null;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* `layer`'s first `width` by `height` texels over those of `into` at
|
|
579
|
+
* (x, y), as Flash's store composites source over, a channel at a time
|
|
580
|
+
* on whole bytes: s + ((d * (256 - sa)) >> 8), at most 255, a source of
|
|
581
|
+
* alpha 255 put as it is and one of 0 leaving what was there; an opaque
|
|
582
|
+
* store's alpha 255 after. GL's own blending rounds where Flash floors,
|
|
583
|
+
* a level apart: the destination's rectangle is copied into a layer of
|
|
584
|
+
* its own first, and both read exactly.
|
|
585
|
+
*/
|
|
586
|
+
private compositeOver(
|
|
587
|
+
layer: Layer,
|
|
588
|
+
into: WebGLFramebuffer,
|
|
589
|
+
x: number,
|
|
590
|
+
y: number,
|
|
591
|
+
width: number,
|
|
592
|
+
height: number,
|
|
593
|
+
opaque: boolean,
|
|
594
|
+
): void {
|
|
595
|
+
const gl = this.gl;
|
|
596
|
+
const state = this.state;
|
|
597
|
+
state.scissor(null);
|
|
598
|
+
const below = this.pool.takeLayer(width, height);
|
|
599
|
+
this.blit(into, x, y, below.framebuffer, 0, 0, width, height);
|
|
600
|
+
const over = this.overProgramMade();
|
|
601
|
+
state.bindFramebuffer(into);
|
|
602
|
+
state.viewport(x, y, width, height);
|
|
603
|
+
state.colorMask(15);
|
|
604
|
+
state.stencil(null);
|
|
605
|
+
state.setBlend(null);
|
|
606
|
+
state.useProgram(over.program);
|
|
607
|
+
state.bindVertexArray(null);
|
|
608
|
+
const nearest = this.gpu.sampler(0);
|
|
609
|
+
state.bindTexture(TEXTURE_UNIT, layer.texture);
|
|
610
|
+
state.bindSampler(TEXTURE_UNIT, nearest);
|
|
611
|
+
state.bindTexture(LAYER_UNIT, below.texture);
|
|
612
|
+
state.bindSampler(LAYER_UNIT, nearest);
|
|
613
|
+
gl.uniform2i(over.at, x, y);
|
|
614
|
+
gl.uniform1i(over.opaque, opaque ? 1 : 0);
|
|
615
|
+
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
616
|
+
this.pool.giveLayer(below);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/** compositeOver's program, linked as first asked for. */
|
|
620
|
+
private overProgramMade(): OverProgram {
|
|
621
|
+
if (this.overProgram) {
|
|
622
|
+
return this.overProgram;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const gl = this.gl;
|
|
626
|
+
const program = linkProgram(gl, OVER_VERTEX, OVER_FRAGMENT);
|
|
627
|
+
this.state.useProgram(program);
|
|
628
|
+
gl.uniform1i(gl.getUniformLocation(program, "uSource"), TEXTURE_UNIT);
|
|
629
|
+
gl.uniform1i(gl.getUniformLocation(program, "uBelow"), LAYER_UNIT);
|
|
630
|
+
this.overProgram = {
|
|
631
|
+
program,
|
|
632
|
+
at: gl.getUniformLocation(program, "uAt"),
|
|
633
|
+
opaque: gl.getUniformLocation(program, "uOpaque"),
|
|
634
|
+
};
|
|
635
|
+
return this.overProgram;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Texture `id`'s `width` by `height` texels read back, premultiplied RGBA
|
|
640
|
+
* row by row; null on a lost context or without the texture.
|
|
641
|
+
*/
|
|
642
|
+
readTexture(id: number, width: number, height: number): Uint8Array | null {
|
|
643
|
+
const framebuffer = this.gl.isContextLost() ? null : this.textureFramebuffer(id);
|
|
644
|
+
if (!framebuffer) {
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const bytes = new Uint8Array(width * height * 4);
|
|
649
|
+
this.state.bindFramebuffer(framebuffer);
|
|
650
|
+
this.gl.readPixels(0, 0, width, height, this.gl.RGBA, this.gl.UNSIGNED_BYTE, bytes);
|
|
651
|
+
return bytes;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Texture `id`'s `width` by `height` texels at (x, y) cleared to `pixel`,
|
|
656
|
+
* premultiplied ARGB, which comes back exactly; false on a lost context
|
|
657
|
+
* or without the texture.
|
|
658
|
+
*/
|
|
659
|
+
fillTexture(
|
|
660
|
+
textures: Textures,
|
|
661
|
+
id: number,
|
|
662
|
+
x: number,
|
|
663
|
+
y: number,
|
|
664
|
+
width: number,
|
|
665
|
+
height: number,
|
|
666
|
+
pixel: number,
|
|
667
|
+
): boolean {
|
|
668
|
+
const gl = this.gl;
|
|
669
|
+
this.gpu.uploadTextures(textures);
|
|
670
|
+
const framebuffer = gl.isContextLost() ? null : this.textureFramebuffer(id);
|
|
671
|
+
if (!framebuffer) {
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
this.state.bindFramebuffer(framebuffer);
|
|
676
|
+
this.state.colorMask(15);
|
|
677
|
+
this.state.scissor([x, y, width, height]);
|
|
678
|
+
gl.clearColor(
|
|
679
|
+
((pixel >>> 16) & 0xff) / 255,
|
|
680
|
+
((pixel >>> 8) & 0xff) / 255,
|
|
681
|
+
(pixel & 0xff) / 255,
|
|
682
|
+
(pixel >>> 24) / 255,
|
|
683
|
+
);
|
|
684
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
685
|
+
this.state.scissor(null);
|
|
686
|
+
return true;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Texture `from`'s `width` by `height` texels at (x, y) copied to (dx, dy)
|
|
691
|
+
* of texture `to`, replacing what was there: as a memmove where they are
|
|
692
|
+
* one texture, through a layer of its own, as a texture copied into
|
|
693
|
+
* itself is GL's feedback loop. Pending textures are uploaded first.
|
|
694
|
+
* False on a lost context or without either texture.
|
|
695
|
+
*/
|
|
696
|
+
copyTexture(
|
|
697
|
+
textures: Textures,
|
|
698
|
+
from: number,
|
|
699
|
+
x: number,
|
|
700
|
+
y: number,
|
|
701
|
+
width: number,
|
|
702
|
+
height: number,
|
|
703
|
+
to: number,
|
|
704
|
+
dx: number,
|
|
705
|
+
dy: number,
|
|
706
|
+
): boolean {
|
|
707
|
+
const gl = this.gl;
|
|
708
|
+
this.gpu.uploadTextures(textures);
|
|
709
|
+
const source = gl.isContextLost() ? null : this.textureFramebuffer(from);
|
|
710
|
+
const target = source ? this.textureFramebuffer(to) : null;
|
|
711
|
+
if (!source || !target) {
|
|
712
|
+
return false;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
this.state.scissor(null);
|
|
716
|
+
if (from !== to) {
|
|
717
|
+
this.blit(source, x, y, target, dx, dy, width, height);
|
|
718
|
+
return true;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const between = this.pool.takeLayer(width, height);
|
|
722
|
+
this.state.bindFramebuffer(between.framebuffer);
|
|
723
|
+
gl.invalidateSubFramebuffer(gl.FRAMEBUFFER, [gl.COLOR_ATTACHMENT0], 0, 0, width, height);
|
|
724
|
+
this.blit(source, x, y, between.framebuffer, 0, 0, width, height);
|
|
725
|
+
this.blit(between.framebuffer, 0, 0, target, dx, dy, width, height);
|
|
726
|
+
this.pool.giveLayer(between);
|
|
727
|
+
return true;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/** `width` by `height` texels at (x, y) of `from` blitted to (dx, dy) of `to`, the state cache's binding `to` after. */
|
|
731
|
+
private blit(
|
|
732
|
+
from: WebGLFramebuffer,
|
|
733
|
+
x: number,
|
|
734
|
+
y: number,
|
|
735
|
+
to: WebGLFramebuffer,
|
|
736
|
+
dx: number,
|
|
737
|
+
dy: number,
|
|
738
|
+
width: number,
|
|
739
|
+
height: number,
|
|
740
|
+
): void {
|
|
741
|
+
const gl = this.gl;
|
|
742
|
+
this.state.bindFramebuffer(to);
|
|
743
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, from);
|
|
744
|
+
gl.blitFramebuffer(
|
|
745
|
+
x,
|
|
746
|
+
y,
|
|
747
|
+
x + width,
|
|
748
|
+
y + height,
|
|
749
|
+
dx,
|
|
750
|
+
dy,
|
|
751
|
+
dx + width,
|
|
752
|
+
dy + height,
|
|
753
|
+
gl.COLOR_BUFFER_BIT,
|
|
754
|
+
gl.NEAREST,
|
|
755
|
+
);
|
|
756
|
+
// Bound as the state cache has it.
|
|
757
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, to);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/** A framebuffer onto texture `id`, made as first asked for; null for an id of no texture. */
|
|
761
|
+
private textureFramebuffer(id: number): WebGLFramebuffer | null {
|
|
762
|
+
const gl = this.gl;
|
|
763
|
+
const texture = this.gpu.textures[id] ?? null;
|
|
764
|
+
const known = this.textureFramebuffers[id] ?? null;
|
|
765
|
+
if (known?.texture === texture) {
|
|
766
|
+
return known.framebuffer;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// The id names another texture now: its framebuffer goes with the one it was made for.
|
|
770
|
+
if (known) {
|
|
771
|
+
gl.deleteFramebuffer(known.framebuffer);
|
|
772
|
+
this.textureFramebuffers[id] = null;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
if (!texture) {
|
|
776
|
+
return null;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const framebuffer = gl.createFramebuffer() as WebGLFramebuffer;
|
|
780
|
+
this.state.bindFramebuffer(framebuffer);
|
|
781
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
|
|
782
|
+
this.textureFramebuffers[id] = { texture, framebuffer };
|
|
783
|
+
return framebuffer;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* A 3D object's bitmap drawn (PLANE): the commands, as `execute` draws
|
|
788
|
+
* them, into a layer `samples` times the size of a `width` by `height`
|
|
789
|
+
* bitmap a side, halved till a sample a texel, and kept as kept output
|
|
790
|
+
* `keep` (Keeps), the one kept before given back. Called as the frame is
|
|
791
|
+
* walked, before its own commands are drawn, which composite it.
|
|
792
|
+
*/
|
|
793
|
+
drawPlane(
|
|
794
|
+
keep: number,
|
|
795
|
+
commands: Commands,
|
|
796
|
+
scene: SceneList,
|
|
797
|
+
rows: Rows,
|
|
798
|
+
geometries: Geometries,
|
|
799
|
+
textures: Textures,
|
|
800
|
+
filters: Filters,
|
|
801
|
+
keeps: Keeps,
|
|
802
|
+
target: Target,
|
|
803
|
+
width: number,
|
|
804
|
+
height: number,
|
|
805
|
+
samples: number,
|
|
806
|
+
): void {
|
|
807
|
+
const gl = this.gl;
|
|
808
|
+
if (gl.isContextLost()) {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
const layer = this.drawSampled(
|
|
813
|
+
commands,
|
|
814
|
+
scene,
|
|
815
|
+
rows,
|
|
816
|
+
geometries,
|
|
817
|
+
textures,
|
|
818
|
+
filters,
|
|
819
|
+
keeps,
|
|
820
|
+
target,
|
|
821
|
+
width,
|
|
822
|
+
height,
|
|
823
|
+
samples,
|
|
824
|
+
);
|
|
825
|
+
// Clear past the bitmap, which a layer taken may be larger than: its quad's smoothing reads
|
|
826
|
+
// half a texel past its edges.
|
|
827
|
+
this.state.bindFramebuffer(layer.framebuffer);
|
|
828
|
+
this.state.stencil(null);
|
|
829
|
+
this.state.colorMask(15);
|
|
830
|
+
gl.clearColor(0, 0, 0, 0);
|
|
831
|
+
for (const [x, y, w, h] of [
|
|
832
|
+
[width, 0, layer.width - width, layer.height],
|
|
833
|
+
[0, height, width, layer.height - height],
|
|
834
|
+
]) {
|
|
835
|
+
if (w > 0 && h > 0) {
|
|
836
|
+
this.state.scissor([x, y, w, h]);
|
|
837
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
this.state.scissor(null);
|
|
842
|
+
this.pool.keep(keep, layer);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* The commands drawn, as `execute` draws them, into a layer `samples`
|
|
847
|
+
* times the size of a `width` by `height` bitmap a side, halved till a
|
|
848
|
+
* sample a pixel: the layer, its first `width` by `height` the bitmap.
|
|
849
|
+
*/
|
|
850
|
+
private drawSampled(
|
|
851
|
+
commands: Commands,
|
|
852
|
+
scene: SceneList,
|
|
853
|
+
rows: Rows,
|
|
854
|
+
geometries: Geometries,
|
|
855
|
+
textures: Textures,
|
|
856
|
+
filters: Filters,
|
|
857
|
+
keeps: Keeps,
|
|
858
|
+
target: Target,
|
|
859
|
+
width: number,
|
|
860
|
+
height: number,
|
|
861
|
+
samples: number,
|
|
862
|
+
): Layer {
|
|
863
|
+
let layer = this.pool.takeLayer(width * samples, height * samples);
|
|
864
|
+
this.execute(commands, scene, rows, geometries, textures, filters, keeps, target, layer);
|
|
865
|
+
for (let k = samples; k > 1; k /= 2) {
|
|
866
|
+
const half = this.filters.halve(layer, (width * k) / 2, (height * k) / 2);
|
|
867
|
+
this.pool.giveLayer(layer);
|
|
868
|
+
layer = half;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
return layer;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/** Advanced text's outlines uploaded (GpuTextures.uploadOutlines). */
|
|
875
|
+
uploadOutlines(data: Float32Array, count: number, epoch: number): void {
|
|
876
|
+
this.gpu.uploadOutlines(data, count, epoch);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/** What the backend made on the context, deleted. */
|
|
880
|
+
destroy(): void {
|
|
881
|
+
this.draws.destroy();
|
|
882
|
+
this.atlas.destroy();
|
|
883
|
+
this.compositor.destroy();
|
|
884
|
+
this.filters.destroy();
|
|
885
|
+
this.kernels.clear();
|
|
886
|
+
for (const open of this.layers) {
|
|
887
|
+
if (open.layer) {
|
|
888
|
+
this.pool.deleteLayer(open.layer);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
this.pool.destroy();
|
|
893
|
+
this.stage.destroy();
|
|
894
|
+
for (const known of this.textureFramebuffers) {
|
|
895
|
+
if (known) {
|
|
896
|
+
this.gl.deleteFramebuffer(known.framebuffer);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
this.textureFramebuffers = [];
|
|
901
|
+
if (this.overProgram) {
|
|
902
|
+
this.gl.deleteProgram(this.overProgram.program);
|
|
903
|
+
this.overProgram = null;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
this.gpu.destroy();
|
|
907
|
+
this.state.forget();
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* The layer pushed at command `k` drawn straight onto the target below,
|
|
912
|
+
* if it may be: unfiltered and not kept, one run of a flat fill alone
|
|
913
|
+
* (Draws.blendable) drawn normally in it, and a mode whose blend of the
|
|
914
|
+
* fill pixel by pixel, its edges covering the pixels in part, is that
|
|
915
|
+
* of the layer's pixels covered alike, as each is linear in what covers it
|
|
916
|
+
* (add short of its sums' clamp); its colour on the quad's. A layer and its
|
|
917
|
+
* resolve and composite left out, what reads what is below copies it as
|
|
918
|
+
* the composite would. Whether it was.
|
|
919
|
+
*/
|
|
920
|
+
private blendDirect(commands: Commands, k: number, scene: SceneList, canvas: Open): boolean {
|
|
921
|
+
const ops = commands.ops;
|
|
922
|
+
const at = k * 4;
|
|
923
|
+
const run = at + 4;
|
|
924
|
+
const pop = at + 8;
|
|
925
|
+
if (
|
|
926
|
+
k + 2 >= commands.length ||
|
|
927
|
+
ops[at + 2] !== 0 ||
|
|
928
|
+
ops[run] !== RUN ||
|
|
929
|
+
ops[run + 2] !== 1 ||
|
|
930
|
+
ops[run + 3] !== NORMAL ||
|
|
931
|
+
ops[pop] !== LAYER_POP ||
|
|
932
|
+
ops[pop + 3] >= 0 ||
|
|
933
|
+
!direct(ops[pop + 1]) ||
|
|
934
|
+
!this.draws.blendable(scene, ops[run + 1])
|
|
935
|
+
) {
|
|
936
|
+
return false;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
const p = commands.params;
|
|
940
|
+
const from = ops[at + 1];
|
|
941
|
+
const under = this.layers.at(-1) ?? canvas;
|
|
942
|
+
const rect = this.rect;
|
|
943
|
+
rect.x = p[from];
|
|
944
|
+
rect.y = p[from + 1];
|
|
945
|
+
rect.width = p[from + 2];
|
|
946
|
+
rect.height = p[from + 3];
|
|
947
|
+
const mode = ops[pop + 1];
|
|
948
|
+
const compositor = this.compositor;
|
|
949
|
+
if (!compositor.cover(rect, under)) {
|
|
950
|
+
return true;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// Drawn into already, as a kept output is composited (COMPOSITE); a copy of what is below
|
|
954
|
+
// binds its framebuffer back.
|
|
955
|
+
const reads = readsBelow(mode, under.opaque);
|
|
956
|
+
const back = reads && compositor.copyBelow(under);
|
|
957
|
+
this.draws.maskBy(this.masks);
|
|
958
|
+
const place = compositor.place;
|
|
959
|
+
// Kept to the layer's rectangle, as the composite is, past which nothing below is copied.
|
|
960
|
+
this.state.scissor([place.fx, place.fy, place.width, place.height]);
|
|
961
|
+
this.draws.drawBlended(
|
|
962
|
+
scene,
|
|
963
|
+
ops[run + 1],
|
|
964
|
+
mode,
|
|
965
|
+
reads,
|
|
966
|
+
ops[pop + 2],
|
|
967
|
+
back ? place.backX : 0,
|
|
968
|
+
back ? place.backY : 0,
|
|
969
|
+
);
|
|
970
|
+
this.state.scissor(null);
|
|
971
|
+
return true;
|
|
972
|
+
}
|
|
973
|
+
}
|