@driftengine/core 4.13.6 → 4.14.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 (91) hide show
  1. package/CHANGELOG.json +51 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.js +1 -0
  5. package/dist/render/backend/api.d.ts +19 -3
  6. package/dist/render/backend/webgl2/motionTarget.d.ts +39 -0
  7. package/dist/render/backend/webgl2/motionTarget.js +254 -0
  8. package/dist/render/backend/webgl2/pointSkinFeedback.d.ts +13 -3
  9. package/dist/render/backend/webgl2/pointSkinFeedback.js +45 -12
  10. package/dist/render/backend/webgl2/renderer.d.ts +101 -7
  11. package/dist/render/backend/webgl2/renderer.js +337 -9
  12. package/dist/render/backend/webgpu/pointSkinPass.d.ts +16 -3
  13. package/dist/render/backend/webgpu/pointSkinPass.js +79 -21
  14. package/dist/render/backend/webgpu/postPass.d.ts +6 -1
  15. package/dist/render/backend/webgpu/renderer.d.ts +41 -0
  16. package/dist/render/backend/webgpu/renderer.js +143 -5
  17. package/dist/render/baseDecals.d.ts +48 -0
  18. package/dist/render/baseDecals.js +71 -0
  19. package/dist/render/emissiveFlow.d.ts +40 -0
  20. package/dist/render/emissiveFlow.js +51 -0
  21. package/dist/render/fixtureAtlas.d.ts +8 -3
  22. package/dist/render/fixtureAtlas.js +17 -6
  23. package/dist/render/globalMedium.d.ts +17 -0
  24. package/dist/render/globalMedium.js +14 -0
  25. package/dist/render/globalMediumPass.js +3 -2
  26. package/dist/render/iridescentOverlay.d.ts +78 -0
  27. package/dist/render/iridescentOverlay.js +102 -0
  28. package/dist/render/mesh.d.ts +24 -0
  29. package/dist/render/mesh.js +79 -0
  30. package/dist/render/pointSkin.d.ts +18 -0
  31. package/dist/render/pointSkin.js +50 -5
  32. package/dist/render/recon/mover.d.ts +3 -2
  33. package/dist/render/recon/mover.js +3 -2
  34. package/dist/render/sceneTarget.d.ts +2 -0
  35. package/dist/render/sceneTarget.js +1 -1
  36. package/dist/render/shaders/flat/baseDecals.d.ts +17 -0
  37. package/dist/render/shaders/flat/baseDecals.js +71 -0
  38. package/dist/render/shaders/flat/emissiveFlow.d.ts +15 -0
  39. package/dist/render/shaders/flat/emissiveFlow.js +45 -0
  40. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  41. package/dist/render/shaders/flat/glassTint.js +3 -0
  42. package/dist/render/shaders/flat/index.d.ts +16 -0
  43. package/dist/render/shaders/flat/index.js +9 -0
  44. package/dist/render/shaders/flat/iridescentOverlay.d.ts +15 -0
  45. package/dist/render/shaders/flat/iridescentOverlay.js +65 -0
  46. package/dist/render/shaders/flat/layered.js +4 -4
  47. package/dist/render/shaders/flat/main.d.ts +1 -1
  48. package/dist/render/shaders/flat/main.js +12 -1
  49. package/dist/render/shaders/flat/mapReads.d.ts +1 -1
  50. package/dist/render/shaders/flat/mapReads.js +7 -3
  51. package/dist/render/shaders/flat/worldUv.js +6 -5
  52. package/dist/render/shaders/generated/flat.wgsl.d.ts +480 -16
  53. package/dist/render/shaders/generated/flat.wgsl.js +519 -55
  54. package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +15 -5
  55. package/dist/render/shaders/generated/temporalResolve.wgsl.js +15 -5
  56. package/dist/render/shaders/temporalResolve.js +25 -9
  57. package/dist/render/surfaceTexture.d.ts +32 -0
  58. package/dist/render/temporalPass.d.ts +3 -1
  59. package/dist/render/temporalPass.js +9 -1
  60. package/package.json +6 -6
  61. package/src/index.ts +3 -0
  62. package/src/render/backend/api.ts +19 -2
  63. package/src/render/backend/webgl2/motionTarget.ts +299 -0
  64. package/src/render/backend/webgl2/pointSkinFeedback.ts +49 -14
  65. package/src/render/backend/webgl2/renderer.ts +411 -9
  66. package/src/render/backend/webgpu/pointSkinPass.ts +90 -23
  67. package/src/render/backend/webgpu/renderer.ts +163 -3
  68. package/src/render/baseDecals.ts +93 -0
  69. package/src/render/emissiveFlow.ts +66 -0
  70. package/src/render/fixtureAtlas.ts +26 -7
  71. package/src/render/globalMedium.ts +31 -0
  72. package/src/render/globalMediumPass.ts +3 -7
  73. package/src/render/iridescentOverlay.ts +177 -0
  74. package/src/render/mesh.ts +82 -0
  75. package/src/render/pointSkin.ts +64 -5
  76. package/src/render/recon/mover.ts +3 -2
  77. package/src/render/sceneTarget.ts +3 -0
  78. package/src/render/shaders/flat/baseDecals.ts +71 -0
  79. package/src/render/shaders/flat/emissiveFlow.ts +45 -0
  80. package/src/render/shaders/flat/glassTint.ts +7 -1
  81. package/src/render/shaders/flat/index.ts +25 -0
  82. package/src/render/shaders/flat/iridescentOverlay.ts +65 -0
  83. package/src/render/shaders/flat/layered.ts +4 -4
  84. package/src/render/shaders/flat/main.ts +12 -1
  85. package/src/render/shaders/flat/mapReads.ts +7 -3
  86. package/src/render/shaders/flat/worldUv.ts +6 -5
  87. package/src/render/shaders/generated/flat.wgsl.ts +519 -55
  88. package/src/render/shaders/generated/temporalResolve.wgsl.ts +15 -5
  89. package/src/render/shaders/temporalResolve.ts +25 -9
  90. package/src/render/surfaceTexture.ts +32 -0
  91. package/src/render/temporalPass.ts +8 -0
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Where every mover was last frame, on WebGL2: the motion its temporal resolve follows.
3
+ *
4
+ * **What it is for.** The resolve reprojects last frame's picture by the camera's motion, read
5
+ * back from the depth, which is exact for a world standing still and wrong for whatever moved: a
6
+ * head turning or a boot in a kick leaves a trail of its earlier pixels. WebGPU's reconstruction
7
+ * follows each mover's own motion through `shaders/recon/motion.wgsl.ts`; this is the same idea
8
+ * for the other backend. The frame's movers are recorded as they are drawn and drawn again at the
9
+ * end of the frame into a target of their own, each texel saying where the surface it shows stood
10
+ * last frame: the offset to it on screen, its distance from last frame's eye, and that it is a
11
+ * mover's. The resolve reads it where a mover's texel is and the camera's reprojection everywhere
12
+ * else.
13
+ *
14
+ * **Three kinds**, as `recordDrawMotion` on WebGPU decides them: a rigid mesh under last frame's
15
+ * model; a skinned one under last frame's palette as well; and a mesh rewritten in place, whose
16
+ * last frame's positions `Mesh.keepPrevious` copied before the first rewrite of the frame. A cloth
17
+ * draw and an instanced batch keep the camera's motion here, which the renderer says once.
18
+ *
19
+ * **Drawn against the frame's own depth**, attached and never written, with the test passing at its
20
+ * own surface and a nudge toward the eye to forgive the last bit of arithmetic: rasterised through
21
+ * the frame's jittered matrix so it covers the pixels the surface did, its motion taken between the
22
+ * unjittered ones. **What it gives up**: a mover hidden behind glass or seen in a mirror keeps the
23
+ * camera's motion; a multisampled frame keeps it everywhere; and a morph target's change of shape
24
+ * is not followed.
25
+ */
26
+ import { MORPH_DELTA_TEXTURE_UNIT, SKIN_PALETTE_TEXTURE_UNIT } from '../../lightBudget.ts';
27
+ import { compileProgram, uniformLocations } from '../../shader.ts';
28
+ import { SkinPaletteTexture } from './skinPaletteTexture.ts';
29
+ import type { Mesh } from '../../mesh.ts';
30
+
31
+ const RIGID = 0;
32
+ const SKINNED = 1;
33
+ const DYNAMIC = 2;
34
+ type Kind = typeof RIGID | typeof SKINNED | typeof DYNAMIC;
35
+
36
+ /** The vertex stage for one kind, skinned with eight influences where `eight` says. */
37
+ function vertexSource(kind: Kind, eight: boolean): string {
38
+ return /* glsl */ `#version 300 es
39
+ precision highp float;
40
+ precision highp int;
41
+ layout(location = 0) in vec3 aPosition;
42
+ layout(location = 1) in vec3 aWas;
43
+ layout(location = 11) in vec4 aJoints;
44
+ layout(location = 12) in vec4 aWeights;
45
+ layout(location = 14) in vec4 aJoints2;
46
+ layout(location = 15) in vec4 aWeights2;
47
+ uniform mat4 uModel;
48
+ uniform mat4 uWasModel;
49
+ uniform mat4 uJittered;
50
+ uniform mat4 uViewProj;
51
+ uniform mat4 uWasViewProj;
52
+ uniform highp sampler2D uPaletteNow;
53
+ uniform highp sampler2D uPaletteWas;
54
+ out vec4 vNow;
55
+ out vec4 vWas;
56
+
57
+ mat4 joint(highp sampler2D palette, float index) {
58
+ int x = int(index) * 4;
59
+ return mat4(
60
+ texelFetch(palette, ivec2(x, 0), 0),
61
+ texelFetch(palette, ivec2(x + 1, 0), 0),
62
+ texelFetch(palette, ivec2(x + 2, 0), 0),
63
+ texelFetch(palette, ivec2(x + 3, 0), 0));
64
+ }
65
+
66
+ /* The same blend as skinning.ts, joint by joint, so a skinned mover lands where its draw did. */
67
+ mat4 skin(highp sampler2D palette) {
68
+ mat4 m = joint(palette, aJoints.x) * aWeights.x + joint(palette, aJoints.y) * aWeights.y
69
+ + joint(palette, aJoints.z) * aWeights.z + joint(palette, aJoints.w) * aWeights.w;
70
+ ${
71
+ eight
72
+ ? `m += joint(palette, aJoints2.x) * aWeights2.x + joint(palette, aJoints2.y) * aWeights2.y
73
+ + joint(palette, aJoints2.z) * aWeights2.z + joint(palette, aJoints2.w) * aWeights2.w;`
74
+ : ''
75
+ }
76
+ return m;
77
+ }
78
+
79
+ void main() {
80
+ vec4 now = vec4(aPosition, 1.0);
81
+ vec4 was = now;
82
+ ${
83
+ kind === SKINNED
84
+ ? 'was = skin(uPaletteWas) * now;\n now = skin(uPaletteNow) * now;'
85
+ : kind === DYNAMIC
86
+ ? 'was = vec4(aWas, 1.0);'
87
+ : ''
88
+ }
89
+ vec4 world = uModel * now;
90
+ gl_Position = uJittered * world;
91
+ vNow = uViewProj * world;
92
+ vWas = uWasViewProj * (uWasModel * was);
93
+ }
94
+ `;
95
+ }
96
+
97
+ const FRAGMENT = /* glsl */ `#version 300 es
98
+ precision highp float;
99
+ in vec4 vNow;
100
+ in vec4 vWas;
101
+ out vec4 motion;
102
+ /* The offset on screen to where this surface stood, its distance from last frame's eye, and 1. */
103
+ void main() {
104
+ vec2 now = vNow.xy / vNow.w * 0.5 + 0.5;
105
+ vec2 was = vWas.xy / max(vWas.w, 1e-6) * 0.5 + 0.5;
106
+ motion = vec4(was - now, vWas.w, 1.0);
107
+ }
108
+ `;
109
+
110
+ interface Program {
111
+ readonly program: WebGLProgram;
112
+ readonly uniforms: Record<string, WebGLUniformLocation>;
113
+ }
114
+
115
+ export class GlMotionPass {
116
+ private readonly programs = new Map<number, Program>();
117
+ private texture: WebGLTexture | null = null;
118
+ private framebuffer: WebGLFramebuffer | null = null;
119
+ private width = 0;
120
+ private height = 0;
121
+ private depth: WebGLTexture | null = null;
122
+ /** This frame's palette and last frame's, uploaded record by record. */
123
+ private readonly paletteNow = new SkinPaletteTexture();
124
+ private readonly paletteWas = new SkinPaletteTexture();
125
+
126
+ /** The frame's movers: mesh, kind, two matrices, two palettes. Grown, never shrunk. */
127
+ private readonly meshes: Mesh[] = [];
128
+ private kinds = new Uint8Array(16);
129
+ private matrices = new Float32Array(16 * 32);
130
+ private readonly palettes: (Float32Array | null)[] = [];
131
+ private readonly wasPalettes: (Float32Array | null)[] = [];
132
+ private count = 0;
133
+
134
+ /** A frame states its movers afresh. */
135
+ reset(): void {
136
+ this.count = 0;
137
+ }
138
+
139
+ get recorded(): number {
140
+ return this.count;
141
+ }
142
+
143
+ /** A rigid mover under last frame's model. */
144
+ recordRigid(mesh: Mesh, model: ArrayLike<number>, wasModel: ArrayLike<number>): void {
145
+ this.take(mesh, RIGID, model, wasModel, null, null);
146
+ }
147
+
148
+ /** A skinned mover under last frame's model and palette. */
149
+ recordSkinned(
150
+ mesh: Mesh,
151
+ model: ArrayLike<number>,
152
+ wasModel: ArrayLike<number>,
153
+ palette: Float32Array,
154
+ wasPalette: Float32Array,
155
+ ): void {
156
+ this.take(mesh, SKINNED, model, wasModel, palette, wasPalette);
157
+ }
158
+
159
+ /** A mesh rewritten this frame, under last frame's positions and model. */
160
+ recordDynamic(mesh: Mesh, model: ArrayLike<number>, wasModel: ArrayLike<number>): void {
161
+ this.take(mesh, DYNAMIC, model, wasModel, null, null);
162
+ }
163
+
164
+ private take(
165
+ mesh: Mesh,
166
+ kind: Kind,
167
+ model: ArrayLike<number>,
168
+ wasModel: ArrayLike<number>,
169
+ palette: Float32Array | null,
170
+ wasPalette: Float32Array | null,
171
+ ): void {
172
+ const at = this.count;
173
+ if (at >= this.kinds.length) {
174
+ const kinds = new Uint8Array(this.kinds.length * 2);
175
+ kinds.set(this.kinds);
176
+ this.kinds = kinds;
177
+ const matrices = new Float32Array(this.matrices.length * 2);
178
+ matrices.set(this.matrices);
179
+ this.matrices = matrices;
180
+ }
181
+ this.meshes[at] = mesh;
182
+ this.kinds[at] = kind;
183
+ this.matrices.set(model, at * 32);
184
+ this.matrices.set(wasModel, at * 32 + 16);
185
+ this.palettes[at] = palette;
186
+ this.wasPalettes[at] = wasPalette;
187
+ this.count = at + 1;
188
+ }
189
+
190
+ /**
191
+ * Draw the recorded movers into the motion target against `depth`, and return the target; null
192
+ * where nothing was recorded. Leaves the framebuffer, program and array unbound, the depth test on
193
+ * with the frame's compare and writes on, no offset, and the palette units holding its palettes.
194
+ */
195
+ draw(
196
+ gl: WebGL2RenderingContext,
197
+ depth: WebGLTexture,
198
+ width: number,
199
+ height: number,
200
+ reversedDepth: boolean,
201
+ jittered: Float32Array,
202
+ viewProj: Float32Array,
203
+ wasViewProj: Float32Array,
204
+ ): WebGLTexture | null {
205
+ if (this.count === 0) return null;
206
+ this.ensureTarget(gl, depth, width, height);
207
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
208
+ gl.viewport(0, 0, width, height);
209
+ gl.clearColor(0, 0, 0, 0);
210
+ gl.clear(gl.COLOR_BUFFER_BIT);
211
+ gl.enable(gl.DEPTH_TEST);
212
+ gl.depthMask(false);
213
+ gl.depthFunc(reversedDepth ? gl.GEQUAL : gl.LEQUAL);
214
+ /* Toward the eye, which is a larger depth when depth is reversed. */
215
+ gl.enable(gl.POLYGON_OFFSET_FILL);
216
+ gl.polygonOffset(reversedDepth ? 1 : -1, reversedDepth ? 4 : -4);
217
+ gl.disable(gl.BLEND);
218
+ gl.disable(gl.CULL_FACE);
219
+ for (let i = 0; i < this.count; i++) {
220
+ const mesh = this.meshes[i] as Mesh;
221
+ const kind = this.kinds[i] as Kind;
222
+ const eight = kind === SKINNED && mesh.isSkinnedEight;
223
+ const { program, uniforms } = this.programFor(gl, kind, eight);
224
+ gl.useProgram(program);
225
+ gl.uniformMatrix4fv(uniforms['uJittered'] ?? null, false, jittered);
226
+ gl.uniformMatrix4fv(uniforms['uViewProj'] ?? null, false, viewProj);
227
+ gl.uniformMatrix4fv(uniforms['uWasViewProj'] ?? null, false, wasViewProj);
228
+ gl.uniformMatrix4fv(uniforms['uModel'] ?? null, false, this.matrices, i * 32, 16);
229
+ gl.uniformMatrix4fv(uniforms['uWasModel'] ?? null, false, this.matrices, i * 32 + 16, 16);
230
+ if (kind === SKINNED) {
231
+ /* On the units a skinned draw binds its palette and morph on, which every one binds again
232
+ for itself: nothing the lit pass keeps across frames is moved. */
233
+ gl.activeTexture(gl.TEXTURE0 + SKIN_PALETTE_TEXTURE_UNIT);
234
+ this.paletteNow.update(gl, this.palettes[i] as Float32Array);
235
+ gl.activeTexture(gl.TEXTURE0 + MORPH_DELTA_TEXTURE_UNIT);
236
+ this.paletteWas.update(gl, this.wasPalettes[i] as Float32Array);
237
+ gl.uniform1i(uniforms['uPaletteNow'] ?? null, SKIN_PALETTE_TEXTURE_UNIT);
238
+ gl.uniform1i(uniforms['uPaletteWas'] ?? null, MORPH_DELTA_TEXTURE_UNIT);
239
+ }
240
+ mesh.drawMotion(gl, kind === DYNAMIC);
241
+ }
242
+ gl.disable(gl.POLYGON_OFFSET_FILL);
243
+ gl.polygonOffset(0, 0);
244
+ gl.depthMask(true);
245
+ gl.activeTexture(gl.TEXTURE0);
246
+ gl.useProgram(null);
247
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
248
+ return this.texture;
249
+ }
250
+
251
+ private programFor(gl: WebGL2RenderingContext, kind: Kind, eight: boolean): Program {
252
+ const key = kind * 2 + (eight ? 1 : 0);
253
+ const held = this.programs.get(key);
254
+ if (held !== undefined) return held;
255
+ const program = compileProgram(gl, vertexSource(kind, eight), FRAGMENT, `motion.${key}`);
256
+ const made = { program, uniforms: uniformLocations(gl, program, `motion.${key}`) };
257
+ this.programs.set(key, made);
258
+ return made;
259
+ }
260
+
261
+ /** An `RGBA16F` target the frame's size, its depth the frame's own attachment. */
262
+ private ensureTarget(
263
+ gl: WebGL2RenderingContext,
264
+ depth: WebGLTexture,
265
+ width: number,
266
+ height: number,
267
+ ): void {
268
+ if (this.texture !== null && this.width === width && this.height === height) {
269
+ if (this.depth === depth) return;
270
+ } else {
271
+ if (this.texture !== null) gl.deleteTexture(this.texture);
272
+ this.texture = gl.createTexture();
273
+ gl.bindTexture(gl.TEXTURE_2D, this.texture);
274
+ gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA16F, width, height);
275
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
276
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
277
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
278
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
279
+ gl.bindTexture(gl.TEXTURE_2D, null);
280
+ this.width = width;
281
+ this.height = height;
282
+ }
283
+ this.framebuffer ??= gl.createFramebuffer();
284
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
285
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0);
286
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depth, 0);
287
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
288
+ this.depth = depth;
289
+ }
290
+
291
+ dispose(gl: WebGL2RenderingContext): void {
292
+ for (const { program } of this.programs.values()) gl.deleteProgram(program);
293
+ this.programs.clear();
294
+ if (this.texture !== null) gl.deleteTexture(this.texture);
295
+ if (this.framebuffer !== null) gl.deleteFramebuffer(this.framebuffer);
296
+ this.texture = null;
297
+ this.framebuffer = null;
298
+ }
299
+ }
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import { POINT_SKIN_TEXTURE_UNIT } from '../../lightBudget.ts';
12
12
  import type { Mesh } from '../../mesh.ts';
13
- import { reachOf, type PointSkinData, type PointSkinReach } from '../../pointSkin.ts';
13
+ import { reachOf, widenReach, type PointSkinData, type PointSkinReach } from '../../pointSkin.ts';
14
14
  import { compileProgram } from '../../shader.ts';
15
15
 
16
16
  const POSE_VERT = /* glsl */ `#version 300 es
@@ -80,12 +80,14 @@ export function createPointSkinPose(gl: WebGL2RenderingContext): PointSkinPose {
80
80
 
81
81
  export class GlPointSkin {
82
82
  readonly mesh: Mesh;
83
- readonly reach: PointSkinReach;
83
+ /** Widened by each rewrite, never narrowed: see `widenReach`. */
84
+ reach: PointSkinReach;
84
85
  private readonly count: number;
85
86
  private readonly vao: WebGLVertexArrayObject;
87
+ /** Joints, weights, points and normals, in that order. */
86
88
  private readonly buffers: WebGLBuffer[] = [];
87
- private readonly joints: WebGLTexture;
88
- private readonly width: number;
89
+ private joints: WebGLTexture;
90
+ private width: number;
89
91
 
90
92
  constructor(gl: WebGL2RenderingContext, mesh: Mesh, data: PointSkinData) {
91
93
  this.mesh = mesh;
@@ -107,17 +109,37 @@ export class GlPointSkin {
107
109
  gl.bindVertexArray(null);
108
110
  gl.bindBuffer(gl.ARRAY_BUFFER, null);
109
111
 
110
- /* One row, four texels a joint, as the skin palette is. */
111
112
  this.width = Math.max(1, this.reach.joints * 4);
112
- const joints = gl.createTexture();
113
- if (joints === null) throw new Error('createTexture failed');
114
- this.joints = joints;
115
- gl.activeTexture(gl.TEXTURE0 + POINT_SKIN_TEXTURE_UNIT);
116
- gl.bindTexture(gl.TEXTURE_2D, joints);
117
- gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, this.width, 1);
118
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
119
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
120
- gl.bindTexture(gl.TEXTURE_2D, null);
113
+ this.joints = jointTexture(gl, this.width);
114
+ }
115
+
116
+ /**
117
+ * Rewrite vertices `first` onward, as many as `data` holds, in place: their four arrays sent at
118
+ * their offsets and nothing else. A command in the stream like a pose, so the pose after it reads
119
+ * the rewrite and one before it does not. A joint none of the skin named before grows the joint
120
+ * texture, which is made again for it; a skin settles at the joints its shapes name, so that is
121
+ * rare after its first changes.
122
+ */
123
+ rewrite(gl: WebGL2RenderingContext, first: number, data: PointSkinData): void {
124
+ const sent: readonly (readonly [Float32Array, number])[] = [
125
+ [data.joints, 16],
126
+ [data.weights, 16],
127
+ [data.points, 48],
128
+ [data.normals, 12],
129
+ ];
130
+ for (let at = 0; at < sent.length; at++) {
131
+ const [values, stride] = sent[at] as readonly [Float32Array, number];
132
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[at] as WebGLBuffer);
133
+ gl.bufferSubData(gl.ARRAY_BUFFER, first * stride, values);
134
+ }
135
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
136
+ this.reach = widenReach(this.reach, data);
137
+ const width = Math.max(1, this.reach.joints * 4);
138
+ if (width > this.width) {
139
+ gl.deleteTexture(this.joints);
140
+ this.width = width;
141
+ this.joints = jointTexture(gl, width);
142
+ }
121
143
  }
122
144
 
123
145
  /** One buffer, read as each `[location, size, stride, offset]` says. */
@@ -171,3 +193,16 @@ export class GlPointSkin {
171
193
  gl.deleteVertexArray(this.vao);
172
194
  }
173
195
  }
196
+
197
+ /** One row, four texels a joint, as the skin palette is. */
198
+ function jointTexture(gl: WebGL2RenderingContext, width: number): WebGLTexture {
199
+ const joints = gl.createTexture();
200
+ if (joints === null) throw new Error('createTexture failed');
201
+ gl.activeTexture(gl.TEXTURE0 + POINT_SKIN_TEXTURE_UNIT);
202
+ gl.bindTexture(gl.TEXTURE_2D, joints);
203
+ gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, width, 1);
204
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
205
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
206
+ gl.bindTexture(gl.TEXTURE_2D, null);
207
+ return joints;
208
+ }