@driftengine/core 4.9.0 → 4.11.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 +133 -11
- package/README.md +43 -38
- package/dist/index.d.ts +7 -4
- package/dist/index.js +5 -4
- 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 +189 -4
- package/dist/render/backend/webgl2/renderer.js +614 -66
- 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 +24 -4
- package/dist/render/backend/webgpu/buffers.js +116 -59
- package/dist/render/backend/webgpu/clothKernels.d.ts +8 -0
- package/dist/render/backend/webgpu/clothKernels.js +76 -0
- package/dist/render/backend/webgpu/clothOps.d.ts +39 -0
- package/dist/render/backend/webgpu/clothOps.js +78 -0
- package/dist/render/backend/webgpu/clothSetLayout.d.ts +25 -0
- package/dist/render/backend/webgpu/clothSetLayout.js +84 -0
- package/dist/render/backend/webgpu/clothSetPack.d.ts +33 -0
- package/dist/render/backend/webgpu/clothSetPack.js +144 -0
- 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 +20 -4
- package/dist/render/backend/webgpu/drawCommand.js +11 -3
- package/dist/render/backend/webgpu/flatPass.d.ts +85 -42
- package/dist/render/backend/webgpu/flatPass.js +137 -20
- package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
- package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
- package/dist/render/backend/webgpu/instanced.d.ts +14 -1
- package/dist/render/backend/webgpu/instanced.js +24 -1
- package/dist/render/backend/webgpu/latePass.js +1 -0
- package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
- package/dist/render/backend/webgpu/materialStore.js +265 -0
- package/dist/render/backend/webgpu/motionPass.js +15 -6
- package/dist/render/backend/webgpu/pipelineCache.d.ts +28 -1
- package/dist/render/backend/webgpu/pipelineCache.js +46 -1
- package/dist/render/backend/webgpu/postPass.d.ts +1 -1
- package/dist/render/backend/webgpu/renderer.d.ts +200 -35
- package/dist/render/backend/webgpu/renderer.js +993 -168
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +9 -2
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +121 -47
- 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 +242 -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/backend/webgpu/vertexFormats.d.ts +22 -0
- package/dist/render/backend/webgpu/vertexFormats.js +97 -0
- 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/globalMediumPass.d.ts +2 -0
- package/dist/render/globalMediumPass.js +1 -0
- package/dist/render/indexWidth.d.ts +17 -0
- package/dist/render/indexWidth.js +23 -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/lightMatrix.d.ts +11 -0
- package/dist/render/lightMatrix.js +14 -0
- package/dist/render/lightmap.d.ts +1 -1
- package/dist/render/mesh.d.ts +14 -0
- package/dist/render/mesh.js +44 -14
- package/dist/render/pointLightSelection.d.ts +4 -4
- package/dist/render/renderQuality.d.ts +30 -0
- package/dist/render/renderQuality.js +5 -0
- package/dist/render/rendererHarness.d.ts +7 -0
- package/dist/render/rendererHarness.js +12 -0
- package/dist/render/screenSpaceReflection.d.ts +2 -1
- package/dist/render/shader.d.ts +15 -0
- package/dist/render/shader.js +41 -0
- 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/directionalShadow.js +84 -10
- package/dist/render/shaders/flat/glassTint.d.ts +1 -1
- package/dist/render/shaders/flat/glassTint.js +3 -0
- package/dist/render/shaders/flat/index.d.ts +21 -0
- package/dist/render/shaders/flat/index.js +42 -7
- package/dist/render/shaders/flat/layered.d.ts +19 -0
- package/dist/render/shaders/flat/layered.js +115 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +58 -21
- package/dist/render/shaders/flat/mapReads.d.ts +10 -0
- package/dist/render/shaders/flat/mapReads.js +38 -0
- package/dist/render/shaders/flat/models.js +1 -1
- package/dist/render/shaders/flat/movingSun.d.ts +21 -0
- package/dist/render/shaders/flat/movingSun.js +46 -0
- package/dist/render/shaders/flat/normalMap.d.ts +6 -2
- package/dist/render/shaders/flat/normalMap.js +14 -3
- package/dist/render/shaders/flat/overlay.d.ts +3 -2
- package/dist/render/shaders/flat/overlay.js +42 -20
- package/dist/render/shaders/flat/pointShadow.js +2 -2
- package/dist/render/shaders/flat/preamble.js +59 -27
- package/dist/render/shaders/flat/worldUv.d.ts +22 -0
- package/dist/render/shaders/flat/worldUv.js +113 -0
- 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 +3616 -3042
- package/dist/render/shaders/generated/flat.wgsl.js +3688 -3113
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +8 -3
- package/dist/render/shaders/generated/globalMedium.wgsl.js +8 -3
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +15 -10
- package/dist/render/shaders/generated/lightVolume.wgsl.js +16 -11
- package/dist/render/shaders/globalMedium.js +9 -1
- package/dist/render/shaders/lightVolume.js +9 -1
- package/dist/render/shaders/outputTransform.d.ts +7 -0
- package/dist/render/shaders/outputTransform.js +28 -14
- package/dist/render/shaders/tangentFrame.d.ts +1 -1
- package/dist/render/shaders/tangentFrame.js +16 -3
- package/dist/render/shaders/vertexChannel.d.ts +7 -0
- package/dist/render/shaders/vertexChannel.js +26 -0
- package/dist/render/skinnedClothRun.d.ts +28 -0
- package/dist/render/skinnedClothRun.js +103 -35
- package/dist/render/staticDraws.d.ts +47 -0
- package/dist/render/staticDraws.js +43 -0
- package/dist/render/surfaceLayers.d.ts +39 -0
- package/dist/render/surfaceLayers.js +26 -0
- package/dist/render/surfaceOverlay.d.ts +16 -3
- package/dist/render/surfaceOverlay.js +11 -6
- package/dist/render/surfaceProjection.d.ts +34 -0
- package/dist/render/surfaceProjection.js +30 -0
- package/dist/render/surfaceTexture.d.ts +68 -7
- package/dist/render/surfaceTexture.js +56 -17
- package/dist/render/vertexPacking.d.ts +48 -0
- package/dist/render/vertexPacking.js +68 -0
- package/package.json +6 -6
- package/src/index.ts +12 -4
- 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 +723 -79
- 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 +195 -70
- package/src/render/backend/webgpu/clothKernels.ts +86 -0
- package/src/render/backend/webgpu/clothOps.ts +86 -0
- package/src/render/backend/webgpu/clothSetLayout.ts +102 -0
- package/src/render/backend/webgpu/clothSetPack.ts +197 -0
- 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 +25 -4
- package/src/render/backend/webgpu/flatPass.ts +212 -41
- package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
- package/src/render/backend/webgpu/instanced.ts +32 -1
- package/src/render/backend/webgpu/latePass.ts +1 -0
- package/src/render/backend/webgpu/materialStore.ts +276 -0
- package/src/render/backend/webgpu/motionPass.ts +18 -7
- package/src/render/backend/webgpu/pipelineCache.ts +53 -7
- package/src/render/backend/webgpu/renderer.ts +1201 -202
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
- 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 +364 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
- package/src/render/backend/webgpu/uniformRing.ts +10 -1
- package/src/render/backend/webgpu/vertexFormats.ts +113 -0
- 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/globalMediumPass.ts +7 -0
- package/src/render/indexWidth.ts +27 -0
- package/src/render/instances.ts +27 -0
- package/src/render/lightBudget.ts +11 -0
- package/src/render/lightMatrix.ts +15 -0
- package/src/render/lightmap.ts +1 -1
- package/src/render/mesh.ts +72 -14
- package/src/render/pointLightSelection.ts +4 -4
- package/src/render/renderQuality.ts +38 -0
- package/src/render/rendererHarness.ts +15 -0
- package/src/render/screenSpaceReflection.ts +2 -1
- package/src/render/shader.ts +59 -0
- package/src/render/shaders/boneAnimation.ts +63 -0
- package/src/render/shaders/depth.ts +45 -10
- package/src/render/shaders/flat/directionalShadow.ts +84 -10
- package/src/render/shaders/flat/glassTint.ts +7 -1
- package/src/render/shaders/flat/index.ts +63 -7
- package/src/render/shaders/flat/layered.ts +115 -0
- package/src/render/shaders/flat/main.ts +58 -21
- package/src/render/shaders/flat/mapReads.ts +38 -0
- package/src/render/shaders/flat/models.ts +1 -1
- package/src/render/shaders/flat/movingSun.ts +46 -0
- package/src/render/shaders/flat/normalMap.ts +14 -3
- package/src/render/shaders/flat/overlay.ts +42 -20
- package/src/render/shaders/flat/pointShadow.ts +2 -2
- package/src/render/shaders/flat/preamble.ts +59 -27
- package/src/render/shaders/flat/worldUv.ts +113 -0
- package/src/render/shaders/generated/depth.wgsl.ts +95 -0
- package/src/render/shaders/generated/flat.wgsl.ts +3688 -3113
- package/src/render/shaders/generated/globalMedium.wgsl.ts +8 -3
- package/src/render/shaders/generated/lightVolume.wgsl.ts +16 -11
- package/src/render/shaders/globalMedium.ts +9 -1
- package/src/render/shaders/lightVolume.ts +9 -1
- package/src/render/shaders/outputTransform.ts +30 -14
- package/src/render/shaders/tangentFrame.ts +16 -3
- package/src/render/shaders/vertexChannel.ts +27 -0
- package/src/render/skinnedClothRun.ts +138 -38
- package/src/render/staticDraws.ts +95 -0
- package/src/render/surfaceLayers.ts +57 -0
- package/src/render/surfaceOverlay.ts +24 -7
- package/src/render/surfaceProjection.ts +57 -0
- package/src/render/surfaceTexture.ts +124 -27
- package/src/render/vertexPacking.ts +109 -0
- package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +0 -74
- package/dist/render/backend/webgpu/gpuSkinnedCloth.js +0 -452
- package/src/render/backend/webgpu/gpuSkinnedCloth.ts +0 -502
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vertex animation from bone textures: a clip that turns and places each bone frame by frame,
|
|
3
|
+
* which every vertex of an instanced batch's mesh follows — one bone a vertex, named by the mesh's
|
|
4
|
+
* second coordinates — at a moment each instance keeps its own clock for.
|
|
5
|
+
*
|
|
6
|
+
* **What a crowd is built from.** A rigged character skinned per draw costs a palette upload and a
|
|
7
|
+
* draw each; a crowd of them, played back from a clip a mesh, costs nothing on the processor once
|
|
8
|
+
* the clip is on the device, and every instance moves at the frame's rate on its own clock rather
|
|
9
|
+
* than in step with the others. What it gives up against skinning is the blend: a vertex follows
|
|
10
|
+
* exactly one bone, so a joint bends hard, which at a crowd's distance is what such clips are made
|
|
11
|
+
* for.
|
|
12
|
+
*
|
|
13
|
+
* **The vertex's bone is its second coordinates' u times `boneScale`**, rounded: a mesh's second
|
|
14
|
+
* coordinates (`MeshData.lightmapUvs`) are free on a crowd, which bakes no lightmap, and the format
|
|
15
|
+
* that writes one bone a sixty-fourth of u is the format this reads with `boneScale: 64`.
|
|
16
|
+
*
|
|
17
|
+
* **A frame's turn and place move the vertex as the bone does**: the turn about the mesh's origin,
|
|
18
|
+
* then the place added — `turn(q, p) + t` — and its normal and tangent turned with it. Between two
|
|
19
|
+
* frames both are blended: the places linearly, the turns normalised along the shorter arc. The
|
|
20
|
+
* moment is `(sceneTime × rate + phase) × framesPerSecond`, looping, where the scene's time is the
|
|
21
|
+
* clock `setAnimationTime` sets, once a frame before the shadows, and each instance's phase and
|
|
22
|
+
* rate are `MeshInstances.clocks`.
|
|
23
|
+
*
|
|
24
|
+
* Backend-neutral: both backends upload what `packBoneAnimation` packs and run
|
|
25
|
+
* `shaders/boneAnimation.ts`, which is `animateBoneVertex` below, line for line.
|
|
26
|
+
*/
|
|
27
|
+
import type { MeshInstances } from './instances.ts';
|
|
28
|
+
|
|
29
|
+
/** A clip on the device, from `createBoneAnimation`. Opaque but for its size. */
|
|
30
|
+
export interface BoneAnimationHandle {
|
|
31
|
+
readonly bones: number;
|
|
32
|
+
readonly frames: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** A clip, as a caller hands it to `createBoneAnimation`. */
|
|
36
|
+
export interface BoneAnimationClip {
|
|
37
|
+
/** Bones each frame holds. */
|
|
38
|
+
readonly bones: number;
|
|
39
|
+
/** Frames, played in order and looped. */
|
|
40
|
+
readonly frames: number;
|
|
41
|
+
readonly framesPerSecond: number;
|
|
42
|
+
/** Where each bone puts what it carries: three floats a bone, bone by bone, frame by frame. */
|
|
43
|
+
readonly places: Float32Array;
|
|
44
|
+
/** Each bone's turn: a unit quaternion, x y z w, laid out as `places` is. */
|
|
45
|
+
readonly turns: Float32Array;
|
|
46
|
+
/** How many bones one unit of the second coordinates' u counts: `bones` unless given. */
|
|
47
|
+
readonly boneScale?: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A clip as textures hold it: a texel a bone a frame, bones across and frames down. */
|
|
51
|
+
export interface BoneAnimationTexels {
|
|
52
|
+
readonly bones: number;
|
|
53
|
+
readonly frames: number;
|
|
54
|
+
readonly framesPerSecond: number;
|
|
55
|
+
readonly boneScale: number;
|
|
56
|
+
/** RGBA floats: the place, and 1. */
|
|
57
|
+
readonly places: Float32Array;
|
|
58
|
+
/** RGBA floats: the turn, normalised. */
|
|
59
|
+
readonly turns: Float32Array;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The most bones or frames a clip may have: WebGL2's guaranteed texture size. A clip past it is
|
|
64
|
+
* refused rather than cut, since a cut clip is a crowd frozen at its last row.
|
|
65
|
+
*/
|
|
66
|
+
export const MAX_BONE_TEXTURE_SIZE = 2048;
|
|
67
|
+
|
|
68
|
+
/** A clip checked and packed into its two textures' texels. Runs once, when the clip is made. */
|
|
69
|
+
export function packBoneAnimation(clip: BoneAnimationClip): BoneAnimationTexels {
|
|
70
|
+
const { bones, frames, framesPerSecond } = clip;
|
|
71
|
+
for (const [name, value] of [
|
|
72
|
+
['bones', bones],
|
|
73
|
+
['frames', frames],
|
|
74
|
+
] as const) {
|
|
75
|
+
if (!Number.isInteger(value) || value < 1 || value > MAX_BONE_TEXTURE_SIZE) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`createBoneAnimation: ${name} is ${value}; a whole number from 1 to ${MAX_BONE_TEXTURE_SIZE}`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (!(framesPerSecond > 0)) {
|
|
82
|
+
throw new Error(`createBoneAnimation: framesPerSecond is ${framesPerSecond}; above zero`);
|
|
83
|
+
}
|
|
84
|
+
const count = bones * frames;
|
|
85
|
+
if (clip.places.length !== count * 3 || clip.turns.length !== count * 4) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`createBoneAnimation: ${bones} bones over ${frames} frames is ${count * 3} places and ` +
|
|
88
|
+
`${count * 4} turn numbers, and was given ${clip.places.length} and ${clip.turns.length}`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
const boneScale = clip.boneScale ?? bones;
|
|
92
|
+
if (!(boneScale > 0)) {
|
|
93
|
+
throw new Error(`createBoneAnimation: boneScale is ${boneScale}; above zero`);
|
|
94
|
+
}
|
|
95
|
+
const places = new Float32Array(count * 4);
|
|
96
|
+
const turns = new Float32Array(count * 4);
|
|
97
|
+
for (let i = 0; i < count; i++) {
|
|
98
|
+
places[i * 4] = clip.places[i * 3] as number;
|
|
99
|
+
places[i * 4 + 1] = clip.places[i * 3 + 1] as number;
|
|
100
|
+
places[i * 4 + 2] = clip.places[i * 3 + 2] as number;
|
|
101
|
+
places[i * 4 + 3] = 1;
|
|
102
|
+
const x = clip.turns[i * 4] as number;
|
|
103
|
+
const y = clip.turns[i * 4 + 1] as number;
|
|
104
|
+
const z = clip.turns[i * 4 + 2] as number;
|
|
105
|
+
const w = clip.turns[i * 4 + 3] as number;
|
|
106
|
+
const length = Math.hypot(x, y, z, w);
|
|
107
|
+
/* A zero turn is no turn, rather than a NaN through every vertex that reads it. */
|
|
108
|
+
const scale = length > 0 ? 1 / length : 0;
|
|
109
|
+
turns[i * 4] = x * scale;
|
|
110
|
+
turns[i * 4 + 1] = y * scale;
|
|
111
|
+
turns[i * 4 + 2] = z * scale;
|
|
112
|
+
turns[i * 4 + 3] = length > 0 ? w * scale : 1;
|
|
113
|
+
}
|
|
114
|
+
return { bones, frames, framesPerSecond, boneScale, places, turns };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** How wide a batch's clock texture is: one row up to this many instances, then more rows. */
|
|
118
|
+
export const CLOCK_TEXTURE_WIDTH = 1024;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Each instance's clock into `out`, two floats an instance — its phase in seconds and its rate —
|
|
122
|
+
* from `data.clocks`, and `(0, 1)` where it gives none: every instance at the clip's own speed,
|
|
123
|
+
* in step. `out` is the batch's, sized once.
|
|
124
|
+
*/
|
|
125
|
+
export function packInstanceClocks(data: MeshInstances, out: Float32Array): void {
|
|
126
|
+
const clocks = data.clocks;
|
|
127
|
+
const count = Math.min(data.count, out.length / 2);
|
|
128
|
+
for (let i = 0; i < count; i++) {
|
|
129
|
+
out[i * 2] = clocks === undefined ? 0 : ((clocks[i * 2] as number | undefined) ?? 0);
|
|
130
|
+
out[i * 2 + 1] = clocks === undefined ? 1 : ((clocks[i * 2 + 1] as number | undefined) ?? 1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Which two frames a moment falls between, and how far: what the shader reads. Looped, with a
|
|
136
|
+
* moment before the clip's start counted back from its end. **The first frame is clamped to the
|
|
137
|
+
* last**, with the blend left at whatever reaches the next: a moment a hair short of the loop that
|
|
138
|
+
* float error rounds up to `frames` lands on the last frame blended fully into the first, which is
|
|
139
|
+
* the first frame — the right answer — where an index of `frames` would read past the texture.
|
|
140
|
+
*/
|
|
141
|
+
export function boneFrame(
|
|
142
|
+
moment: number,
|
|
143
|
+
frames: number,
|
|
144
|
+
out: { first: number; second: number; blend: number },
|
|
145
|
+
): void {
|
|
146
|
+
const wrapped = moment - frames * Math.floor(moment / frames);
|
|
147
|
+
const first = Math.min(Math.floor(wrapped), frames - 1);
|
|
148
|
+
out.first = first;
|
|
149
|
+
out.second = first + 1 === frames ? 0 : first + 1;
|
|
150
|
+
out.blend = Math.min(Math.max(wrapped - first, 0), 1);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const scratchFrame = { first: 0, second: 0, blend: 0 };
|
|
154
|
+
const mixed = new Float32Array(4);
|
|
155
|
+
|
|
156
|
+
/** `v` turned by the unit quaternion `q`, into `out`. */
|
|
157
|
+
function turn(q: ArrayLike<number>, at: number, v: ArrayLike<number>, out: Float32Array): void {
|
|
158
|
+
const qx = q[at] as number;
|
|
159
|
+
const qy = q[at + 1] as number;
|
|
160
|
+
const qz = q[at + 2] as number;
|
|
161
|
+
const qw = q[at + 3] as number;
|
|
162
|
+
const vx = v[0] as number;
|
|
163
|
+
const vy = v[1] as number;
|
|
164
|
+
const vz = v[2] as number;
|
|
165
|
+
/* v + 2 q.xyz × (q.xyz × v + q.w v), as the shader writes it. */
|
|
166
|
+
const cx = qy * vz - qz * vy + qw * vx;
|
|
167
|
+
const cy = qz * vx - qx * vz + qw * vy;
|
|
168
|
+
const cz = qx * vy - qy * vx + qw * vz;
|
|
169
|
+
out[0] = vx + 2 * (qy * cz - qz * cy);
|
|
170
|
+
out[1] = vy + 2 * (qz * cx - qx * cz);
|
|
171
|
+
out[2] = vz + 2 * (qx * cy - qy * cx);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Where the clip puts a vertex at rest at `position`, following `bone`, at the scene's `time` on an
|
|
176
|
+
* instance clock of `phase` and `rate`, into `out`. The processor's statement of what the shader
|
|
177
|
+
* does, which the tests hold the arithmetic to and a device check holds the shader to.
|
|
178
|
+
*/
|
|
179
|
+
export function animateBoneVertex(
|
|
180
|
+
clip: BoneAnimationTexels,
|
|
181
|
+
bone: number,
|
|
182
|
+
position: ArrayLike<number>,
|
|
183
|
+
time: number,
|
|
184
|
+
phase: number,
|
|
185
|
+
rate: number,
|
|
186
|
+
out: Float32Array,
|
|
187
|
+
): void {
|
|
188
|
+
boneFrame((time * rate + phase) * clip.framesPerSecond, clip.frames, scratchFrame);
|
|
189
|
+
const { first, second, blend } = scratchFrame;
|
|
190
|
+
const b = Math.min(Math.max(bone, 0), clip.bones - 1);
|
|
191
|
+
const a = (first * clip.bones + b) * 4;
|
|
192
|
+
const c = (second * clip.bones + b) * 4;
|
|
193
|
+
const q = clip.turns;
|
|
194
|
+
/* The shorter arc: a quaternion and its negation are one turn. */
|
|
195
|
+
const sign =
|
|
196
|
+
(q[a] as number) * (q[c] as number) +
|
|
197
|
+
(q[a + 1] as number) * (q[c + 1] as number) +
|
|
198
|
+
(q[a + 2] as number) * (q[c + 2] as number) +
|
|
199
|
+
(q[a + 3] as number) * (q[c + 3] as number) <
|
|
200
|
+
0
|
|
201
|
+
? -1
|
|
202
|
+
: 1;
|
|
203
|
+
for (let k = 0; k < 4; k++) {
|
|
204
|
+
mixed[k] = (q[a + k] as number) * (1 - blend) + sign * (q[c + k] as number) * blend;
|
|
205
|
+
}
|
|
206
|
+
const length = Math.hypot(
|
|
207
|
+
mixed[0] as number,
|
|
208
|
+
mixed[1] as number,
|
|
209
|
+
mixed[2] as number,
|
|
210
|
+
mixed[3] as number,
|
|
211
|
+
);
|
|
212
|
+
for (let k = 0; k < 4; k++) mixed[k] = (mixed[k] as number) / length;
|
|
213
|
+
turn(mixed, 0, position, out);
|
|
214
|
+
const p = clip.places;
|
|
215
|
+
for (let k = 0; k < 3; k++) {
|
|
216
|
+
out[k] = (out[k] as number) + (p[a + k] as number) * (1 - blend) + (p[c + k] as number) * blend;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** The bone a vertex follows, from its second coordinates' u: what the shader rounds. */
|
|
221
|
+
export function boneOf(u: number, boneScale: number): number {
|
|
222
|
+
return Math.floor(u * boneScale + 0.5);
|
|
223
|
+
}
|
|
@@ -2,25 +2,106 @@
|
|
|
2
2
|
* A block-compressed image a caller hands to `createSurfaceTexture`, and the one decision about it
|
|
3
3
|
* both backends share: whether this device takes it as blocks.
|
|
4
4
|
*
|
|
5
|
-
* **Uploaded as it is, or not at all.** A
|
|
6
|
-
* of one (BC1) against four for RGBA, and keeps the mip
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* **Uploaded as it is, or not at all.** A block format costs a byte a texel on the GPU (BC7, ETC2
|
|
6
|
+
* with alpha, ASTC 4x4) or half of one (BC1, ETC2 without) against four for RGBA, and keeps the mip
|
|
7
|
+
* chain its author built rather than one a box filter makes. **Three families, and a device has the
|
|
8
|
+
* ones it has**: desktops sample BC, phones ETC2 and ASTC, and few anything else. A device that
|
|
9
|
+
* cannot sample a format gets nothing from its blocks, and core does not convert them: the loader
|
|
10
|
+
* in `@driftengine/assets` asks `uploadsCompressed` first, and where the answer is no it decodes
|
|
11
|
+
* them — and encodes ETC2 where the device takes that — off the main thread. A caller handing a
|
|
12
|
+
* source this device cannot take is refused by name.
|
|
11
13
|
*
|
|
12
14
|
* Backend-neutral, because both backends must agree on what they accept before either touches a
|
|
13
|
-
* device. `
|
|
15
|
+
* device. `@driftengine/drft`'s `BcFormat` is the BC part of `BlockFormat`; core imports nothing
|
|
14
16
|
* from that package, and a format added there without a twin here fails where the loader passes one
|
|
15
17
|
* to the other, at compile time.
|
|
16
18
|
*/
|
|
17
19
|
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
+
/**
|
|
21
|
+
* BC1 to BC5 and BC7; ETC2's three and EAC's two; and ASTC at every block size the LDR profile has.
|
|
22
|
+
* BC6H, ASTC HDR and the signed forms are not among them.
|
|
23
|
+
*/
|
|
24
|
+
export type BlockFormat =
|
|
25
|
+
| 'bc1'
|
|
26
|
+
| 'bc2'
|
|
27
|
+
| 'bc3'
|
|
28
|
+
| 'bc4'
|
|
29
|
+
| 'bc5'
|
|
30
|
+
| 'bc7'
|
|
31
|
+
| 'etc2-rgb8'
|
|
32
|
+
| 'etc2-rgb8a1'
|
|
33
|
+
| 'etc2-rgba8'
|
|
34
|
+
| 'eac-r11'
|
|
35
|
+
| 'eac-rg11'
|
|
36
|
+
| 'astc-4x4'
|
|
37
|
+
| 'astc-5x4'
|
|
38
|
+
| 'astc-5x5'
|
|
39
|
+
| 'astc-6x5'
|
|
40
|
+
| 'astc-6x6'
|
|
41
|
+
| 'astc-8x5'
|
|
42
|
+
| 'astc-8x6'
|
|
43
|
+
| 'astc-8x8'
|
|
44
|
+
| 'astc-10x5'
|
|
45
|
+
| 'astc-10x6'
|
|
46
|
+
| 'astc-10x8'
|
|
47
|
+
| 'astc-10x10'
|
|
48
|
+
| 'astc-12x10'
|
|
49
|
+
| 'astc-12x12';
|
|
50
|
+
|
|
51
|
+
/** The formats with no sRGB form: one or two channels of data, never colour. */
|
|
52
|
+
type LinearOnly = 'bc4' | 'bc5' | 'eac-r11' | 'eac-rg11';
|
|
20
53
|
|
|
21
54
|
/** A format as a device offers it: the colour space is part of the name, as WebGPU's are. */
|
|
22
|
-
export type CompressedTextureFormat =
|
|
23
|
-
|
|
55
|
+
export type CompressedTextureFormat = BlockFormat | `${Exclude<BlockFormat, LinearOnly>}-srgb`;
|
|
56
|
+
|
|
57
|
+
/** A format's block: how many texels across and down, and how many bytes it takes. */
|
|
58
|
+
export interface BlockShape {
|
|
59
|
+
readonly width: number;
|
|
60
|
+
readonly height: number;
|
|
61
|
+
readonly bytes: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const shape = (width: number, height: number, bytes: number): BlockShape => ({
|
|
65
|
+
width,
|
|
66
|
+
height,
|
|
67
|
+
bytes,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
/** Every format's block. ASTC's are sixteen bytes whatever their size, which is its whole idea. */
|
|
71
|
+
export const BLOCK_SHAPES: Readonly<Record<BlockFormat, BlockShape>> = {
|
|
72
|
+
bc1: shape(4, 4, 8),
|
|
73
|
+
bc2: shape(4, 4, 16),
|
|
74
|
+
bc3: shape(4, 4, 16),
|
|
75
|
+
bc4: shape(4, 4, 8),
|
|
76
|
+
bc5: shape(4, 4, 16),
|
|
77
|
+
bc7: shape(4, 4, 16),
|
|
78
|
+
'etc2-rgb8': shape(4, 4, 8),
|
|
79
|
+
'etc2-rgb8a1': shape(4, 4, 8),
|
|
80
|
+
'etc2-rgba8': shape(4, 4, 16),
|
|
81
|
+
'eac-r11': shape(4, 4, 8),
|
|
82
|
+
'eac-rg11': shape(4, 4, 16),
|
|
83
|
+
'astc-4x4': shape(4, 4, 16),
|
|
84
|
+
'astc-5x4': shape(5, 4, 16),
|
|
85
|
+
'astc-5x5': shape(5, 5, 16),
|
|
86
|
+
'astc-6x5': shape(6, 5, 16),
|
|
87
|
+
'astc-6x6': shape(6, 6, 16),
|
|
88
|
+
'astc-8x5': shape(8, 5, 16),
|
|
89
|
+
'astc-8x6': shape(8, 6, 16),
|
|
90
|
+
'astc-8x8': shape(8, 8, 16),
|
|
91
|
+
'astc-10x5': shape(10, 5, 16),
|
|
92
|
+
'astc-10x6': shape(10, 6, 16),
|
|
93
|
+
'astc-10x8': shape(10, 8, 16),
|
|
94
|
+
'astc-10x10': shape(10, 10, 16),
|
|
95
|
+
'astc-12x10': shape(12, 10, 16),
|
|
96
|
+
'astc-12x12': shape(12, 12, 16),
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/** Every format's family, which is what a device offers or does not. */
|
|
100
|
+
export function blockFamily(format: BlockFormat): 'bc' | 'etc2' | 'astc' {
|
|
101
|
+
if (format.startsWith('bc')) return 'bc';
|
|
102
|
+
if (format.startsWith('astc')) return 'astc';
|
|
103
|
+
return 'etc2';
|
|
104
|
+
}
|
|
24
105
|
|
|
25
106
|
/** Block-compressed texels with their stored mip chain, level 0 first. */
|
|
26
107
|
export interface CompressedTextureSource {
|
|
@@ -52,15 +133,10 @@ export function compressedLayers(
|
|
|
52
133
|
return sources as readonly CompressedTextureSource[];
|
|
53
134
|
}
|
|
54
135
|
|
|
55
|
-
/** Bytes per
|
|
56
|
-
export const BLOCK_BYTES: Readonly<Record<BlockFormat, number>> =
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
bc3: 16,
|
|
60
|
-
bc4: 8,
|
|
61
|
-
bc5: 16,
|
|
62
|
-
bc7: 16,
|
|
63
|
-
};
|
|
136
|
+
/** Bytes per block. See `BLOCK_SHAPES` for the block's size, which is not always 4x4. */
|
|
137
|
+
export const BLOCK_BYTES: Readonly<Record<BlockFormat, number>> = Object.fromEntries(
|
|
138
|
+
Object.entries(BLOCK_SHAPES).map(([format, block]) => [format, block.bytes]),
|
|
139
|
+
) as Record<BlockFormat, number>;
|
|
64
140
|
|
|
65
141
|
/** Whether `source` is a compressed source rather than an image. */
|
|
66
142
|
export function isCompressedSource(source: unknown): source is CompressedTextureSource {
|
|
@@ -79,13 +155,15 @@ export function compressedFormatName(
|
|
|
79
155
|
srgb: boolean,
|
|
80
156
|
): CompressedTextureFormat | null {
|
|
81
157
|
if (!srgb) return format;
|
|
82
|
-
return format === 'bc4' || format === 'bc5'
|
|
158
|
+
return format === 'bc4' || format === 'bc5' || format === 'eac-r11' || format === 'eac-rg11'
|
|
159
|
+
? null
|
|
160
|
+
: `${format}-srgb`;
|
|
83
161
|
}
|
|
84
162
|
|
|
85
163
|
/**
|
|
86
164
|
* Whether a `width` by `height` source in `format` uploads as blocks on a device offering
|
|
87
|
-
* `available`. Level 0 must be whole blocks each way: WebGPU refuses any other size for a
|
|
88
|
-
* texture, and WebGL2's
|
|
165
|
+
* `available`. Level 0 must be whole blocks each way: WebGPU refuses any other size for a block
|
|
166
|
+
* texture, and WebGL2's extensions say the same, so a 6x6 image in 4x4 blocks is decoded rather than
|
|
89
167
|
* refused. What would make that wrong is a device that pads; none is relied on here.
|
|
90
168
|
*/
|
|
91
169
|
export function uploadsCompressed(
|
|
@@ -96,14 +174,26 @@ export function uploadsCompressed(
|
|
|
96
174
|
available: readonly CompressedTextureFormat[],
|
|
97
175
|
): boolean {
|
|
98
176
|
const name = compressedFormatName(format, srgb);
|
|
99
|
-
|
|
177
|
+
const block = BLOCK_SHAPES[format];
|
|
178
|
+
return (
|
|
179
|
+
name !== null &&
|
|
180
|
+
width % block.width === 0 &&
|
|
181
|
+
height % block.height === 0 &&
|
|
182
|
+
available.includes(name)
|
|
183
|
+
);
|
|
100
184
|
}
|
|
101
185
|
|
|
102
186
|
/** The bytes level `level` of a `width` by `height` image takes: whole blocks, one at least. */
|
|
103
|
-
function levelBytes(
|
|
187
|
+
export function levelBytes(
|
|
188
|
+
format: BlockFormat,
|
|
189
|
+
width: number,
|
|
190
|
+
height: number,
|
|
191
|
+
level: number,
|
|
192
|
+
): number {
|
|
104
193
|
const w = Math.max(1, width >> level);
|
|
105
194
|
const h = Math.max(1, height >> level);
|
|
106
|
-
|
|
195
|
+
const block = BLOCK_SHAPES[format];
|
|
196
|
+
return Math.ceil(w / block.width) * Math.ceil(h / block.height) * block.bytes;
|
|
107
197
|
}
|
|
108
198
|
|
|
109
199
|
/**
|
|
@@ -162,11 +252,13 @@ export function compressedRefusal(
|
|
|
162
252
|
available: readonly CompressedTextureFormat[],
|
|
163
253
|
): string {
|
|
164
254
|
const asked = `${format.toUpperCase()}${srgb ? ' sRGB' : ''} at ${width}x${height}`;
|
|
165
|
-
const offered = available.length === 0 ? 'no
|
|
255
|
+
const offered = available.length === 0 ? 'no block format' : available.join(', ');
|
|
256
|
+
const block = BLOCK_SHAPES[format];
|
|
166
257
|
return (
|
|
167
258
|
`createSurfaceTexture: this device cannot take ${asked} as blocks — it offers ${offered}, and ` +
|
|
168
|
-
|
|
169
|
-
'@driftengine/assets), or ask
|
|
259
|
+
`the texture must be whole ${block.width}x${block.height} blocks at level 0. Decode it first ` +
|
|
260
|
+
'(decodeBc, or encodeEtc2 for a device that takes ETC2, in @driftengine/assets), or ask ' +
|
|
261
|
+
'renderer.compressedFormats before choosing.'
|
|
170
262
|
);
|
|
171
263
|
}
|
|
172
264
|
|
|
@@ -28,6 +28,42 @@ const INTERNAL: Readonly<Record<CompressedTextureFormat, number>> = {
|
|
|
28
28
|
bc5: 0x8dbd, // COMPRESSED_RED_GREEN_RGTC2_EXT
|
|
29
29
|
bc7: 0x8e8c, // COMPRESSED_RGBA_BPTC_UNORM_EXT
|
|
30
30
|
'bc7-srgb': 0x8e8d, // COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT
|
|
31
|
+
'eac-r11': 0x9270, // COMPRESSED_R11_EAC
|
|
32
|
+
'eac-rg11': 0x9272, // COMPRESSED_RG11_EAC
|
|
33
|
+
'etc2-rgb8': 0x9274, // COMPRESSED_RGB8_ETC2
|
|
34
|
+
'etc2-rgb8-srgb': 0x9275, // COMPRESSED_SRGB8_ETC2
|
|
35
|
+
'etc2-rgb8a1': 0x9276, // COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2
|
|
36
|
+
'etc2-rgb8a1-srgb': 0x9277, // COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2
|
|
37
|
+
'etc2-rgba8': 0x9278, // COMPRESSED_RGBA8_ETC2_EAC
|
|
38
|
+
'etc2-rgba8-srgb': 0x9279, // COMPRESSED_SRGB8_ALPHA8_ETC2_EAC
|
|
39
|
+
'astc-4x4': 0x93b0, // COMPRESSED_RGBA_ASTC_4x4_KHR
|
|
40
|
+
'astc-4x4-srgb': 0x93d0, // COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR
|
|
41
|
+
'astc-5x4': 0x93b1, // COMPRESSED_RGBA_ASTC_5x4_KHR
|
|
42
|
+
'astc-5x4-srgb': 0x93d1, // COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR
|
|
43
|
+
'astc-5x5': 0x93b2, // COMPRESSED_RGBA_ASTC_5x5_KHR
|
|
44
|
+
'astc-5x5-srgb': 0x93d2, // COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR
|
|
45
|
+
'astc-6x5': 0x93b3, // COMPRESSED_RGBA_ASTC_6x5_KHR
|
|
46
|
+
'astc-6x5-srgb': 0x93d3, // COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR
|
|
47
|
+
'astc-6x6': 0x93b4, // COMPRESSED_RGBA_ASTC_6x6_KHR
|
|
48
|
+
'astc-6x6-srgb': 0x93d4, // COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR
|
|
49
|
+
'astc-8x5': 0x93b5, // COMPRESSED_RGBA_ASTC_8x5_KHR
|
|
50
|
+
'astc-8x5-srgb': 0x93d5, // COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR
|
|
51
|
+
'astc-8x6': 0x93b6, // COMPRESSED_RGBA_ASTC_8x6_KHR
|
|
52
|
+
'astc-8x6-srgb': 0x93d6, // COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR
|
|
53
|
+
'astc-8x8': 0x93b7, // COMPRESSED_RGBA_ASTC_8x8_KHR
|
|
54
|
+
'astc-8x8-srgb': 0x93d7, // COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR
|
|
55
|
+
'astc-10x5': 0x93b8, // COMPRESSED_RGBA_ASTC_10x5_KHR
|
|
56
|
+
'astc-10x5-srgb': 0x93d8, // COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR
|
|
57
|
+
'astc-10x6': 0x93b9, // COMPRESSED_RGBA_ASTC_10x6_KHR
|
|
58
|
+
'astc-10x6-srgb': 0x93d9, // COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR
|
|
59
|
+
'astc-10x8': 0x93ba, // COMPRESSED_RGBA_ASTC_10x8_KHR
|
|
60
|
+
'astc-10x8-srgb': 0x93da, // COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR
|
|
61
|
+
'astc-10x10': 0x93bb, // COMPRESSED_RGBA_ASTC_10x10_KHR
|
|
62
|
+
'astc-10x10-srgb': 0x93db, // COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR
|
|
63
|
+
'astc-12x10': 0x93bc, // COMPRESSED_RGBA_ASTC_12x10_KHR
|
|
64
|
+
'astc-12x10-srgb': 0x93dc, // COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR
|
|
65
|
+
'astc-12x12': 0x93bd, // COMPRESSED_RGBA_ASTC_12x12_KHR
|
|
66
|
+
'astc-12x12-srgb': 0x93dd, // COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR
|
|
31
67
|
};
|
|
32
68
|
|
|
33
69
|
const BY_EXTENSION: readonly (readonly [string, readonly CompressedTextureFormat[]])[] = [
|
|
@@ -35,6 +71,53 @@ const BY_EXTENSION: readonly (readonly [string, readonly CompressedTextureFormat
|
|
|
35
71
|
['WEBGL_compressed_texture_s3tc_srgb', ['bc1-srgb', 'bc2-srgb', 'bc3-srgb']],
|
|
36
72
|
['EXT_texture_compression_rgtc', ['bc4', 'bc5']],
|
|
37
73
|
['EXT_texture_compression_bptc', ['bc7', 'bc7-srgb']],
|
|
74
|
+
/* The two families a phone has: every GLES 3 device takes ETC2, and most take ASTC beside it. */
|
|
75
|
+
[
|
|
76
|
+
'WEBGL_compressed_texture_etc',
|
|
77
|
+
[
|
|
78
|
+
'etc2-rgb8',
|
|
79
|
+
'etc2-rgb8-srgb',
|
|
80
|
+
'etc2-rgb8a1',
|
|
81
|
+
'etc2-rgb8a1-srgb',
|
|
82
|
+
'etc2-rgba8',
|
|
83
|
+
'etc2-rgba8-srgb',
|
|
84
|
+
'eac-r11',
|
|
85
|
+
'eac-rg11',
|
|
86
|
+
],
|
|
87
|
+
],
|
|
88
|
+
[
|
|
89
|
+
'WEBGL_compressed_texture_astc',
|
|
90
|
+
[
|
|
91
|
+
'astc-4x4',
|
|
92
|
+
'astc-4x4-srgb',
|
|
93
|
+
'astc-5x4',
|
|
94
|
+
'astc-5x4-srgb',
|
|
95
|
+
'astc-5x5',
|
|
96
|
+
'astc-5x5-srgb',
|
|
97
|
+
'astc-6x5',
|
|
98
|
+
'astc-6x5-srgb',
|
|
99
|
+
'astc-6x6',
|
|
100
|
+
'astc-6x6-srgb',
|
|
101
|
+
'astc-8x5',
|
|
102
|
+
'astc-8x5-srgb',
|
|
103
|
+
'astc-8x6',
|
|
104
|
+
'astc-8x6-srgb',
|
|
105
|
+
'astc-8x8',
|
|
106
|
+
'astc-8x8-srgb',
|
|
107
|
+
'astc-10x5',
|
|
108
|
+
'astc-10x5-srgb',
|
|
109
|
+
'astc-10x6',
|
|
110
|
+
'astc-10x6-srgb',
|
|
111
|
+
'astc-10x8',
|
|
112
|
+
'astc-10x8-srgb',
|
|
113
|
+
'astc-10x10',
|
|
114
|
+
'astc-10x10-srgb',
|
|
115
|
+
'astc-12x10',
|
|
116
|
+
'astc-12x10-srgb',
|
|
117
|
+
'astc-12x12',
|
|
118
|
+
'astc-12x12-srgb',
|
|
119
|
+
],
|
|
120
|
+
],
|
|
38
121
|
];
|
|
39
122
|
|
|
40
123
|
/**
|
|
@@ -20,6 +20,8 @@ export interface MediumLight {
|
|
|
20
20
|
readonly sunShadow: number;
|
|
21
21
|
/** World → light clip, **uncorrected**: the shader does its own `* 0.5 + 0.5`. */
|
|
22
22
|
readonly lightViewProj: ReadonlyMat4;
|
|
23
|
+
/** The same for the sun's moving layer, whose shadow pass may be drawn with its own. */
|
|
24
|
+
readonly movingLightViewProj: ReadonlyMat4;
|
|
23
25
|
/** The sun's static, moving and peeled maps, as one array. See `shadowMap.ts`. */
|
|
24
26
|
readonly sunShadows: WebGLTexture;
|
|
25
27
|
readonly peeledEnabled: boolean;
|
|
@@ -240,6 +242,11 @@ export class GlobalMediumPass {
|
|
|
240
242
|
gl.uniform1i(u['uSteps'] ?? null, steps);
|
|
241
243
|
gl.uniform1f(u['uSunShadow'] ?? null, light.sunShadow);
|
|
242
244
|
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, light.lightViewProj as Float32Array);
|
|
245
|
+
gl.uniformMatrix4fv(
|
|
246
|
+
u['uMovingLightViewProj'] ?? null,
|
|
247
|
+
false,
|
|
248
|
+
light.movingLightViewProj as Float32Array,
|
|
249
|
+
);
|
|
243
250
|
gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, light.peeledEnabled ? 1 : 0);
|
|
244
251
|
|
|
245
252
|
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A mesh's indices in the narrowest type that holds them: sixteen bits where every vertex can be
|
|
3
|
+
* named in sixteen, thirty-two otherwise — half the index memory for every mesh of 65,536 vertices
|
|
4
|
+
* or fewer, which is most of them. Lossless because `validateMeshData` has already refused an
|
|
5
|
+
* index past the last vertex.
|
|
6
|
+
*
|
|
7
|
+
* **Padded to an even count**, so the sixteen-bit copy is a whole number of four-byte words: WebGPU
|
|
8
|
+
* writes a buffer only in whole words. The extra index is never drawn, since every draw counts the
|
|
9
|
+
* mesh's own indices. What would make this wrong is a draw that counts the buffer instead.
|
|
10
|
+
*
|
|
11
|
+
* **What it costs** is a copy of the indices while the mesh is built, let go once they are on the
|
|
12
|
+
* device; and every draw of the mesh has to say which width it holds, which is why the width
|
|
13
|
+
* travels with the mesh rather than being worked out where it is drawn.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** The most vertices sixteen-bit indices can name: every index below 65,536. */
|
|
17
|
+
export const MAX_UINT16_VERTICES = 65536;
|
|
18
|
+
|
|
19
|
+
export function compactIndices(
|
|
20
|
+
indices: Uint32Array,
|
|
21
|
+
vertexCount: number,
|
|
22
|
+
): Uint16Array | Uint32Array {
|
|
23
|
+
if (vertexCount > MAX_UINT16_VERTICES) return indices;
|
|
24
|
+
const out = new Uint16Array(indices.length + (indices.length % 2));
|
|
25
|
+
out.set(indices);
|
|
26
|
+
return out;
|
|
27
|
+
}
|
package/src/render/instances.ts
CHANGED
|
@@ -66,6 +66,14 @@ export interface MeshInstances {
|
|
|
66
66
|
*/
|
|
67
67
|
readonly uvRegions?: Float32Array;
|
|
68
68
|
/** How many instances the arrays hold. Fixed at creation; the buffers are sized from it. */
|
|
69
|
+
/**
|
|
70
|
+
* Two floats each, the instance's clock for a batch that plays a bone animation
|
|
71
|
+
* (`InstancedOptions.animation`): its phase in seconds, added to the scene's time once the rate has
|
|
72
|
+
* scaled it, and its rate, 1 for the clip's own speed. **Absent, or an instance past its end, is
|
|
73
|
+
* `(0, 1)`** — every instance at the clip's speed and in step. Read by `uploadInstanced`, as the
|
|
74
|
+
* matrices are; a batch with no animation ignores it.
|
|
75
|
+
*/
|
|
76
|
+
readonly clocks?: Float32Array;
|
|
69
77
|
readonly capacity: number;
|
|
70
78
|
/** How many are live. The rest of the buffer is neither uploaded nor drawn. */
|
|
71
79
|
count: number;
|
|
@@ -140,6 +148,8 @@ export function packInstances(instances: MeshInstances, out: Float32Array): void
|
|
|
140
148
|
}
|
|
141
149
|
}
|
|
142
150
|
|
|
151
|
+
import type { BoneAnimationHandle } from './boneAnimation.ts';
|
|
152
|
+
|
|
143
153
|
/** How a batch made by `createInstanced` behaves. */
|
|
144
154
|
export interface InstancedOptions {
|
|
145
155
|
/**
|
|
@@ -154,4 +164,21 @@ export interface InstancedOptions {
|
|
|
154
164
|
* place gains only the whole-batch test, which it could as well do itself.
|
|
155
165
|
*/
|
|
156
166
|
readonly cull?: boolean;
|
|
167
|
+
/**
|
|
168
|
+
* A bone animation every instance plays (`createBoneAnimation`): each vertex follows the bone its
|
|
169
|
+
* mesh's second coordinates name, at the instance's own moment of the clip (`MeshInstances.clocks`
|
|
170
|
+
* and the clock `setAnimationTime` sets), in the colour pass and in every shadow. The mesh must
|
|
171
|
+
* carry second coordinates. See `boneAnimation.ts`.
|
|
172
|
+
*
|
|
173
|
+
* **An animated batch is culled whole, never instance by instance**: its instances read their
|
|
174
|
+
* clocks by index, which a cull that compacts the survivors would reorder. And its bounds are the
|
|
175
|
+
* rest pose's, so a clip that carries vertices far from it wants a mesh whose bounds hold them.
|
|
176
|
+
*
|
|
177
|
+
* **What it gives up**: under reconstruction or the temporal resolve, a crowd's own movement is not
|
|
178
|
+
* in the frame's motion — the motion pass places a batch by its matrices, not by its clip — so its
|
|
179
|
+
* moving parts are reprojected as though still, which softens a fast clip a little; and a glass
|
|
180
|
+
* batch's coloured shadow is cast from rest. What would make the first wrong is a clip fast enough
|
|
181
|
+
* at the size it is seen to smear, which is then the motion pass's to learn.
|
|
182
|
+
*/
|
|
183
|
+
readonly animation?: BoneAnimationHandle;
|
|
157
184
|
}
|
|
@@ -276,6 +276,17 @@ export const SKIN_BLUR_ALBEDO_UNIT = SKIN_BLUR_DEPTH_UNIT + 1;
|
|
|
276
276
|
export const PARTICLE_SPRITE_UNIT = SKIN_BLUR_ALBEDO_UNIT + 1;
|
|
277
277
|
export const PARTICLE_DEPTH_UNIT = PARTICLE_SPRITE_UNIT + 1;
|
|
278
278
|
|
|
279
|
+
/**
|
|
280
|
+
* A bone animation's places and turns and its batch's clocks (`shaders/boneAnimation.ts`): vertex
|
|
281
|
+
* samplers of the animated instanced program, so they count against the vertex stage's sixteen, not
|
|
282
|
+
* the lit stage's. Numbered past every unit before them rather than sharing the cloth's, which no
|
|
283
|
+
* animated program declares: a unit two programs both write is a binding one draw can leave for the
|
|
284
|
+
* other to read. Twenty-six to twenty-eight, inside the combined pool of thirty-two.
|
|
285
|
+
*/
|
|
286
|
+
export const BONE_PLACES_TEXTURE_UNIT = PARTICLE_DEPTH_UNIT + 1;
|
|
287
|
+
export const BONE_TURNS_TEXTURE_UNIT = BONE_PLACES_TEXTURE_UNIT + 1;
|
|
288
|
+
export const INSTANCE_CLOCKS_TEXTURE_UNIT = BONE_TURNS_TEXTURE_UNIT + 1;
|
|
289
|
+
|
|
279
290
|
/**
|
|
280
291
|
* A cookie's tile, in texels a side.
|
|
281
292
|
*
|
|
@@ -67,3 +67,18 @@ export function computeLightMatrix(
|
|
|
67
67
|
// to express its residual depth tolerance in stable world-space metres.
|
|
68
68
|
return radius * 6;
|
|
69
69
|
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The depth an orthographic light matrix spans, in metres: what `computeLightMatrix` returns, read
|
|
73
|
+
* back off any such matrix. Its depth row is the light's axis scaled by two over the span, so the
|
|
74
|
+
* span is two over that row's length — whatever focus, radius or bearing built it.
|
|
75
|
+
*
|
|
76
|
+
* **For a matrix the renderer is handed rather than one it built**: the sun's moving layer is read
|
|
77
|
+
* through the matrix its shadow pass was drawn with, and its depth tolerance and distance fade are
|
|
78
|
+
* in metres of that matrix. What would make this wrong is a perspective light, whose depth is not
|
|
79
|
+
* linear; the sun's never is.
|
|
80
|
+
*/
|
|
81
|
+
export function orthographicDepthSpan(m: ArrayLike<number>): number {
|
|
82
|
+
const length = Math.hypot(m[2] as number, m[6] as number, m[10] as number);
|
|
83
|
+
return length > 0 ? 2 / length : 1;
|
|
84
|
+
}
|
package/src/render/lightmap.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* light = irradiance(uv) * max(0, dot(d.xyz, n) + d.w)
|
|
16
16
|
* diffuse += albedo * (1 - metal) * light
|
|
17
17
|
*
|
|
18
|
-
*
|
|
18
|
+
* A directional lightmap read, once its own vectors have decoded the
|
|
19
19
|
* page — the first-order spherical harmonic its direction carries, in this engine's axes. **Added
|
|
20
20
|
* to what the dynamic lights give**, and to the ambient: a consumer whose bake already holds the
|
|
21
21
|
* sky's light zeroes the ambient for those draws itself, with `setAmbientSH` and nine zeros.
|