@driftengine/core 4.9.0 → 4.10.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/CHANGELOG.json +62 -11
- package/README.md +43 -38
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -3
- package/dist/render/ambientHarmonics.d.ts +1 -1
- package/dist/render/ambientHarmonics.js +1 -1
- package/dist/render/areaLights.d.ts +4 -4
- package/dist/render/areaLights.js +1 -1
- package/dist/render/backend/api.d.ts +10 -3
- package/dist/render/backend/select.js +11 -5
- package/dist/render/backend/webgl2/boneAnimations.d.ts +29 -0
- package/dist/render/backend/webgl2/boneAnimations.js +80 -0
- package/dist/render/backend/webgl2/instanced.d.ts +5 -1
- package/dist/render/backend/webgl2/instanced.js +13 -1
- package/dist/render/backend/webgl2/renderer.d.ts +90 -2
- package/dist/render/backend/webgl2/renderer.js +253 -24
- package/dist/render/backend/webgl2/skinPaletteTexture.d.ts +5 -3
- package/dist/render/backend/webgl2/skinPaletteTexture.js +6 -4
- package/dist/render/backend/webgpu/boneAnimations.d.ts +34 -0
- package/dist/render/backend/webgpu/boneAnimations.js +76 -0
- package/dist/render/backend/webgpu/buffers.d.ts +19 -4
- package/dist/render/backend/webgpu/buffers.js +18 -11
- package/dist/render/backend/webgpu/compressedUpload.d.ts +0 -1
- package/dist/render/backend/webgpu/compressedUpload.js +53 -8
- package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
- package/dist/render/backend/webgpu/depthPass.js +49 -14
- package/dist/render/backend/webgpu/drawCommand.d.ts +18 -4
- package/dist/render/backend/webgpu/drawCommand.js +10 -3
- package/dist/render/backend/webgpu/flatPass.d.ts +85 -42
- package/dist/render/backend/webgpu/flatPass.js +136 -20
- package/dist/render/backend/webgpu/instanced.d.ts +14 -1
- package/dist/render/backend/webgpu/instanced.js +24 -1
- package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
- package/dist/render/backend/webgpu/materialStore.js +265 -0
- package/dist/render/backend/webgpu/pipelineCache.d.ts +6 -0
- package/dist/render/backend/webgpu/pipelineCache.js +7 -0
- package/dist/render/backend/webgpu/renderer.d.ts +152 -35
- package/dist/render/backend/webgpu/renderer.js +787 -142
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +7 -3
- package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
- package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
- package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
- package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
- package/dist/render/backend/webgpu/staticBundles.js +241 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
- package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
- package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
- package/dist/render/backend/webgpu/uniformRing.js +9 -1
- package/dist/render/bloomChain.d.ts +3 -3
- package/dist/render/boneAnimation.d.ts +92 -0
- package/dist/render/boneAnimation.js +133 -0
- package/dist/render/compressedSource.d.ts +33 -13
- package/dist/render/compressedSource.js +70 -25
- package/dist/render/glCompressed.js +83 -0
- package/dist/render/instances.d.ts +26 -0
- package/dist/render/lightBudget.d.ts +10 -0
- package/dist/render/lightBudget.js +10 -0
- package/dist/render/lightmap.d.ts +1 -1
- package/dist/render/mesh.d.ts +5 -0
- package/dist/render/mesh.js +6 -0
- package/dist/render/pointLightSelection.d.ts +4 -4
- package/dist/render/screenSpaceReflection.d.ts +2 -1
- package/dist/render/shaders/boneAnimation.d.ts +19 -0
- package/dist/render/shaders/boneAnimation.js +63 -0
- package/dist/render/shaders/depth.d.ts +7 -0
- package/dist/render/shaders/depth.js +35 -10
- package/dist/render/shaders/flat/index.d.ts +6 -0
- package/dist/render/shaders/flat/index.js +27 -5
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +15 -5
- package/dist/render/shaders/flat/models.js +1 -1
- package/dist/render/shaders/flat/overlay.js +1 -1
- package/dist/render/shaders/flat/pointShadow.js +2 -2
- package/dist/render/shaders/flat/preamble.js +38 -24
- package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
- package/dist/render/shaders/generated/depth.wgsl.js +93 -0
- package/dist/render/shaders/generated/flat.wgsl.d.ts +3077 -2903
- package/dist/render/shaders/generated/flat.wgsl.js +3100 -2925
- package/dist/render/shaders/outputTransform.d.ts +7 -0
- package/dist/render/shaders/outputTransform.js +28 -14
- package/dist/render/shaders/vertexChannel.d.ts +7 -0
- package/dist/render/shaders/vertexChannel.js +26 -0
- package/dist/render/staticDraws.d.ts +47 -0
- package/dist/render/staticDraws.js +43 -0
- package/dist/render/surfaceTexture.d.ts +38 -7
- package/dist/render/surfaceTexture.js +56 -17
- package/package.json +6 -6
- package/src/index.ts +8 -2
- package/src/render/ambientHarmonics.ts +1 -1
- package/src/render/areaLights.ts +4 -4
- package/src/render/backend/api.ts +13 -3
- package/src/render/backend/select.ts +10 -5
- package/src/render/backend/webgl2/boneAnimations.ts +105 -0
- package/src/render/backend/webgl2/instanced.ts +20 -1
- package/src/render/backend/webgl2/renderer.ts +315 -24
- package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
- package/src/render/backend/webgpu/boneAnimations.ts +97 -0
- package/src/render/backend/webgpu/buffers.ts +42 -16
- package/src/render/backend/webgpu/compressedUpload.ts +59 -9
- package/src/render/backend/webgpu/depthPass.ts +51 -10
- package/src/render/backend/webgpu/drawCommand.ts +22 -4
- package/src/render/backend/webgpu/flatPass.ts +187 -14
- package/src/render/backend/webgpu/instanced.ts +32 -1
- package/src/render/backend/webgpu/materialStore.ts +276 -0
- package/src/render/backend/webgpu/pipelineCache.ts +7 -0
- package/src/render/backend/webgpu/renderer.ts +928 -155
- package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
- package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
- package/src/render/backend/webgpu/staticBundles.ts +363 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
- package/src/render/backend/webgpu/uniformRing.ts +10 -1
- package/src/render/bloomChain.ts +3 -3
- package/src/render/boneAnimation.ts +223 -0
- package/src/render/compressedSource.ts +121 -29
- package/src/render/glCompressed.ts +83 -0
- package/src/render/instances.ts +27 -0
- package/src/render/lightBudget.ts +11 -0
- package/src/render/lightmap.ts +1 -1
- package/src/render/mesh.ts +6 -0
- package/src/render/pointLightSelection.ts +4 -4
- package/src/render/screenSpaceReflection.ts +2 -1
- package/src/render/shaders/boneAnimation.ts +63 -0
- package/src/render/shaders/depth.ts +45 -10
- package/src/render/shaders/flat/index.ts +33 -5
- package/src/render/shaders/flat/main.ts +15 -5
- package/src/render/shaders/flat/models.ts +1 -1
- package/src/render/shaders/flat/overlay.ts +1 -1
- package/src/render/shaders/flat/pointShadow.ts +2 -2
- package/src/render/shaders/flat/preamble.ts +38 -24
- package/src/render/shaders/generated/depth.wgsl.ts +95 -0
- package/src/render/shaders/generated/flat.wgsl.ts +3100 -2925
- package/src/render/shaders/outputTransform.ts +30 -14
- package/src/render/shaders/vertexChannel.ts +27 -0
- package/src/render/staticDraws.ts +95 -0
- package/src/render/surfaceTexture.ts +94 -27
|
@@ -218,6 +218,8 @@ import {
|
|
|
218
218
|
DEPTH_CUTOUT_FRAG,
|
|
219
219
|
DEPTH_CUTOUT_VERT,
|
|
220
220
|
DEPTH_FRAG,
|
|
221
|
+
DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT,
|
|
222
|
+
DEPTH_INSTANCED_ANIMATED_VERT,
|
|
221
223
|
DEPTH_INSTANCED_CUTOUT_VERT,
|
|
222
224
|
DEPTH_INSTANCED_VERT,
|
|
223
225
|
DEPTH_SKINNED_VERT,
|
|
@@ -232,6 +234,8 @@ import { GlassCasterList, type GlassReplaySink } from '../../glassCasters.ts';
|
|
|
232
234
|
import { SunGlassTint } from '../../sunGlassTint.ts';
|
|
233
235
|
import { GlassTintPrograms, type GlassTintProgram } from './glassTintPrograms.ts';
|
|
234
236
|
import type { SceneCasterMaterial, ShadowCasterSink, ShadowCasters } from '../../shadowCasters.ts';
|
|
237
|
+
import { captureStaticDraws, replayStaticDraws } from '../../staticDraws.ts';
|
|
238
|
+
import type { StaticDrawsHandle, StaticDrawsList } from '../../staticDraws.ts';
|
|
235
239
|
import { FACE_COUNT, PointShadowMap, type PointGlassDraw } from '../../pointShadowMap.ts';
|
|
236
240
|
import { createResolvedPointShadows, DEFAULT_SOURCE_RADIUS } from '../../pointShadowImage.ts';
|
|
237
241
|
import {
|
|
@@ -277,7 +281,11 @@ import { SurfaceTexture } from '../../surfaceTexture.ts';
|
|
|
277
281
|
import { sceneCaptureTexels } from '../../sceneCapture.ts';
|
|
278
282
|
import { GlSceneCaptureTarget } from './sceneCaptureTarget.ts';
|
|
279
283
|
import { glCompressedFormats } from '../../glCompressed.ts';
|
|
280
|
-
import type {
|
|
284
|
+
import type {
|
|
285
|
+
CompressedTextureFormat,
|
|
286
|
+
CompressedTextureSource,
|
|
287
|
+
SurfaceSource,
|
|
288
|
+
} from '../../compressedSource.ts';
|
|
281
289
|
import type { SurfaceMaterial, SurfaceTextureOptions } from '../../surfaceTexture.ts';
|
|
282
290
|
import { PlumeRenderer } from '../../plumeRenderer.ts';
|
|
283
291
|
import { createScatterDeform, resolveScatterDeform } from '../../scatterDeform.ts';
|
|
@@ -335,6 +343,14 @@ import type { WindStreakOptions } from '../../windStreakRenderer.ts';
|
|
|
335
343
|
import type { WindField } from '../../windField.ts';
|
|
336
344
|
import type { FlockParams } from '../../flockRenderer.ts';
|
|
337
345
|
import { InstancedBatch } from './instanced.ts';
|
|
346
|
+
import { GlBoneAnimation } from './boneAnimations.ts';
|
|
347
|
+
import { packBoneAnimation } from '../../boneAnimation.ts';
|
|
348
|
+
import type { BoneAnimationClip, BoneAnimationHandle } from '../../boneAnimation.ts';
|
|
349
|
+
import {
|
|
350
|
+
BONE_PLACES_TEXTURE_UNIT,
|
|
351
|
+
BONE_TURNS_TEXTURE_UNIT,
|
|
352
|
+
INSTANCE_CLOCKS_TEXTURE_UNIT,
|
|
353
|
+
} from '../../lightBudget.ts';
|
|
338
354
|
import type { InstancedOptions, MeshInstances } from '../../instances.ts';
|
|
339
355
|
import { cutoutOf } from '../../cutoutCaster.ts';
|
|
340
356
|
import { CUTOUT_RESOLVE_CODE, resolveCutout } from '../../cutoutDither.ts';
|
|
@@ -1582,6 +1598,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1582
1598
|
private readonly depthCutoutUniforms: Record<string, WebGLUniformLocation>;
|
|
1583
1599
|
private readonly depthInstancedCutoutProgram: WebGLProgram;
|
|
1584
1600
|
private readonly depthInstancedCutoutUniforms: Record<string, WebGLUniformLocation>;
|
|
1601
|
+
/** A crowd's casters: the instanced depth programs that play a bone animation. */
|
|
1602
|
+
private readonly depthAnimatedProgram: WebGLProgram;
|
|
1603
|
+
private readonly depthAnimatedUniforms: Record<string, WebGLUniformLocation>;
|
|
1604
|
+
private readonly depthAnimatedCutoutProgram: WebGLProgram;
|
|
1605
|
+
private readonly depthAnimatedCutoutUniforms: Record<string, WebGLUniformLocation>;
|
|
1585
1606
|
private readonly scatterDepthProgram: WebGLProgram;
|
|
1586
1607
|
private readonly scatterDepthUniforms: Record<string, WebGLUniformLocation>;
|
|
1587
1608
|
/** The sun's static, moving and peeled maps, as layers of one array. See `shadowMap.ts`. */
|
|
@@ -2002,24 +2023,33 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2002
2023
|
const gpuBatch = batch as InstancedBatch;
|
|
2003
2024
|
const count = Math.min(data.count, gpuBatch.capacity);
|
|
2004
2025
|
if (count === 0) return;
|
|
2026
|
+
const clip = gpuBatch.animation;
|
|
2027
|
+
if (clip !== null && clip.disposed) return;
|
|
2005
2028
|
this.shadowDrawBudget.ask();
|
|
2006
2029
|
const twoSided = material?.doubleSided === true;
|
|
2007
2030
|
if (twoSided) gl.disable(gl.CULL_FACE);
|
|
2008
2031
|
const cutout = cutoutOf(material);
|
|
2009
2032
|
if (cutout !== null) {
|
|
2010
|
-
|
|
2011
|
-
|
|
2033
|
+
/* A crowd casts where its clip has moved it, through the animated variant. */
|
|
2034
|
+
const uniforms =
|
|
2035
|
+
clip === null ? this.depthInstancedCutoutUniforms : this.depthAnimatedCutoutUniforms;
|
|
2036
|
+
gl.useProgram(
|
|
2037
|
+
clip === null ? this.depthInstancedCutoutProgram : this.depthAnimatedCutoutProgram,
|
|
2038
|
+
);
|
|
2039
|
+
this.bindCutoutDepth(uniforms, cutout);
|
|
2040
|
+
if (clip !== null) this.bindAnimation(uniforms, gpuBatch);
|
|
2012
2041
|
gpuBatch.draw(gl, count);
|
|
2013
2042
|
if (twoSided && this.depthPassCullsFaces) gl.enable(gl.CULL_FACE);
|
|
2014
2043
|
gl.useProgram(this.depthProgram);
|
|
2015
2044
|
this.restoreSurfaceTextureUnit();
|
|
2016
2045
|
return;
|
|
2017
2046
|
}
|
|
2018
|
-
const u = this.depthInstancedUniforms;
|
|
2019
|
-
gl.useProgram(this.depthInstancedProgram);
|
|
2047
|
+
const u = clip === null ? this.depthInstancedUniforms : this.depthAnimatedUniforms;
|
|
2048
|
+
gl.useProgram(clip === null ? this.depthInstancedProgram : this.depthAnimatedProgram);
|
|
2020
2049
|
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, this.activeDepthViewProj);
|
|
2021
2050
|
gl.uniform1i(u['uPeelShadowLayer'] ?? null, this.activeDepthPeel ? 1 : 0);
|
|
2022
2051
|
if (this.activeDepthPeel) gl.uniform1i(u['uPreviousShadowMap'] ?? null, 0);
|
|
2052
|
+
if (clip !== null) this.bindAnimation(u, gpuBatch);
|
|
2023
2053
|
gpuBatch.draw(gl, count);
|
|
2024
2054
|
if (twoSided && this.depthPassCullsFaces) gl.enable(gl.CULL_FACE);
|
|
2025
2055
|
gl.useProgram(this.depthProgram);
|
|
@@ -3182,10 +3212,33 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3182
3212
|
this.depthInstancedCutoutProgram,
|
|
3183
3213
|
'depth-instanced-cutout',
|
|
3184
3214
|
);
|
|
3215
|
+
this.depthAnimatedProgram = compileProgram(
|
|
3216
|
+
gl,
|
|
3217
|
+
DEPTH_INSTANCED_ANIMATED_VERT,
|
|
3218
|
+
DEPTH_FRAG,
|
|
3219
|
+
'depth-instanced-animated',
|
|
3220
|
+
);
|
|
3221
|
+
this.depthAnimatedUniforms = uniformLocations(
|
|
3222
|
+
gl,
|
|
3223
|
+
this.depthAnimatedProgram,
|
|
3224
|
+
'depth-instanced-animated',
|
|
3225
|
+
);
|
|
3226
|
+
this.depthAnimatedCutoutProgram = compileProgram(
|
|
3227
|
+
gl,
|
|
3228
|
+
DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT,
|
|
3229
|
+
DEPTH_CUTOUT_FRAG,
|
|
3230
|
+
'depth-instanced-animated-cutout',
|
|
3231
|
+
);
|
|
3232
|
+
this.depthAnimatedCutoutUniforms = uniformLocations(
|
|
3233
|
+
gl,
|
|
3234
|
+
this.depthAnimatedCutoutProgram,
|
|
3235
|
+
'depth-instanced-animated-cutout',
|
|
3236
|
+
);
|
|
3185
3237
|
/* The cutout map is always read from the surface unit, which the draw borrows and gives back. */
|
|
3186
3238
|
for (const [program, uniforms] of [
|
|
3187
3239
|
[this.depthCutoutProgram, this.depthCutoutUniforms],
|
|
3188
3240
|
[this.depthInstancedCutoutProgram, this.depthInstancedCutoutUniforms],
|
|
3241
|
+
[this.depthAnimatedCutoutProgram, this.depthAnimatedCutoutUniforms],
|
|
3189
3242
|
] as const) {
|
|
3190
3243
|
gl.useProgram(program);
|
|
3191
3244
|
gl.uniform1i(uniforms['uCutoutMap'] ?? null, SURFACE_TEXTURE_UNIT);
|
|
@@ -3379,6 +3432,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3379
3432
|
this.lightVolumeProgram,
|
|
3380
3433
|
this.depthCutoutProgram,
|
|
3381
3434
|
this.depthInstancedCutoutProgram,
|
|
3435
|
+
this.depthAnimatedProgram,
|
|
3436
|
+
this.depthAnimatedCutoutProgram,
|
|
3382
3437
|
]) {
|
|
3383
3438
|
gl.deleteProgram(program);
|
|
3384
3439
|
}
|
|
@@ -3725,9 +3780,12 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3725
3780
|
*
|
|
3726
3781
|
* Not a hot path — it re-uploads the whole image and regenerates the mip chain.
|
|
3727
3782
|
*/
|
|
3728
|
-
updateSurfaceTexture(
|
|
3783
|
+
updateSurfaceTexture(
|
|
3784
|
+
texture: SurfaceTexture,
|
|
3785
|
+
source: TexImageSource | CompressedTextureSource,
|
|
3786
|
+
): void {
|
|
3729
3787
|
if (this.contextLost) return;
|
|
3730
|
-
texture.update(this.gl, source);
|
|
3788
|
+
texture.update(this.gl, source, this.compressedFormats);
|
|
3731
3789
|
/* New pixels are a new copy for the overlay's slot: see `SurfaceTexture.flatLayer`. */
|
|
3732
3790
|
if (texture === this.overlayMaps) this.bindOverlayMaps(texture);
|
|
3733
3791
|
}
|
|
@@ -3964,6 +4022,13 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3964
4022
|
this.materialDoubleSided = material?.doubleSided === true;
|
|
3965
4023
|
this.materialHasMaps = materialHasMaps(material);
|
|
3966
4024
|
this.currentMaterial = material;
|
|
4025
|
+
const reflectivity = material?.reflectivity;
|
|
4026
|
+
const environmentGain = material?.environmentGain;
|
|
4027
|
+
this.materialReflectivity =
|
|
4028
|
+
reflectivity === undefined ? null : Math.min(1, Math.max(0, reflectivity));
|
|
4029
|
+
this.materialEnvironmentGain =
|
|
4030
|
+
environmentGain === undefined ? null : Math.max(0, environmentGain);
|
|
4031
|
+
this.writeEnvironmentDials();
|
|
3967
4032
|
/* What the blur spreads this skin by: the last material to name a profile says. */
|
|
3968
4033
|
const model = material?.model;
|
|
3969
4034
|
warnUnpagedLightmap(model, material?.modelMap);
|
|
@@ -4011,7 +4076,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4011
4076
|
const v = this.cutoutVector;
|
|
4012
4077
|
v[0] = material?.cutout ?? 0;
|
|
4013
4078
|
v[1] = CUTOUT_RESOLVE_CODE[this.cutoutResolveNow(material, translucent)];
|
|
4014
|
-
|
|
4079
|
+
/* z unused: the dither's frame is the pass's, in `uHighlightMin.w`. See `uCutout`. */
|
|
4080
|
+
v[2] = 0;
|
|
4015
4081
|
/* The material's lighting channels, which the lamps' are compared with. See `uCutout`. */
|
|
4016
4082
|
v[3] = lightChannelsOf(material?.lightChannels);
|
|
4017
4083
|
return v;
|
|
@@ -4225,8 +4291,24 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4225
4291
|
*/
|
|
4226
4292
|
setSurfaceReflectivity(amount: number): void {
|
|
4227
4293
|
if (this.contextLost) return;
|
|
4294
|
+
this.passReflectivity = Math.min(1, Math.max(0, amount));
|
|
4295
|
+
this.writeEnvironmentDials();
|
|
4296
|
+
}
|
|
4297
|
+
|
|
4298
|
+
/**
|
|
4299
|
+
* The reflectivity and environment gain the setters gave the pass, and the ones the material
|
|
4300
|
+
* set now states, or null where it states none (`SurfaceMaterial.reflectivity`). A draw wears the
|
|
4301
|
+
* material's where it has one and the pass's otherwise.
|
|
4302
|
+
*/
|
|
4303
|
+
private passReflectivity = 0;
|
|
4304
|
+
private passEnvironmentGain = 1;
|
|
4305
|
+
private materialReflectivity: number | null = null;
|
|
4306
|
+
private materialEnvironmentGain: number | null = null;
|
|
4307
|
+
|
|
4308
|
+
private writeEnvironmentDials(): void {
|
|
4228
4309
|
this.materials.dirty();
|
|
4229
|
-
this.environmentDials[1] =
|
|
4310
|
+
this.environmentDials[1] = this.materialReflectivity ?? this.passReflectivity;
|
|
4311
|
+
this.environmentDials[2] = this.materialEnvironmentGain ?? this.passEnvironmentGain;
|
|
4230
4312
|
this.writeFlatVector('uEnvironmentDials', this.environmentDials);
|
|
4231
4313
|
}
|
|
4232
4314
|
|
|
@@ -4314,9 +4396,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4314
4396
|
*/
|
|
4315
4397
|
setEnvironmentGain(gain: number): void {
|
|
4316
4398
|
if (this.contextLost) return;
|
|
4317
|
-
this.
|
|
4318
|
-
this.
|
|
4319
|
-
this.writeFlatVector('uEnvironmentDials', this.environmentDials);
|
|
4399
|
+
this.passEnvironmentGain = Math.max(0, gain);
|
|
4400
|
+
this.writeEnvironmentDials();
|
|
4320
4401
|
}
|
|
4321
4402
|
|
|
4322
4403
|
/**
|
|
@@ -4599,8 +4680,104 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4599
4680
|
'locations the joint indices and weights occupy, and this mesh supplies them.',
|
|
4600
4681
|
);
|
|
4601
4682
|
}
|
|
4683
|
+
const animation = (options.animation ?? null) as GlBoneAnimation | null;
|
|
4684
|
+
/* The bone a vertex follows is its second coordinates' u, which a mesh carries in its grain
|
|
4685
|
+
lane; a mesh with none has nothing to name a bone by. See `boneAnimation.ts`. */
|
|
4686
|
+
if (animation !== null && !mesh.hasGrain) {
|
|
4687
|
+
throw new Error(
|
|
4688
|
+
'WebGL2: a bone animation reads each vertex’s bone from the mesh’s second coordinates ' +
|
|
4689
|
+
'(`MeshData.lightmapUvs`), and this mesh has none.',
|
|
4690
|
+
);
|
|
4691
|
+
}
|
|
4602
4692
|
this.ensureInstancedProgram();
|
|
4603
|
-
|
|
4693
|
+
if (animation !== null) this.ensureAnimatedProgram();
|
|
4694
|
+
return new InstancedBatch(this.gl, mesh, capacity, options.cull ?? false, animation);
|
|
4695
|
+
}
|
|
4696
|
+
|
|
4697
|
+
/**
|
|
4698
|
+
* A bone animation an instanced batch can play (`InstancedOptions.animation`): per frame, a turn
|
|
4699
|
+
* and a place for each bone, which every vertex of the batch's mesh follows — the bone its second
|
|
4700
|
+
* coordinates' u names, times `boneScale` — at each instance's own moment of the clip. What a
|
|
4701
|
+
* crowd is made of: every instance moves at the frame's rate on its own clock, in the colour pass
|
|
4702
|
+
* and in every shadow, with nothing on the processor after this. See `boneAnimation.ts` for the
|
|
4703
|
+
* clip's layout and the arithmetic, which both backends run.
|
|
4704
|
+
*
|
|
4705
|
+
* What it gives up against skinning is the blend: a vertex follows one bone. Refused, with what
|
|
4706
|
+
* was wrong, for a clip whose numbers are not its size, or past 2,048 bones or frames.
|
|
4707
|
+
*/
|
|
4708
|
+
createBoneAnimation(clip: BoneAnimationClip): BoneAnimationHandle {
|
|
4709
|
+
return new GlBoneAnimation(this.gl, packBoneAnimation(clip));
|
|
4710
|
+
}
|
|
4711
|
+
|
|
4712
|
+
/** Release a clip. A batch still playing it draws nothing, and casts nothing, after. */
|
|
4713
|
+
disposeBoneAnimation(animation: BoneAnimationHandle): void {
|
|
4714
|
+
const clip = animation as GlBoneAnimation;
|
|
4715
|
+
if (this.contextLost || clip.disposed) return;
|
|
4716
|
+
clip.dispose(this.gl);
|
|
4717
|
+
}
|
|
4718
|
+
|
|
4719
|
+
/**
|
|
4720
|
+
* The clock a bone animation plays by this frame, in seconds on the caller's clock: an instance's
|
|
4721
|
+
* moment is this times its rate plus its phase (`MeshInstances.clocks`). **Set it before the
|
|
4722
|
+
* shadows are drawn**, once a frame, as the wind is set: a shadow drawn under one clock and its
|
|
4723
|
+
* caster under another come apart at the frame's rate. 0 until set, which holds every crowd at
|
|
4724
|
+
* the moment its phases name.
|
|
4725
|
+
*/
|
|
4726
|
+
setAnimationTime(seconds: number): void {
|
|
4727
|
+
if (Number.isFinite(seconds)) this.animationTime = seconds;
|
|
4728
|
+
}
|
|
4729
|
+
|
|
4730
|
+
/** The clock `setAnimationTime` set. */
|
|
4731
|
+
private animationTime = 0;
|
|
4732
|
+
|
|
4733
|
+
/**
|
|
4734
|
+
* An animated batch's clip, clocks and numbers into whichever program is bound: the three vertex
|
|
4735
|
+
* textures on units of their own (`lightBudget.ts`), the clip's speed and bone scale, the clock.
|
|
4736
|
+
*/
|
|
4737
|
+
private bindAnimation(u: Record<string, WebGLUniformLocation>, batch: InstancedBatch): void {
|
|
4738
|
+
const clip = batch.animation;
|
|
4739
|
+
const clocks = batch.clocks;
|
|
4740
|
+
if (clip === null || clocks === null) return;
|
|
4741
|
+
const { gl } = this;
|
|
4742
|
+
gl.activeTexture(gl.TEXTURE0 + BONE_PLACES_TEXTURE_UNIT);
|
|
4743
|
+
gl.bindTexture(gl.TEXTURE_2D, clip.places);
|
|
4744
|
+
gl.activeTexture(gl.TEXTURE0 + BONE_TURNS_TEXTURE_UNIT);
|
|
4745
|
+
gl.bindTexture(gl.TEXTURE_2D, clip.turns);
|
|
4746
|
+
gl.activeTexture(gl.TEXTURE0 + INSTANCE_CLOCKS_TEXTURE_UNIT);
|
|
4747
|
+
gl.bindTexture(gl.TEXTURE_2D, clocks.texture);
|
|
4748
|
+
gl.uniform1i(u['uBonePlaces'] ?? null, BONE_PLACES_TEXTURE_UNIT);
|
|
4749
|
+
gl.uniform1i(u['uBoneTurns'] ?? null, BONE_TURNS_TEXTURE_UNIT);
|
|
4750
|
+
gl.uniform1i(u['uInstanceClocks'] ?? null, INSTANCE_CLOCKS_TEXTURE_UNIT);
|
|
4751
|
+
gl.uniform2f(u['uBoneClip'] ?? null, clip.framesPerSecond, clip.boneScale);
|
|
4752
|
+
gl.uniform1f(u['uSceneTime'] ?? null, this.animationTime);
|
|
4753
|
+
}
|
|
4754
|
+
|
|
4755
|
+
/** The animated instanced program, compiled on first use. See `ensureInstancedProgram`. */
|
|
4756
|
+
private flatAnimatedProgram: WebGLProgram | null = null;
|
|
4757
|
+
private flatAnimatedUniforms: Record<string, WebGLUniformLocation> | null = null;
|
|
4758
|
+
|
|
4759
|
+
/** Compile the animated variant, once, and feed it this frame's pass state. */
|
|
4760
|
+
private ensureAnimatedProgram(): void {
|
|
4761
|
+
if (this.contextLost || this.flatAnimatedProgram !== null) return;
|
|
4762
|
+
const { gl } = this;
|
|
4763
|
+
const program = compileProgram(
|
|
4764
|
+
gl,
|
|
4765
|
+
flatVert({ skinned: false, morphed: false, instanced: true, animated: true }),
|
|
4766
|
+
this.flatFragSource,
|
|
4767
|
+
'flat.animated',
|
|
4768
|
+
);
|
|
4769
|
+
const uniforms = uniformLocations(gl, program, 'flat.animated');
|
|
4770
|
+
this.flatAnimatedProgram = program;
|
|
4771
|
+
this.flatAnimatedUniforms = uniforms;
|
|
4772
|
+
this.flatTargets.push({ program, uniforms });
|
|
4773
|
+
const camera = this.lastPassCamera;
|
|
4774
|
+
const env = this.lastPassEnv;
|
|
4775
|
+
if (camera !== null && env !== null) {
|
|
4776
|
+
gl.useProgram(program);
|
|
4777
|
+
this.writeMeshPassState(uniforms, camera, env);
|
|
4778
|
+
this.writeMaterialState(uniforms, this.currentMaterial);
|
|
4779
|
+
this.useFlatProgram();
|
|
4780
|
+
}
|
|
4604
4781
|
}
|
|
4605
4782
|
|
|
4606
4783
|
/** Push placement and colour. Only the live prefix. */
|
|
@@ -4663,18 +4840,29 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4663
4840
|
order. See `InstancedOptions.cull`. */
|
|
4664
4841
|
const culling = !blend && batch.cull;
|
|
4665
4842
|
if (culling && !batchBoxVisible(batch.box, this.frustum, this.occlusion)) return;
|
|
4843
|
+
/* A batch playing a bone animation takes the animated variant; a disposed clip draws nothing. */
|
|
4844
|
+
const clip = batch.animation;
|
|
4845
|
+
if (clip !== null && clip.disposed) return;
|
|
4666
4846
|
this.drawBudget.ask();
|
|
4667
4847
|
this.ensureInstancedProgram();
|
|
4848
|
+
if (clip !== null) this.ensureAnimatedProgram();
|
|
4668
4849
|
/* A material with a model takes that model's instanced program; see `modelProgram`. */
|
|
4669
4850
|
const modelKind = this.currentMaterial?.model?.kind ?? null;
|
|
4670
|
-
const modelled =
|
|
4671
|
-
|
|
4672
|
-
|
|
4851
|
+
const modelled =
|
|
4852
|
+
modelKind === null
|
|
4853
|
+
? null
|
|
4854
|
+
: this.modelProgram(clip === null ? 'instanced' : 'animated', modelKind);
|
|
4855
|
+
const program =
|
|
4856
|
+
modelled?.program ?? (clip === null ? this.flatInstancedProgram : this.flatAnimatedProgram);
|
|
4857
|
+
const u =
|
|
4858
|
+
modelled?.uniforms ??
|
|
4859
|
+
(clip === null ? this.flatInstancedUniforms : this.flatAnimatedUniforms);
|
|
4673
4860
|
if (program === null || u === null) return;
|
|
4674
4861
|
|
|
4675
4862
|
const { gl } = this;
|
|
4676
4863
|
gl.useProgram(program);
|
|
4677
4864
|
gl.uniform1i(u['uHasTangents'] ?? null, mesh.hasTangents ? 1 : 0);
|
|
4865
|
+
if (clip !== null) this.bindAnimation(u, batch);
|
|
4678
4866
|
|
|
4679
4867
|
const lit = options.lit ?? true;
|
|
4680
4868
|
/* Added light fades in the medium rather than receding into it. See `drawFog.ts`. */
|
|
@@ -4737,8 +4925,13 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4737
4925
|
gl.uniform4fv(u['uCutout'] ?? null, this.cutoutUniform(this.currentMaterial, true));
|
|
4738
4926
|
const keeps = !blend && this.reflectionKeeps();
|
|
4739
4927
|
if (keeps) this.reflectionTargets?.on();
|
|
4740
|
-
|
|
4741
|
-
|
|
4928
|
+
/* An animated batch reads its clocks by instance index, which the culled copy would reorder:
|
|
4929
|
+
it is culled whole and never instance by instance. */
|
|
4930
|
+
if (culling && clip === null && cullsInstances(count, mesh.indexCount)) {
|
|
4931
|
+
batch.drawCulled(gl, data, this.frustum);
|
|
4932
|
+
} else {
|
|
4933
|
+
batch.draw(gl, count);
|
|
4934
|
+
}
|
|
4742
4935
|
if (keeps) this.reflectionTargets?.off();
|
|
4743
4936
|
if (coverage) gl.disable(gl.SAMPLE_ALPHA_TO_COVERAGE);
|
|
4744
4937
|
if (ditheredBlend)
|
|
@@ -4800,7 +4993,9 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4800
4993
|
glass.transmission,
|
|
4801
4994
|
glass.frost,
|
|
4802
4995
|
);
|
|
4803
|
-
|
|
4996
|
+
/* w: this framebuffer's first row is the bottom, so up the screen is up the rows. */
|
|
4997
|
+
const tint = options.refractTint ?? WHITE_TINT;
|
|
4998
|
+
gl.uniform4f(u['uRefractTint'] ?? null, tint[0] ?? 1, tint[1] ?? 1, tint[2] ?? 1, 1);
|
|
4804
4999
|
gl.uniform3fv(u['uGlassTint'] ?? null, glass.tint);
|
|
4805
5000
|
return true;
|
|
4806
5001
|
}
|
|
@@ -6111,6 +6306,71 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6111
6306
|
casters(this.casterSink);
|
|
6112
6307
|
}
|
|
6113
6308
|
|
|
6309
|
+
/**
|
|
6310
|
+
* Draws that do not change between frames, enumerated once and kept: what a stage, a set or any
|
|
6311
|
+
* world that stands still draws, recorded so a frame replays it rather than issuing it.
|
|
6312
|
+
*
|
|
6313
|
+
* **The same enumeration `drawSceneCasters` takes**, called once, here: rigid meshes and instanced
|
|
6314
|
+
* batches, each with its material. A skinned mesh or a scatter batch is refused by name, since
|
|
6315
|
+
* both change every frame. What it captures is what the enumeration said at the time — the
|
|
6316
|
+
* matrices and the instance counts are copied, so a caller's scratch is free to move on — while
|
|
6317
|
+
* the meshes, batches, textures and materials stay the caller's: a list must be disposed, or made
|
|
6318
|
+
* again, before anything it holds is, and made again when a material it holds is changed.
|
|
6319
|
+
*
|
|
6320
|
+
* **What it is for is the frame's processor time.** WebGPU records the list into a render bundle
|
|
6321
|
+
* per view and replays it with one call, where a draw issued a frame at a time costs its encoding
|
|
6322
|
+
* every frame and again for every view that draws it. This backend has no bundles and replays the
|
|
6323
|
+
* entries through the scene-caster path every frame, at the cost a frame drawing them itself pays;
|
|
6324
|
+
* the picture is the same.
|
|
6325
|
+
*
|
|
6326
|
+
* What a list gives up against drawing each frame is culling: every entry is drawn in every view,
|
|
6327
|
+
* because a bundle cannot leave one out. A world split into lists by region culls by list.
|
|
6328
|
+
*/
|
|
6329
|
+
createStaticDraws(casters: ShadowCasters): StaticDrawsHandle {
|
|
6330
|
+
return captureStaticDraws(casters);
|
|
6331
|
+
}
|
|
6332
|
+
|
|
6333
|
+
/**
|
|
6334
|
+
* Draw a list into the open mesh pass, under the camera, the lights and the fog `bindMeshPass`
|
|
6335
|
+
* set — the frame's, a mirror's, a capture's. Opaque draws, through each entry's material.
|
|
6336
|
+
*
|
|
6337
|
+
* **Its draws are rigid and wear no overlay**, so a skin palette, morph weights, cloth or a surface
|
|
6338
|
+
* overlay set before it is put back to none, as `bindMeshPass` puts an overlay back; and no
|
|
6339
|
+
* material is left set after it, which is what `setMaterial(null)` leaves. The state a pass set —
|
|
6340
|
+
* the grain, the relief, the fog, the grade — is the list's to draw with, as it is any draw's, and
|
|
6341
|
+
* a list drawn under a different one is recorded again for it on WebGPU.
|
|
6342
|
+
*/
|
|
6343
|
+
drawStaticDraws(draws: StaticDrawsHandle): void {
|
|
6344
|
+
if (this.contextLost) return;
|
|
6345
|
+
const list = draws as StaticDrawsList;
|
|
6346
|
+
if (list.disposed) {
|
|
6347
|
+
this.warnStaticDisposed();
|
|
6348
|
+
return;
|
|
6349
|
+
}
|
|
6350
|
+
this.setSkinPalette(null);
|
|
6351
|
+
this.setMorphWeights(null);
|
|
6352
|
+
this.setCloth(null);
|
|
6353
|
+
this.setSurfaceOverlay(null);
|
|
6354
|
+
this.setMaterial(null);
|
|
6355
|
+
this.sceneCasterMaterial = undefined;
|
|
6356
|
+
replayStaticDraws(list, this.sceneCasterSink);
|
|
6357
|
+
this.setMaterial(null);
|
|
6358
|
+
this.sceneCasterMaterial = undefined;
|
|
6359
|
+
}
|
|
6360
|
+
|
|
6361
|
+
/** Release a list. Its meshes, batches and materials are the caller's and are not released. */
|
|
6362
|
+
disposeStaticDraws(draws: StaticDrawsHandle): void {
|
|
6363
|
+
(draws as StaticDrawsList).disposed = true;
|
|
6364
|
+
}
|
|
6365
|
+
|
|
6366
|
+
/** A disposed list drawn, said once: the frame loop does not throw. */
|
|
6367
|
+
private warnStaticDisposed(): void {
|
|
6368
|
+
if (this.warnedStaticDisposed) return;
|
|
6369
|
+
this.warnedStaticDisposed = true;
|
|
6370
|
+
console.warn('drawStaticDraws: this list was disposed, and draws nothing.');
|
|
6371
|
+
}
|
|
6372
|
+
private warnedStaticDisposed = false;
|
|
6373
|
+
|
|
6114
6374
|
/**
|
|
6115
6375
|
* Draw the pass's glass twice: where the nearest pane is, and what the panes let through.
|
|
6116
6376
|
*
|
|
@@ -6339,9 +6599,10 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6339
6599
|
instanced: boolean,
|
|
6340
6600
|
label: string,
|
|
6341
6601
|
eight = false,
|
|
6602
|
+
animated = false,
|
|
6342
6603
|
): { program: WebGLProgram; uniforms: Record<string, WebGLUniformLocation> } | null => {
|
|
6343
6604
|
if (old === null) return null;
|
|
6344
|
-
const vertex = flatVert({ skinned, morphed, instanced });
|
|
6605
|
+
const vertex = flatVert({ skinned, morphed, instanced, animated });
|
|
6345
6606
|
const program = compileProgram(
|
|
6346
6607
|
gl,
|
|
6347
6608
|
eight ? skinEight(vertex) : vertex,
|
|
@@ -6374,6 +6635,17 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6374
6635
|
const instanced = recompile(this.flatInstancedProgram, false, false, true, 'flat.instanced');
|
|
6375
6636
|
this.flatInstancedProgram = instanced?.program ?? null;
|
|
6376
6637
|
this.flatInstancedUniforms = instanced?.uniforms ?? null;
|
|
6638
|
+
const animated = recompile(
|
|
6639
|
+
this.flatAnimatedProgram,
|
|
6640
|
+
false,
|
|
6641
|
+
false,
|
|
6642
|
+
true,
|
|
6643
|
+
'flat.animated',
|
|
6644
|
+
false,
|
|
6645
|
+
true,
|
|
6646
|
+
);
|
|
6647
|
+
this.flatAnimatedProgram = animated?.program ?? null;
|
|
6648
|
+
this.flatAnimatedUniforms = animated?.uniforms ?? null;
|
|
6377
6649
|
|
|
6378
6650
|
for (const [key, entry] of this.clothFlat) {
|
|
6379
6651
|
const eight = key.startsWith('8');
|
|
@@ -6404,7 +6676,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6404
6676
|
}
|
|
6405
6677
|
|
|
6406
6678
|
this.flatTargets.length = 0;
|
|
6407
|
-
for (const target of [plain, skinned, morphed, both, skinned8, both8, instanced]) {
|
|
6679
|
+
for (const target of [plain, skinned, morphed, both, skinned8, both8, instanced, animated]) {
|
|
6408
6680
|
if (target !== null) this.flatTargets.push(target);
|
|
6409
6681
|
}
|
|
6410
6682
|
for (const target of this.clothFlat.values()) this.flatTargets.push(target);
|
|
@@ -8438,6 +8710,10 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8438
8710
|
/* Nor an ambient of its own: the frame's, until a draw asks. See `setAmbientSH`. */
|
|
8439
8711
|
this.ambientSH.fill(0);
|
|
8440
8712
|
/* No reflection and an environment at its own brightness; the emission the world asks for. */
|
|
8713
|
+
this.passReflectivity = 0;
|
|
8714
|
+
this.passEnvironmentGain = 1;
|
|
8715
|
+
this.materialReflectivity = null;
|
|
8716
|
+
this.materialEnvironmentGain = null;
|
|
8441
8717
|
this.environmentDials[1] = 0;
|
|
8442
8718
|
this.environmentDials[2] = 1;
|
|
8443
8719
|
this.emission[0] = env.emissiveGain;
|
|
@@ -8598,7 +8874,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8598
8874
|
/* Neither refracting nor glass, which is every draw that does not say otherwise. See
|
|
8599
8875
|
`bindSeeThrough` for the four numbers in the one vector. */
|
|
8600
8876
|
gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
|
|
8601
|
-
gl.
|
|
8877
|
+
gl.uniform4f(u['uRefractTint'] ?? null, 1, 1, 1, 1);
|
|
8602
8878
|
gl.uniform3fv(u['uGlassTint'] ?? null, WHITE_TINT);
|
|
8603
8879
|
gl.uniform3fv(u['uDirectionalDir'] ?? null, env.directionalDir);
|
|
8604
8880
|
gl.uniform3fv(u['uDirectionalColor'] ?? null, env.directionalColor);
|
|
@@ -8721,7 +8997,16 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8721
8997
|
gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.peelFilled ? 1 : 0);
|
|
8722
8998
|
gl.uniform1f(u['uShadowMaxSlope'] ?? null, this.quality.directionalShadowMaxSlope);
|
|
8723
8999
|
gl.uniform1i(u['uShadowFilterTaps'] ?? null, this.quality.shadowFilterTaps);
|
|
8724
|
-
|
|
9000
|
+
/* The near corner, and in its w the frame the cutout dither moves with. See `uHighlightMin`.
|
|
9001
|
+
An environment written by hand may leave the box out, as the far corner says below. */
|
|
9002
|
+
const min = env.highlightMin as ArrayLike<number> | undefined;
|
|
9003
|
+
gl.uniform4f(
|
|
9004
|
+
u['uHighlightMin'] ?? null,
|
|
9005
|
+
min?.[0] ?? 0,
|
|
9006
|
+
min?.[1] ?? 0,
|
|
9007
|
+
min?.[2] ?? 0,
|
|
9008
|
+
this.cutoutFrame,
|
|
9009
|
+
);
|
|
8725
9010
|
/* The far corner, and the gain in its w. See `uHighlightMax`. */
|
|
8726
9011
|
/* An environment written by hand may leave the box out; then there is none, as before. */
|
|
8727
9012
|
const max = env.highlightMax ?? NO_HIGHLIGHT;
|
|
@@ -10183,6 +10468,12 @@ function litVertexOf(variant: string): string {
|
|
|
10183
10468
|
}
|
|
10184
10469
|
const skinned = variant.startsWith('skinned') || variant.startsWith('both');
|
|
10185
10470
|
const morphed = variant === 'morphed' || variant.startsWith('both');
|
|
10186
|
-
const
|
|
10471
|
+
const animated = variant === 'animated';
|
|
10472
|
+
const vertex = flatVert({
|
|
10473
|
+
skinned,
|
|
10474
|
+
morphed,
|
|
10475
|
+
instanced: variant === 'instanced' || animated,
|
|
10476
|
+
animated,
|
|
10477
|
+
});
|
|
10187
10478
|
return variant.endsWith('8') ? skinEight(vertex) : vertex;
|
|
10188
10479
|
}
|
|
@@ -22,16 +22,18 @@ export class SkinPaletteTexture {
|
|
|
22
22
|
/**
|
|
23
23
|
* Upload a palette, reallocating only when the joint count changes.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
25
|
+
* **The texture grows and never shrinks**, so the steady state is one `texSubImage2D` whatever
|
|
26
|
+
* mix of rigs a frame draws. It was rebuilt whenever the joint count changed, which with one
|
|
27
|
+
* texture for every skinned draw meant a character and a prop drawn in turn reallocated it at
|
|
28
|
+
* every draw: a GPU allocation in the frame loop. The shader reads joints by index, so a texture
|
|
29
|
+
* wider than the rig is read exactly as one that fits.
|
|
28
30
|
*/
|
|
29
31
|
update(gl: WebGL2RenderingContext, palette: Float32Array): void {
|
|
30
32
|
validateSkinPalette(palette);
|
|
31
33
|
const joints = palette.length / 16;
|
|
32
34
|
const width = paletteTextureWidth(joints);
|
|
33
35
|
|
|
34
|
-
if (this.texture === null ||
|
|
36
|
+
if (this.texture === null || joints > this.joints) {
|
|
35
37
|
if (this.texture !== null) gl.deleteTexture(this.texture);
|
|
36
38
|
const created = gl.createTexture();
|
|
37
39
|
if (created === null) throw new Error('skin palette: failed to create the palette texture');
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A bone animation's textures on this backend, and a batch's clocks: what
|
|
3
|
+
* `shaders/boneAnimation.ts` reads by index, made once and never filtered.
|
|
4
|
+
*
|
|
5
|
+
* **`rgba32float` places and turns, and `rg32float` clocks**, all read with `textureLoad` and bound
|
|
6
|
+
* `unfilterable-float`, as the joint palette is: a turn in half floats moves a crowd's hands by a
|
|
7
|
+
* thousandth of their reach, a jitter at the frame's rate, and the bytes are a clip's, once.
|
|
8
|
+
*/
|
|
9
|
+
import {
|
|
10
|
+
CLOCK_TEXTURE_WIDTH,
|
|
11
|
+
type BoneAnimationHandle,
|
|
12
|
+
type BoneAnimationTexels,
|
|
13
|
+
} from '../../boneAnimation.ts';
|
|
14
|
+
|
|
15
|
+
const USAGE_TEXTURE = 0x4 | 0x2; // TEXTURE_BINDING | COPY_DST
|
|
16
|
+
|
|
17
|
+
/** A clip on the device, from `createBoneAnimation`. */
|
|
18
|
+
export class GpuBoneAnimation implements BoneAnimationHandle {
|
|
19
|
+
readonly bones: number;
|
|
20
|
+
readonly frames: number;
|
|
21
|
+
readonly framesPerSecond: number;
|
|
22
|
+
readonly boneScale: number;
|
|
23
|
+
readonly placesView: GPUTextureView;
|
|
24
|
+
readonly turnsView: GPUTextureView;
|
|
25
|
+
disposed = false;
|
|
26
|
+
private readonly places: GPUTexture;
|
|
27
|
+
private readonly turns: GPUTexture;
|
|
28
|
+
|
|
29
|
+
constructor(device: GPUDevice, texels: BoneAnimationTexels) {
|
|
30
|
+
this.bones = texels.bones;
|
|
31
|
+
this.frames = texels.frames;
|
|
32
|
+
this.framesPerSecond = texels.framesPerSecond;
|
|
33
|
+
this.boneScale = texels.boneScale;
|
|
34
|
+
const make = (label: string, data: Float32Array): GPUTexture => {
|
|
35
|
+
const texture = device.createTexture({
|
|
36
|
+
label,
|
|
37
|
+
size: [texels.bones, texels.frames],
|
|
38
|
+
format: 'rgba32float',
|
|
39
|
+
usage: USAGE_TEXTURE,
|
|
40
|
+
});
|
|
41
|
+
device.queue.writeTexture(
|
|
42
|
+
{ texture },
|
|
43
|
+
data,
|
|
44
|
+
{ bytesPerRow: texels.bones * 16, rowsPerImage: texels.frames },
|
|
45
|
+
[texels.bones, texels.frames],
|
|
46
|
+
);
|
|
47
|
+
return texture;
|
|
48
|
+
};
|
|
49
|
+
this.places = make('bones.places', texels.places);
|
|
50
|
+
this.turns = make('bones.turns', texels.turns);
|
|
51
|
+
this.placesView = this.places.createView();
|
|
52
|
+
this.turnsView = this.turns.createView();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
dispose(): void {
|
|
56
|
+
this.disposed = true;
|
|
57
|
+
this.places.destroy();
|
|
58
|
+
this.turns.destroy();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** A batch's clocks: two floats an instance, wrapped onto rows `CLOCK_TEXTURE_WIDTH` wide. */
|
|
63
|
+
export class GpuInstanceClocks {
|
|
64
|
+
readonly view: GPUTextureView;
|
|
65
|
+
readonly staging: Float32Array;
|
|
66
|
+
private readonly texture: GPUTexture;
|
|
67
|
+
private readonly width: number;
|
|
68
|
+
|
|
69
|
+
constructor(device: GPUDevice, capacity: number, label: string) {
|
|
70
|
+
this.width = Math.max(1, Math.min(capacity, CLOCK_TEXTURE_WIDTH));
|
|
71
|
+
const rows = Math.max(1, Math.ceil(capacity / this.width));
|
|
72
|
+
this.staging = new Float32Array(this.width * rows * 2);
|
|
73
|
+
this.texture = device.createTexture({
|
|
74
|
+
label,
|
|
75
|
+
size: [this.width, rows],
|
|
76
|
+
format: 'rg32float',
|
|
77
|
+
usage: USAGE_TEXTURE,
|
|
78
|
+
});
|
|
79
|
+
this.view = this.texture.createView();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The rows that hold the first `count` instances, from `staging`. */
|
|
83
|
+
upload(queue: GPUQueue, count: number): void {
|
|
84
|
+
if (count <= 0) return;
|
|
85
|
+
const rows = Math.ceil(count / this.width);
|
|
86
|
+
queue.writeTexture(
|
|
87
|
+
{ texture: this.texture },
|
|
88
|
+
this.staging,
|
|
89
|
+
{ bytesPerRow: this.width * 8, rowsPerImage: rows },
|
|
90
|
+
[this.width, rows],
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
dispose(): void {
|
|
95
|
+
this.texture.destroy();
|
|
96
|
+
}
|
|
97
|
+
}
|