@driftengine/core 4.8.2 → 4.8.4
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 +127 -0
- package/README.md +1 -1
- package/dist/geometry/meshConcat.js +2 -0
- package/dist/index.d.ts +11 -4
- package/dist/index.js +22 -1
- package/dist/math/scalar.js +1 -1
- package/dist/render/anisotropicLobe.d.ts +7 -0
- package/dist/render/anisotropicLobe.js +33 -0
- package/dist/render/backend/api.d.ts +41 -4
- package/dist/render/backend/materialChanges.d.ts +7 -2
- package/dist/render/backend/materialChanges.js +8 -3
- package/dist/render/backend/select.js +8 -0
- package/dist/render/backend/webgl2/clothTextures.d.ts +27 -0
- package/dist/render/backend/webgl2/clothTextures.js +93 -0
- package/dist/render/backend/webgl2/renderer.d.ts +159 -3
- package/dist/render/backend/webgl2/renderer.js +662 -80
- package/dist/render/backend/webgl2/skinScatter.d.ts +50 -0
- package/dist/render/backend/webgl2/skinScatter.js +256 -0
- package/dist/render/backend/webgpu/buffers.d.ts +25 -11
- package/dist/render/backend/webgpu/buffers.js +98 -49
- package/dist/render/backend/webgpu/causticsPass.js +2 -1
- package/dist/render/backend/webgpu/clothSolvePack.d.ts +27 -0
- package/dist/render/backend/webgpu/clothSolvePack.js +129 -0
- package/dist/render/backend/webgpu/clothTextures.d.ts +68 -0
- package/dist/render/backend/webgpu/clothTextures.js +145 -0
- package/dist/render/backend/webgpu/compressedUpload.d.ts +18 -0
- package/dist/render/backend/webgpu/compressedUpload.js +38 -0
- package/dist/render/backend/webgpu/computeHost.d.ts +21 -0
- package/dist/render/backend/webgpu/computeHost.js +5 -0
- package/dist/render/backend/webgpu/depthPass.d.ts +29 -9
- package/dist/render/backend/webgpu/depthPass.js +43 -12
- package/dist/render/backend/webgpu/drawCommand.js +6 -2
- package/dist/render/backend/webgpu/filmPass.js +2 -1
- package/dist/render/backend/webgpu/flatPass.d.ts +43 -4
- package/dist/render/backend/webgpu/flatPass.js +111 -13
- package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +74 -0
- package/dist/render/backend/webgpu/gpuSkinnedCloth.js +452 -0
- package/dist/render/backend/webgpu/linePass.js +2 -1
- package/dist/render/backend/webgpu/motionPass.d.ts +20 -0
- package/dist/render/backend/webgpu/motionPass.js +106 -9
- package/dist/render/backend/webgpu/panelPass.js +2 -1
- package/dist/render/backend/webgpu/particlePass.js +2 -1
- package/dist/render/backend/webgpu/plumePass.js +2 -1
- package/dist/render/backend/webgpu/postPass.d.ts +15 -3
- package/dist/render/backend/webgpu/postPass.js +25 -8
- package/dist/render/backend/webgpu/renderer.d.ts +123 -6
- package/dist/render/backend/webgpu/renderer.js +534 -58
- package/dist/render/backend/webgpu/sdfTextPass.js +2 -1
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +33 -0
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +469 -0
- package/dist/render/backend/webgpu/skinScatterPass.d.ts +51 -0
- package/dist/render/backend/webgpu/skinScatterPass.js +253 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +4 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.js +37 -12
- package/dist/render/backend/webgpu/textPass.js +2 -1
- package/dist/render/backend/webgpu/waterPass.js +2 -1
- package/dist/render/backend/webgpu/windStreakPass.js +2 -1
- package/dist/render/boltBatch.js +2 -1
- package/dist/render/causticsRenderer.js +2 -1
- package/dist/render/clothBindingData.d.ts +41 -0
- package/dist/render/clothBindingData.js +72 -0
- package/dist/render/clothPlacement.d.ts +25 -0
- package/dist/render/clothPlacement.js +85 -0
- package/dist/render/compressedSource.d.ts +66 -0
- package/dist/render/compressedSource.js +138 -0
- package/dist/render/cutoutCaster.d.ts +5 -1
- package/dist/render/cutoutCaster.js +6 -0
- package/dist/render/cutoutDither.d.ts +51 -0
- package/dist/render/cutoutDither.js +90 -0
- package/dist/render/decalPass.js +2 -1
- package/dist/render/eyeRefraction.d.ts +35 -0
- package/dist/render/eyeRefraction.js +40 -0
- package/dist/render/glCompressed.d.ts +27 -0
- package/dist/render/glCompressed.js +53 -0
- package/dist/render/globalMediumPass.js +3 -2
- package/dist/render/hairLobes.d.ts +70 -0
- package/dist/render/hairLobes.js +120 -0
- package/dist/render/lightBudget.d.ts +22 -0
- package/dist/render/lightBudget.js +22 -0
- package/dist/render/lineBatch.js +5 -1
- package/dist/render/mesh.d.ts +7 -0
- package/dist/render/mesh.js +24 -0
- package/dist/render/oitPass.js +2 -1
- package/dist/render/particleBatch.js +3 -2
- package/dist/render/particlePool.d.ts +6 -2
- package/dist/render/plumeRenderer.js +3 -2
- package/dist/render/recon/motionVectors.d.ts +4 -2
- package/dist/render/recon/motionVectors.js +6 -3
- package/dist/render/renderQuality.d.ts +29 -2
- package/dist/render/renderQuality.js +5 -0
- package/dist/render/rendererHarness.js +4 -0
- package/dist/render/sceneCoverage.d.ts +54 -0
- package/dist/render/sceneCoverage.js +72 -0
- package/dist/render/sceneTarget.d.ts +12 -0
- package/dist/render/sceneTarget.js +14 -0
- package/dist/render/sdfTextRenderer.js +2 -1
- package/dist/render/shaders/clothBinding.d.ts +22 -0
- package/dist/render/shaders/clothBinding.js +80 -0
- package/dist/render/shaders/depth.js +45 -8
- package/dist/render/shaders/flat/anisotropic.d.ts +15 -0
- package/dist/render/shaders/flat/anisotropic.js +68 -0
- package/dist/render/shaders/flat/eye.d.ts +1 -0
- package/dist/render/shaders/flat/eye.js +81 -0
- package/dist/render/shaders/flat/hair.d.ts +1 -0
- package/dist/render/shaders/flat/hair.js +101 -0
- package/dist/render/shaders/flat/index.js +42 -3
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +113 -17
- package/dist/render/shaders/flat/models.d.ts +14 -0
- package/dist/render/shaders/flat/models.js +273 -0
- package/dist/render/shaders/flat/preamble.js +26 -24
- package/dist/render/shaders/flat/skin.d.ts +1 -0
- package/dist/render/shaders/flat/skin.js +114 -0
- package/dist/render/shaders/generated/depth.wgsl.d.ts +66 -24
- package/dist/render/shaders/generated/depth.wgsl.js +66 -24
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1815 -1704
- package/dist/render/shaders/generated/flat.wgsl.js +1853 -1742
- package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/line.wgsl.js +1 -1
- package/dist/render/shaders/generated/particle.wgsl.d.ts +3 -3
- package/dist/render/shaders/generated/particle.wgsl.js +3 -3
- package/dist/render/shaders/generated/rush.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/rush.wgsl.js +1 -1
- package/dist/render/shaders/generated/sdfText.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/sdfText.wgsl.js +1 -1
- package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +48 -0
- package/dist/render/shaders/generated/skinBlur.wgsl.js +54 -0
- package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/temporalResolve.wgsl.js +1 -1
- package/dist/render/shaders/line.d.ts +1 -1
- package/dist/render/shaders/outputTransform.d.ts +14 -1
- package/dist/render/shaders/outputTransform.js +23 -1
- package/dist/render/shaders/particle.d.ts +3 -3
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +102 -0
- package/dist/render/shaders/recon/resolve.wgsl.js +15 -1
- package/dist/render/shaders/rush.js +13 -2
- package/dist/render/shaders/sdfText.d.ts +1 -1
- package/dist/render/shaders/skinBlur.d.ts +1 -0
- package/dist/render/shaders/skinBlur.js +103 -0
- package/dist/render/shaders/skinning.d.ts +3 -1
- package/dist/render/shaders/skinning.js +31 -4
- package/dist/render/shaders/temporalResolve.js +12 -5
- package/dist/render/shadowCasters.d.ts +13 -1
- package/dist/render/skinBlur.d.ts +41 -0
- package/dist/render/skinBlur.js +49 -0
- package/dist/render/skinScatter.d.ts +67 -0
- package/dist/render/skinScatter.js +80 -0
- package/dist/render/skinnedClothRun.d.ts +26 -0
- package/dist/render/skinnedClothRun.js +84 -0
- package/dist/render/ssrPass.js +2 -1
- package/dist/render/surfaceModel.d.ts +129 -0
- package/dist/render/surfaceModel.js +152 -0
- package/dist/render/surfaceTexture.d.ts +55 -4
- package/dist/render/surfaceTexture.js +60 -46
- package/dist/render/textRenderer.js +2 -1
- package/dist/render/vertexDefaults.js +8 -0
- package/dist/render/waterRenderer.js +2 -1
- package/dist/render/windStreakRenderer.js +2 -1
- package/package.json +6 -6
- package/src/geometry/meshConcat.ts +2 -0
- package/src/index.ts +53 -1
- package/src/math/scalar.ts +1 -1
- package/src/render/anisotropicLobe.ts +41 -0
- package/src/render/backend/api.ts +52 -3
- package/src/render/backend/materialChanges.ts +8 -3
- package/src/render/backend/select.ts +8 -0
- package/src/render/backend/webgl2/clothTextures.ts +120 -0
- package/src/render/backend/webgl2/renderer.ts +750 -80
- package/src/render/backend/webgl2/skinScatter.ts +309 -0
- package/src/render/backend/webgpu/buffers.ts +137 -63
- package/src/render/backend/webgpu/causticsPass.ts +2 -1
- package/src/render/backend/webgpu/clothSolvePack.ts +161 -0
- package/src/render/backend/webgpu/clothTextures.ts +194 -0
- package/src/render/backend/webgpu/compressedUpload.ts +65 -0
- package/src/render/backend/webgpu/computeHost.ts +25 -0
- package/src/render/backend/webgpu/depthPass.ts +43 -10
- package/src/render/backend/webgpu/drawCommand.ts +6 -2
- package/src/render/backend/webgpu/filmPass.ts +2 -1
- package/src/render/backend/webgpu/flatPass.ts +147 -8
- package/src/render/backend/webgpu/gpuSkinnedCloth.ts +502 -0
- package/src/render/backend/webgpu/linePass.ts +2 -1
- package/src/render/backend/webgpu/motionPass.ts +128 -9
- package/src/render/backend/webgpu/panelPass.ts +2 -1
- package/src/render/backend/webgpu/particlePass.ts +2 -1
- package/src/render/backend/webgpu/plumePass.ts +2 -1
- package/src/render/backend/webgpu/postPass.ts +25 -6
- package/src/render/backend/webgpu/renderer.ts +701 -52
- package/src/render/backend/webgpu/sdfTextPass.ts +2 -1
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +470 -0
- package/src/render/backend/webgpu/skinScatterPass.ts +309 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +42 -12
- package/src/render/backend/webgpu/textPass.ts +2 -1
- package/src/render/backend/webgpu/waterPass.ts +2 -1
- package/src/render/backend/webgpu/windStreakPass.ts +2 -1
- package/src/render/boltBatch.ts +2 -1
- package/src/render/causticsRenderer.ts +2 -1
- package/src/render/clothBindingData.ts +89 -0
- package/src/render/clothPlacement.ts +114 -0
- package/src/render/compressedSource.ts +198 -0
- package/src/render/cutoutCaster.ts +20 -2
- package/src/render/cutoutDither.ts +99 -0
- package/src/render/decalPass.ts +2 -1
- package/src/render/eyeRefraction.ts +72 -0
- package/src/render/glCompressed.ts +86 -0
- package/src/render/globalMediumPass.ts +3 -2
- package/src/render/hairLobes.ts +188 -0
- package/src/render/lightBudget.ts +25 -0
- package/src/render/lineBatch.ts +3 -1
- package/src/render/mesh.ts +23 -0
- package/src/render/oitPass.ts +2 -1
- package/src/render/particleBatch.ts +3 -2
- package/src/render/particlePool.ts +6 -2
- package/src/render/plumeRenderer.ts +3 -2
- package/src/render/recon/motionVectors.ts +6 -3
- package/src/render/renderQuality.ts +37 -2
- package/src/render/rendererHarness.ts +4 -0
- package/src/render/sceneCoverage.ts +78 -0
- package/src/render/sceneTarget.ts +21 -0
- package/src/render/sdfTextRenderer.ts +2 -1
- package/src/render/shaders/clothBinding.ts +81 -0
- package/src/render/shaders/depth.ts +45 -8
- package/src/render/shaders/flat/anisotropic.ts +68 -0
- package/src/render/shaders/flat/eye.ts +82 -0
- package/src/render/shaders/flat/hair.ts +102 -0
- package/src/render/shaders/flat/index.ts +42 -3
- package/src/render/shaders/flat/main.ts +113 -17
- package/src/render/shaders/flat/models.ts +286 -0
- package/src/render/shaders/flat/preamble.ts +26 -24
- package/src/render/shaders/flat/skin.ts +116 -0
- package/src/render/shaders/generated/depth.wgsl.ts +66 -24
- package/src/render/shaders/generated/flat.wgsl.ts +1853 -1742
- package/src/render/shaders/generated/line.wgsl.ts +1 -1
- package/src/render/shaders/generated/particle.wgsl.ts +3 -3
- package/src/render/shaders/generated/rush.wgsl.ts +1 -1
- package/src/render/shaders/generated/sdfText.wgsl.ts +1 -1
- package/src/render/shaders/generated/skinBlur.wgsl.ts +56 -0
- package/src/render/shaders/generated/temporalResolve.wgsl.ts +1 -1
- package/src/render/shaders/outputTransform.ts +24 -1
- package/src/render/shaders/recon/motion.wgsl.ts +102 -0
- package/src/render/shaders/recon/resolve.wgsl.ts +15 -1
- package/src/render/shaders/rush.ts +13 -2
- package/src/render/shaders/skinBlur.ts +105 -0
- package/src/render/shaders/skinning.ts +31 -4
- package/src/render/shaders/temporalResolve.ts +12 -5
- package/src/render/shadowCasters.ts +13 -0
- package/src/render/skinBlur.ts +53 -0
- package/src/render/skinScatter.ts +92 -0
- package/src/render/skinnedClothRun.ts +128 -0
- package/src/render/ssrPass.ts +2 -1
- package/src/render/surfaceModel.ts +274 -0
- package/src/render/surfaceTexture.ts +134 -63
- package/src/render/textRenderer.ts +2 -1
- package/src/render/vertexDefaults.ts +8 -0
- package/src/render/waterRenderer.ts +2 -1
- package/src/render/windStreakRenderer.ts +2 -1
- package/dist/math/exact.d.ts +0 -76
- package/dist/math/exact.js +0 -327
- package/src/math/exact.ts +0 -340
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { SceneTarget } from '../../sceneTarget.ts';
|
|
2
|
+
export declare class GlSkinScatter {
|
|
3
|
+
private readonly gl;
|
|
4
|
+
private readonly program;
|
|
5
|
+
private readonly uniforms;
|
|
6
|
+
private readonly vao;
|
|
7
|
+
/** Each profile's scatter distance per channel, in metres: four floats a profile. */
|
|
8
|
+
private readonly profiles;
|
|
9
|
+
private readonly step;
|
|
10
|
+
private readonly depthToViewZ;
|
|
11
|
+
private diffuse;
|
|
12
|
+
private across;
|
|
13
|
+
private multisampled;
|
|
14
|
+
private drawFramebuffer;
|
|
15
|
+
private resolveFramebuffer;
|
|
16
|
+
private acrossFramebuffer;
|
|
17
|
+
private width;
|
|
18
|
+
private height;
|
|
19
|
+
private samples;
|
|
20
|
+
private sharedDepth;
|
|
21
|
+
/** Refused once and for good: a driver that will not render to half floats. */
|
|
22
|
+
private unavailable;
|
|
23
|
+
private cleared;
|
|
24
|
+
private drawn;
|
|
25
|
+
constructor(gl: WebGL2RenderingContext);
|
|
26
|
+
/** Whether any skin's diffuse was drawn this frame. */
|
|
27
|
+
get pending(): boolean;
|
|
28
|
+
/** What one profile scatters, per channel, in metres: the last material to name it says. */
|
|
29
|
+
setProfile(index: number, r: number, g: number, b: number): void;
|
|
30
|
+
/**
|
|
31
|
+
* Whether a skin can be drawn in two halves now: the target fits the frame and shares its depth.
|
|
32
|
+
* False where this driver will not render to half floats, and then skin is drawn whole.
|
|
33
|
+
*/
|
|
34
|
+
ready(scene: SceneTarget): boolean;
|
|
35
|
+
/** Bind the target for one skin's diffuse half, cleared the first time a frame does. */
|
|
36
|
+
begin(): void;
|
|
37
|
+
/**
|
|
38
|
+
* Across into the second target, then down and added into the frame, which `scene` binds. `depth`
|
|
39
|
+
* is the frame's depth as a single-sampled texture, or null where none could be had — and then
|
|
40
|
+
* the diffuse goes back unspread rather than not at all, so the skin keeps its light.
|
|
41
|
+
*/
|
|
42
|
+
spread(scene: SceneTarget, depth: WebGLTexture | null, empty: WebGLTexture, projection: ArrayLike<number>, inverse: ArrayLike<number>): void;
|
|
43
|
+
/** Spent at the frame's start: what was drawn into the target was last frame's. */
|
|
44
|
+
reset(): void;
|
|
45
|
+
dispose(): void;
|
|
46
|
+
private allocate;
|
|
47
|
+
private refuse;
|
|
48
|
+
private halfFloatTexture;
|
|
49
|
+
private release;
|
|
50
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skin's screen-space scattering on WebGL2: the target skin's diffuse half is drawn into, and the
|
|
3
|
+
* two passes that spread it. `skinBlur.ts` is what the blur computes and why; the WebGPU backend's
|
|
4
|
+
* `skinScatterPass.ts` is the same thing in that backend's terms.
|
|
5
|
+
*
|
|
6
|
+
* **Drawn as it goes, not kept**: this backend issues a draw when it is asked for, and the state a
|
|
7
|
+
* skin draw reads — its joint palette, its uniforms — is rewritten by the draw after it. So the
|
|
8
|
+
* renderer draws each skin twice in place, the frame's half and then at once its diffuse half into
|
|
9
|
+
* this target, which shares the frame's depth and tests for equality against it.
|
|
10
|
+
*
|
|
11
|
+
* **The units it reads are its own** — `SKIN_BLUR_TEXTURE_UNIT` and the one after — past every
|
|
12
|
+
* unit a lit program binds, because it runs in the middle of a frame and the draws after it must
|
|
13
|
+
* find their textures where they left them.
|
|
14
|
+
*/
|
|
15
|
+
import { SKIN_BLUR_DEPTH_UNIT, SKIN_BLUR_TEXTURE_UNIT } from '../../lightBudget.js';
|
|
16
|
+
import { blendKeeping } from '../../sceneCoverage.js';
|
|
17
|
+
import { compileProgram, uniformLocations } from '../../shader.js';
|
|
18
|
+
import { FULLSCREEN_VERT } from '../../shaders/fullscreen.js';
|
|
19
|
+
import { SKIN_BLUR_FRAG } from '../../shaders/skinBlur.js';
|
|
20
|
+
import { SKIN_PROFILES } from '../../skinBlur.js';
|
|
21
|
+
const CLEAR = new Float32Array(4);
|
|
22
|
+
export class GlSkinScatter {
|
|
23
|
+
gl;
|
|
24
|
+
program;
|
|
25
|
+
uniforms;
|
|
26
|
+
vao;
|
|
27
|
+
/** Each profile's scatter distance per channel, in metres: four floats a profile. */
|
|
28
|
+
profiles = new Float32Array(SKIN_PROFILES * 4);
|
|
29
|
+
step = new Float32Array(2);
|
|
30
|
+
depthToViewZ = new Float32Array(4);
|
|
31
|
+
diffuse = null;
|
|
32
|
+
across = null;
|
|
33
|
+
multisampled = null;
|
|
34
|
+
drawFramebuffer = null;
|
|
35
|
+
resolveFramebuffer = null;
|
|
36
|
+
acrossFramebuffer = null;
|
|
37
|
+
width = 0;
|
|
38
|
+
height = 0;
|
|
39
|
+
samples = 1;
|
|
40
|
+
sharedDepth = null;
|
|
41
|
+
/** Refused once and for good: a driver that will not render to half floats. */
|
|
42
|
+
unavailable = false;
|
|
43
|
+
cleared = false;
|
|
44
|
+
drawn = false;
|
|
45
|
+
constructor(gl) {
|
|
46
|
+
this.gl = gl;
|
|
47
|
+
this.program = compileProgram(gl, FULLSCREEN_VERT, SKIN_BLUR_FRAG, 'skinBlur');
|
|
48
|
+
this.uniforms = uniformLocations(gl, this.program, 'skinBlur');
|
|
49
|
+
const vao = gl.createVertexArray();
|
|
50
|
+
if (vao === null)
|
|
51
|
+
throw new Error('GlSkinScatter: createVertexArray failed');
|
|
52
|
+
this.vao = vao;
|
|
53
|
+
/*
|
|
54
|
+
* Rendering to half floats is the extension's, not WebGL2's — the scene target asks for it
|
|
55
|
+
* only when it keeps range, so this asks for itself. Without it, skin is drawn whole.
|
|
56
|
+
*/
|
|
57
|
+
if (gl.getExtension('EXT_color_buffer_float') === null)
|
|
58
|
+
this.refuse();
|
|
59
|
+
}
|
|
60
|
+
/** Whether any skin's diffuse was drawn this frame. */
|
|
61
|
+
get pending() {
|
|
62
|
+
return this.drawn;
|
|
63
|
+
}
|
|
64
|
+
/** What one profile scatters, per channel, in metres: the last material to name it says. */
|
|
65
|
+
setProfile(index, r, g, b) {
|
|
66
|
+
const at = Math.min(Math.max(Math.round(index), 0), SKIN_PROFILES - 1) * 4;
|
|
67
|
+
this.profiles[at] = r;
|
|
68
|
+
this.profiles[at + 1] = g;
|
|
69
|
+
this.profiles[at + 2] = b;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Whether a skin can be drawn in two halves now: the target fits the frame and shares its depth.
|
|
73
|
+
* False where this driver will not render to half floats, and then skin is drawn whole.
|
|
74
|
+
*/
|
|
75
|
+
ready(scene) {
|
|
76
|
+
if (this.unavailable)
|
|
77
|
+
return false;
|
|
78
|
+
const depth = scene.sharedDepth();
|
|
79
|
+
const attached = depth.renderbuffer ?? depth.texture;
|
|
80
|
+
if (attached === null)
|
|
81
|
+
return false;
|
|
82
|
+
if (this.drawFramebuffer !== null &&
|
|
83
|
+
this.width === depth.width &&
|
|
84
|
+
this.height === depth.height &&
|
|
85
|
+
this.samples === depth.samples &&
|
|
86
|
+
this.sharedDepth === attached) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
const made = this.allocate(depth.width, depth.height, depth.samples, depth.texture, depth.renderbuffer);
|
|
90
|
+
/* Asked in the middle of a frame: the frame's own framebuffer goes back as it was. */
|
|
91
|
+
scene.bind();
|
|
92
|
+
return made;
|
|
93
|
+
}
|
|
94
|
+
/** Bind the target for one skin's diffuse half, cleared the first time a frame does. */
|
|
95
|
+
begin() {
|
|
96
|
+
const { gl } = this;
|
|
97
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.drawFramebuffer);
|
|
98
|
+
gl.viewport(0, 0, this.width, this.height);
|
|
99
|
+
if (!this.cleared) {
|
|
100
|
+
gl.clearBufferfv(gl.COLOR, 0, CLEAR);
|
|
101
|
+
this.cleared = true;
|
|
102
|
+
}
|
|
103
|
+
this.drawn = true;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Across into the second target, then down and added into the frame, which `scene` binds. `depth`
|
|
107
|
+
* is the frame's depth as a single-sampled texture, or null where none could be had — and then
|
|
108
|
+
* the diffuse goes back unspread rather than not at all, so the skin keeps its light.
|
|
109
|
+
*/
|
|
110
|
+
spread(scene, depth, empty, projection, inverse) {
|
|
111
|
+
const { gl } = this;
|
|
112
|
+
if (this.multisampled !== null) {
|
|
113
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.drawFramebuffer);
|
|
114
|
+
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.resolveFramebuffer);
|
|
115
|
+
gl.blitFramebuffer(0, 0, this.width, this.height, 0, 0, this.width, this.height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
|
|
116
|
+
}
|
|
117
|
+
gl.disable(gl.DEPTH_TEST);
|
|
118
|
+
gl.disable(gl.CULL_FACE);
|
|
119
|
+
gl.disable(gl.BLEND);
|
|
120
|
+
gl.depthMask(false);
|
|
121
|
+
gl.bindVertexArray(this.vao);
|
|
122
|
+
gl.useProgram(this.program);
|
|
123
|
+
const u = this.uniforms;
|
|
124
|
+
gl.uniform1i(u['uSkin'] ?? null, SKIN_BLUR_TEXTURE_UNIT);
|
|
125
|
+
gl.uniform1i(u['uDepth'] ?? null, SKIN_BLUR_DEPTH_UNIT);
|
|
126
|
+
/* The four terms that carry a depth back to view-space metres: the occlusion blur's own. */
|
|
127
|
+
this.depthToViewZ[0] = inverse[10] ?? 0;
|
|
128
|
+
this.depthToViewZ[1] = inverse[14] ?? 0;
|
|
129
|
+
this.depthToViewZ[2] = inverse[11] ?? 0;
|
|
130
|
+
this.depthToViewZ[3] = inverse[15] ?? 1;
|
|
131
|
+
gl.uniform4fv(u['uDepthToViewZ'] ?? null, this.depthToViewZ);
|
|
132
|
+
/* Pixels a metre spans at a metre; nothing to measure against without a depth, so nothing spreads. */
|
|
133
|
+
gl.uniform1f(u['uFocal'] ?? null, depth === null ? 0 : (projection[5] * this.height) / 2);
|
|
134
|
+
gl.uniform4fv(u['uProfiles'] ?? null, this.profiles);
|
|
135
|
+
gl.activeTexture(gl.TEXTURE0 + SKIN_BLUR_DEPTH_UNIT);
|
|
136
|
+
gl.bindTexture(gl.TEXTURE_2D, depth ?? empty);
|
|
137
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.acrossFramebuffer);
|
|
138
|
+
gl.viewport(0, 0, this.width, this.height);
|
|
139
|
+
gl.activeTexture(gl.TEXTURE0 + SKIN_BLUR_TEXTURE_UNIT);
|
|
140
|
+
gl.bindTexture(gl.TEXTURE_2D, this.diffuse);
|
|
141
|
+
this.step[0] = 1 / this.width;
|
|
142
|
+
this.step[1] = 0;
|
|
143
|
+
gl.uniform2fv(u['uStep'] ?? null, this.step);
|
|
144
|
+
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
145
|
+
scene.bind();
|
|
146
|
+
gl.bindTexture(gl.TEXTURE_2D, this.across);
|
|
147
|
+
this.step[0] = 0;
|
|
148
|
+
this.step[1] = 1 / this.height;
|
|
149
|
+
gl.uniform2fv(u['uStep'] ?? null, this.step);
|
|
150
|
+
/* Added: its colour onto the frame's, the frame's alpha kept. */
|
|
151
|
+
gl.enable(gl.BLEND);
|
|
152
|
+
gl.blendEquation(gl.FUNC_ADD);
|
|
153
|
+
blendKeeping(gl, gl.ONE, gl.ONE);
|
|
154
|
+
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
155
|
+
/* The world pass's own state, as the next draw expects to find it. */
|
|
156
|
+
gl.disable(gl.BLEND);
|
|
157
|
+
gl.enable(gl.DEPTH_TEST);
|
|
158
|
+
gl.enable(gl.CULL_FACE);
|
|
159
|
+
gl.depthMask(true);
|
|
160
|
+
gl.bindVertexArray(null);
|
|
161
|
+
}
|
|
162
|
+
/** Spent at the frame's start: what was drawn into the target was last frame's. */
|
|
163
|
+
reset() {
|
|
164
|
+
this.cleared = false;
|
|
165
|
+
this.drawn = false;
|
|
166
|
+
}
|
|
167
|
+
dispose() {
|
|
168
|
+
const { gl } = this;
|
|
169
|
+
this.release();
|
|
170
|
+
gl.deleteProgram(this.program);
|
|
171
|
+
gl.deleteVertexArray(this.vao);
|
|
172
|
+
}
|
|
173
|
+
allocate(width, height, samples, depthTexture, depthRenderbuffer) {
|
|
174
|
+
const { gl } = this;
|
|
175
|
+
this.release();
|
|
176
|
+
this.width = width;
|
|
177
|
+
this.height = height;
|
|
178
|
+
this.samples = samples;
|
|
179
|
+
/* Its own unit, so making its textures unbinds nothing a lit program reads. */
|
|
180
|
+
gl.activeTexture(gl.TEXTURE0 + SKIN_BLUR_TEXTURE_UNIT);
|
|
181
|
+
this.sharedDepth = depthRenderbuffer ?? depthTexture;
|
|
182
|
+
this.diffuse = this.halfFloatTexture(width, height);
|
|
183
|
+
this.across = this.halfFloatTexture(width, height);
|
|
184
|
+
this.drawFramebuffer = gl.createFramebuffer();
|
|
185
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.drawFramebuffer);
|
|
186
|
+
if (samples > 1 && depthRenderbuffer !== null) {
|
|
187
|
+
this.multisampled = gl.createRenderbuffer();
|
|
188
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, this.multisampled);
|
|
189
|
+
gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.RGBA16F, width, height);
|
|
190
|
+
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, this.multisampled);
|
|
191
|
+
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthRenderbuffer);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.diffuse, 0);
|
|
195
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTexture, 0);
|
|
196
|
+
}
|
|
197
|
+
const drawReady = gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE;
|
|
198
|
+
if (this.multisampled !== null) {
|
|
199
|
+
this.resolveFramebuffer = gl.createFramebuffer();
|
|
200
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.resolveFramebuffer);
|
|
201
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.diffuse, 0);
|
|
202
|
+
}
|
|
203
|
+
this.acrossFramebuffer = gl.createFramebuffer();
|
|
204
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.acrossFramebuffer);
|
|
205
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.across, 0);
|
|
206
|
+
const acrossReady = gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE;
|
|
207
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
|
|
208
|
+
if (drawReady && acrossReady)
|
|
209
|
+
return true;
|
|
210
|
+
this.release();
|
|
211
|
+
this.refuse();
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
refuse() {
|
|
215
|
+
if (this.unavailable)
|
|
216
|
+
return;
|
|
217
|
+
this.unavailable = true;
|
|
218
|
+
console.warn("WebGL2: this driver will not render skin's diffuse to half floats, so skinScattering " +
|
|
219
|
+
"'screen-space' draws skin pre-integrated. The frame is otherwise unaffected.");
|
|
220
|
+
}
|
|
221
|
+
halfFloatTexture(width, height) {
|
|
222
|
+
const { gl } = this;
|
|
223
|
+
const texture = gl.createTexture();
|
|
224
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
225
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, width, height, 0, gl.RGBA, gl.HALF_FLOAT, null);
|
|
226
|
+
/* Linear: the blur takes taps between texels, and reads a part-covered one for the part it is. */
|
|
227
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
228
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
229
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
230
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
231
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
232
|
+
return texture;
|
|
233
|
+
}
|
|
234
|
+
release() {
|
|
235
|
+
const { gl } = this;
|
|
236
|
+
if (this.drawFramebuffer !== null)
|
|
237
|
+
gl.deleteFramebuffer(this.drawFramebuffer);
|
|
238
|
+
if (this.resolveFramebuffer !== null)
|
|
239
|
+
gl.deleteFramebuffer(this.resolveFramebuffer);
|
|
240
|
+
if (this.acrossFramebuffer !== null)
|
|
241
|
+
gl.deleteFramebuffer(this.acrossFramebuffer);
|
|
242
|
+
if (this.multisampled !== null)
|
|
243
|
+
gl.deleteRenderbuffer(this.multisampled);
|
|
244
|
+
if (this.diffuse !== null)
|
|
245
|
+
gl.deleteTexture(this.diffuse);
|
|
246
|
+
if (this.across !== null)
|
|
247
|
+
gl.deleteTexture(this.across);
|
|
248
|
+
this.drawFramebuffer = null;
|
|
249
|
+
this.resolveFramebuffer = null;
|
|
250
|
+
this.acrossFramebuffer = null;
|
|
251
|
+
this.multisampled = null;
|
|
252
|
+
this.diffuse = null;
|
|
253
|
+
this.across = null;
|
|
254
|
+
this.sharedDepth = null;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
@@ -42,14 +42,16 @@ export interface GpuMesh {
|
|
|
42
42
|
*
|
|
43
43
|
* **Two, and the first is interleaved** — every attribute this mesh actually supplied, woven
|
|
44
44
|
* into one stride — while the second holds one value per *absent* optional attribute at a stride
|
|
45
|
-
* of zero.
|
|
45
|
+
* of zero. **A dynamic mesh has four**: its positions and its normals, each a buffer of its own so
|
|
46
|
+
* `update` writes them alone, then the interleaved rest and the constants. It is emphatically not one buffer per attribute, which this comment said until
|
|
46
47
|
* 2026-09-17 and which cost the motion pass an afternoon: a pipeline built to that description
|
|
47
48
|
* reads a position out of every twelve bytes of an interleaved vertex, draws geometry that is
|
|
48
49
|
* not the mesh, and fails no validation at all.
|
|
49
50
|
*/
|
|
50
51
|
readonly vertexBuffers: readonly GPUBuffer[];
|
|
51
52
|
/**
|
|
52
|
-
* Bytes a vertex takes in the first of those buffers
|
|
53
|
+
* Bytes a vertex takes in the first of those buffers: the interleaved stride, or twelve for a
|
|
54
|
+
* dynamic mesh, whose first buffer is its positions.
|
|
53
55
|
*
|
|
54
56
|
* Kept because it is a function of which *optional* attributes this mesh supplied, so it differs
|
|
55
57
|
* between two meshes drawn by the same pipeline — and a pass that reads only the position still
|
|
@@ -62,10 +64,17 @@ export interface GpuMesh {
|
|
|
62
64
|
* Byte offsets of the joint indices and weights in the interleaved vertex, or null for a mesh
|
|
63
65
|
* that supplied neither. For a pass that skins through a shader of its own — the motion pass —
|
|
64
66
|
* which has to find them without the `present` map the generated pipelines are keyed by.
|
|
67
|
+
*
|
|
68
|
+
* **Null on a dynamic mesh too**, whose first buffer holds positions alone: the motion pass reads
|
|
69
|
+
* joints from that buffer, so a dynamic skinned mesh's motion between rewrites is its model's
|
|
70
|
+
* rather than its pose's. A rewrite carries its own motion, from last frame's positions.
|
|
65
71
|
*/
|
|
66
72
|
readonly skinOffsets: {
|
|
67
73
|
readonly joints: number;
|
|
68
74
|
readonly weights: number;
|
|
75
|
+
/** The second four influences' offsets, or -1 for a mesh with four. */
|
|
76
|
+
readonly joints2: number;
|
|
77
|
+
readonly weights2: number;
|
|
69
78
|
} | null;
|
|
70
79
|
readonly indexBuffer: GPUBuffer;
|
|
71
80
|
/**
|
|
@@ -114,6 +123,10 @@ export interface GpuMesh {
|
|
|
114
123
|
* the key is built before the present map is consulted.
|
|
115
124
|
*/
|
|
116
125
|
readonly isSkinned: boolean;
|
|
126
|
+
/** Whether its rig moves a vertex by eight influences, which keys a pipeline with `SKIN_EIGHT`. */
|
|
127
|
+
readonly isSkinnedEight: boolean;
|
|
128
|
+
/** How many vertices it has, which a cloth binding is checked against. */
|
|
129
|
+
readonly vertexCount: number;
|
|
117
130
|
/** Whether it has texture coordinates, without which a material's maps read one texel. */
|
|
118
131
|
readonly hasUvs: boolean;
|
|
119
132
|
/**
|
|
@@ -135,18 +148,19 @@ export interface GpuMesh {
|
|
|
135
148
|
/**
|
|
136
149
|
* Rewrite this mesh's positions, and its normals where the caller has them.
|
|
137
150
|
*
|
|
138
|
-
* Absent unless the mesh was created `dynamic`. **
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
151
|
+
* Absent unless the mesh was created `dynamic`. **A dynamic mesh's positions and normals are
|
|
152
|
+
* buffers of their own**, so this is one `writeBuffer` of the caller's positions and one of its
|
|
153
|
+
* normals, and nothing else moves. Until 4.8.4 every attribute was interleaved into one buffer,
|
|
154
|
+
* and a dynamic mesh kept a CPU copy of it to patch and upload whole — a garment's colours,
|
|
155
|
+
* coordinates, tangents and joints re-sent every frame for the twenty-four bytes a vertex that
|
|
156
|
+
* changed, reported at 2.8 MB a frame for one character. What the split costs is a pipeline per
|
|
157
|
+
* layout, keyed by `:dynamic`, and two more buffers bound per draw of such a mesh.
|
|
144
158
|
*/
|
|
145
159
|
readonly update: ((device: GPUDevice, positions: Float32Array, normals?: Float32Array, previous?: GPUBuffer | null) => void) | null;
|
|
146
160
|
/**
|
|
147
|
-
* A dynamic mesh's motion state, which the renderer owns: a buffer for last frame's
|
|
148
|
-
*
|
|
149
|
-
*
|
|
161
|
+
* A dynamic mesh's motion state, which the renderer owns: a buffer for last frame's positions,
|
|
162
|
+
* made the first time a reconstructing renderer rewrites the mesh, and the frame of the last
|
|
163
|
+
* rewrite. Null on a mesh created without `{ dynamic: true }`, which never moves a vertex.
|
|
150
164
|
*/
|
|
151
165
|
readonly motion: {
|
|
152
166
|
previous: GPUBuffer | null;
|
|
@@ -20,6 +20,7 @@ import { INSTANCE_STRIDE } from '../../instances.js';
|
|
|
20
20
|
* without one.
|
|
21
21
|
*/
|
|
22
22
|
const USAGE = {
|
|
23
|
+
COPY_SRC: 0x0004,
|
|
23
24
|
COPY_DST: 0x0008,
|
|
24
25
|
INDEX: 0x0010,
|
|
25
26
|
VERTEX: 0x0020,
|
|
@@ -83,6 +84,13 @@ export const VERTEX_LAYOUT = [
|
|
|
83
84
|
* three lanes and a spare live here instead of three attributes that do not fit.
|
|
84
85
|
*/
|
|
85
86
|
{ name: 'channel', shaderLocation: 13, format: 'float32x4', components: 4, optional: true },
|
|
87
|
+
/*
|
|
88
|
+
* The fifth to eighth influences, at the last two locations. Only a skinned variant reads them,
|
|
89
|
+
* and only with `SKIN_EIGHT` on; every other mesh is fed zeros by the constants buffer. An
|
|
90
|
+
* instanced pipeline reclaims 14 and 15 with the rest of 11 to 15, as `INSTANCE_LOCATIONS` says.
|
|
91
|
+
*/
|
|
92
|
+
{ name: 'joints2', shaderLocation: 14, format: 'float32x4', components: 4, optional: true },
|
|
93
|
+
{ name: 'weights2', shaderLocation: 15, format: 'float32x4', components: 4, optional: true },
|
|
86
94
|
];
|
|
87
95
|
/** Geometry on the device, with the buffers bound in layout order. */
|
|
88
96
|
/**
|
|
@@ -152,8 +160,10 @@ export function createGpuMeshIncremental(device, data, dynamic = false) {
|
|
|
152
160
|
const bounds = boundsOfPositions(data.positions, createBounds());
|
|
153
161
|
const vertexCount = data.positions.length / 3;
|
|
154
162
|
const layered = data.layers !== undefined;
|
|
155
|
-
const supplied = VERTEX_LAYOUT.filter((attribute) => !attribute.optional || data[attribute.name] !== undefined)
|
|
156
|
-
|
|
163
|
+
const supplied = VERTEX_LAYOUT.filter((attribute) => (!attribute.optional || data[attribute.name] !== undefined) &&
|
|
164
|
+
!(dynamic && isStreamed(attribute))).map((attribute) => laidOut(attribute, layered));
|
|
165
|
+
/* Interleaved: one stride holding every attribute the mesh actually supplied — less a dynamic
|
|
166
|
+
mesh's positions and normals, which are buffers of their own. */
|
|
157
167
|
const stride = supplied.reduce((sum, attribute) => sum + attribute.components * 4, 0);
|
|
158
168
|
const step = stride / 4;
|
|
159
169
|
const interleaved = new Float32Array(step * vertexCount);
|
|
@@ -212,27 +222,27 @@ export function createGpuMeshIncremental(device, data, dynamic = false) {
|
|
|
212
222
|
size: align4(data.indices.byteLength),
|
|
213
223
|
usage: USAGE.INDEX | USAGE.COPY_DST,
|
|
214
224
|
});
|
|
215
|
-
/*
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
225
|
+
/* A dynamic mesh's two moving streams: copied from as well as written, for last frame's positions. */
|
|
226
|
+
const streamBytes = align4(vertexCount * 12);
|
|
227
|
+
const streams = dynamic
|
|
228
|
+
? {
|
|
229
|
+
positions: device.createBuffer({
|
|
230
|
+
label: 'mesh.positions',
|
|
231
|
+
size: streamBytes,
|
|
232
|
+
usage: USAGE.VERTEX | USAGE.COPY_DST | USAGE.COPY_SRC,
|
|
233
|
+
}),
|
|
234
|
+
normals: device.createBuffer({
|
|
235
|
+
label: 'mesh.normals',
|
|
236
|
+
size: streamBytes,
|
|
237
|
+
usage: USAGE.VERTEX | USAGE.COPY_DST,
|
|
238
|
+
}),
|
|
239
|
+
}
|
|
240
|
+
: null;
|
|
231
241
|
/*
|
|
232
242
|
* The chunks, in whole vertices and whole indices: a step never writes half of either, so the
|
|
233
243
|
* buffer never holds a torn one even for the frame between two steps.
|
|
234
244
|
*/
|
|
235
|
-
const verticesPerStep = Math.max(1, Math.floor(UPLOAD_BYTES_PER_STEP / stride));
|
|
245
|
+
const verticesPerStep = Math.max(1, Math.floor(UPLOAD_BYTES_PER_STEP / (stride + (dynamic ? 24 : 0))));
|
|
236
246
|
const indexWidth = data.indices.BYTES_PER_ELEMENT;
|
|
237
247
|
const indicesPerStep = Math.max(1, Math.floor(UPLOAD_BYTES_PER_STEP / indexWidth));
|
|
238
248
|
const totalSteps = Math.max(1, Math.ceil(vertexCount / verticesPerStep)) +
|
|
@@ -250,28 +260,32 @@ export function createGpuMeshIncremental(device, data, dynamic = false) {
|
|
|
250
260
|
const mesh = gpuMeshOf({
|
|
251
261
|
vertices,
|
|
252
262
|
constants,
|
|
263
|
+
streams,
|
|
253
264
|
indexBuffer,
|
|
254
|
-
stride,
|
|
265
|
+
stride: streams === null ? stride : 12,
|
|
255
266
|
indexCount: data.indices.length,
|
|
256
267
|
bounds,
|
|
257
268
|
progress,
|
|
258
269
|
hasTangents: data.tangents !== undefined,
|
|
259
270
|
isSkinned: data.joints !== undefined,
|
|
271
|
+
isSkinnedEight: data.joints !== undefined && data.joints2 !== undefined,
|
|
272
|
+
vertexCount,
|
|
260
273
|
hasUvs: data.uvs !== undefined,
|
|
261
|
-
skinOffsets: skinOffsetsOf(supplied),
|
|
274
|
+
skinOffsets: streams === null ? skinOffsetsOf(supplied) : null,
|
|
262
275
|
hasChannel: data.channel !== undefined,
|
|
263
276
|
morph: data.morphTargets === undefined || data.morphTargetCount === undefined
|
|
264
277
|
? null
|
|
265
278
|
: new MorphTexture(device, data.morphTargets, data.positions.length / 3, data.morphTargetCount),
|
|
266
|
-
update:
|
|
279
|
+
update: streams === null
|
|
267
280
|
? null
|
|
268
|
-
:
|
|
281
|
+
: streamUpdate(streams.positions, streams.normals, streamBytes, bounds, vertexCount),
|
|
269
282
|
});
|
|
270
283
|
const job = {
|
|
271
284
|
data,
|
|
272
285
|
interleaved,
|
|
273
286
|
device,
|
|
274
287
|
supplied,
|
|
288
|
+
streams,
|
|
275
289
|
vertices,
|
|
276
290
|
indexBuffer,
|
|
277
291
|
vertexCount,
|
|
@@ -305,6 +319,11 @@ function* uploadSteps(job) {
|
|
|
305
319
|
fieldOffset += attribute.components;
|
|
306
320
|
}
|
|
307
321
|
device.queue.writeBuffer(vertices, from * stride, interleaved, from * step, (to - from) * step);
|
|
322
|
+
if (job.streams !== null) {
|
|
323
|
+
const count = (to - from) * 3;
|
|
324
|
+
device.queue.writeBuffer(job.streams.positions, from * 12, data.positions, from * 3, count);
|
|
325
|
+
device.queue.writeBuffer(job.streams.normals, from * 12, data.normals, from * 3, count);
|
|
326
|
+
}
|
|
308
327
|
taken += 1;
|
|
309
328
|
if (taken < job.totalSteps)
|
|
310
329
|
yield;
|
|
@@ -368,21 +387,33 @@ function skinOffsetsOf(supplied) {
|
|
|
368
387
|
let floats = 0;
|
|
369
388
|
let joints = -1;
|
|
370
389
|
let weights = -1;
|
|
390
|
+
let joints2 = -1;
|
|
391
|
+
let weights2 = -1;
|
|
371
392
|
for (const attribute of supplied) {
|
|
372
393
|
if (attribute.name === 'joints')
|
|
373
394
|
joints = floats * 4;
|
|
374
395
|
if (attribute.name === 'weights')
|
|
375
396
|
weights = floats * 4;
|
|
397
|
+
if (attribute.name === 'joints2')
|
|
398
|
+
joints2 = floats * 4;
|
|
399
|
+
if (attribute.name === 'weights2')
|
|
400
|
+
weights2 = floats * 4;
|
|
376
401
|
floats += attribute.components;
|
|
377
402
|
}
|
|
378
|
-
|
|
403
|
+
if (joints < 0 || weights < 0)
|
|
404
|
+
return null;
|
|
405
|
+
return joints2 >= 0 && weights2 >= 0
|
|
406
|
+
? { joints, weights, joints2, weights2 }
|
|
407
|
+
: { joints, weights, joints2: -1, weights2: -1 };
|
|
379
408
|
}
|
|
380
409
|
/** The handle, made where it can see only what it hands out. */
|
|
381
410
|
function gpuMeshOf(parts) {
|
|
382
411
|
const motion = parts.update === null ? null : { previous: null, changed: -1 };
|
|
383
|
-
const { vertices, constants, indexBuffer, progress } = parts;
|
|
412
|
+
const { vertices, constants, streams, indexBuffer, progress } = parts;
|
|
384
413
|
return {
|
|
385
|
-
vertexBuffers:
|
|
414
|
+
vertexBuffers: streams === null
|
|
415
|
+
? [vertices, constants]
|
|
416
|
+
: [streams.positions, streams.normals, vertices, constants],
|
|
386
417
|
vertexStride: parts.stride,
|
|
387
418
|
skinOffsets: parts.skinOffsets,
|
|
388
419
|
indexBuffer,
|
|
@@ -393,6 +424,8 @@ function gpuMeshOf(parts) {
|
|
|
393
424
|
},
|
|
394
425
|
hasTangents: parts.hasTangents,
|
|
395
426
|
isSkinned: parts.isSkinned,
|
|
427
|
+
isSkinnedEight: parts.isSkinnedEight,
|
|
428
|
+
vertexCount: parts.vertexCount,
|
|
396
429
|
hasUvs: parts.hasUvs,
|
|
397
430
|
hasChannel: parts.hasChannel,
|
|
398
431
|
morph: parts.morph,
|
|
@@ -400,43 +433,47 @@ function gpuMeshOf(parts) {
|
|
|
400
433
|
motion,
|
|
401
434
|
dispose() {
|
|
402
435
|
motion?.previous?.destroy();
|
|
436
|
+
streams?.positions.destroy();
|
|
437
|
+
streams?.normals.destroy();
|
|
403
438
|
vertices.destroy();
|
|
404
439
|
constants.destroy();
|
|
405
440
|
indexBuffer.destroy();
|
|
406
441
|
},
|
|
407
442
|
};
|
|
408
443
|
}
|
|
444
|
+
/** Whether an attribute is one a dynamic mesh keeps in a buffer of its own. */
|
|
445
|
+
function isStreamed(attribute) {
|
|
446
|
+
return attribute.name === 'positions' || attribute.name === 'normals';
|
|
447
|
+
}
|
|
409
448
|
/**
|
|
410
|
-
* A dynamic mesh's `update
|
|
411
|
-
*
|
|
412
|
-
*
|
|
449
|
+
* A dynamic mesh's `update`: the caller's positions, and its normals where it has them, each
|
|
450
|
+
* written to its own buffer as it came. Last frame's positions, where a reconstruction wants them,
|
|
451
|
+
* are copied on the device before the write — a copy submitted ahead of a `writeBuffer` runs ahead
|
|
452
|
+
* of it on the queue — so no CPU copy of anything is kept.
|
|
413
453
|
*/
|
|
414
|
-
function
|
|
454
|
+
function streamUpdate(positionsBuffer, normalsBuffer, streamBytes, bounds, vertexCount) {
|
|
415
455
|
return (target, positions, normals, previous = null) => {
|
|
416
|
-
|
|
417
|
-
|
|
456
|
+
for (const [name, values] of [
|
|
457
|
+
['update', positions],
|
|
458
|
+
['normals', normals],
|
|
459
|
+
]) {
|
|
460
|
+
if (values === undefined || values.length === vertexCount * 3)
|
|
461
|
+
continue;
|
|
462
|
+
throw new Error(`this mesh has ${vertexCount} vertices and the ${name} has ${values.length / 3}. ` +
|
|
418
463
|
"A mesh's vertex count is fixed at creation: the index buffer, the pipeline and " +
|
|
419
464
|
'every other attribute are sized against it.');
|
|
420
465
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
target.queue.
|
|
466
|
+
if (previous !== null) {
|
|
467
|
+
const encoder = target.createCommandEncoder({ label: 'mesh.previousPositions' });
|
|
468
|
+
encoder.copyBufferToBuffer(positionsBuffer, 0, previous, 0, streamBytes);
|
|
469
|
+
target.queue.submit([encoder.finish()]);
|
|
470
|
+
}
|
|
425
471
|
/* Everything that decides whether this is on screen starts from the bounds, so a cloth
|
|
426
472
|
that blew sideways out of its original box would be culled while still visible. */
|
|
427
473
|
boundsOfPositions(positions, bounds);
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
interleaved[at + positionField + 1] = positions[vertex * 3 + 1] ?? 0;
|
|
432
|
-
interleaved[at + positionField + 2] = positions[vertex * 3 + 2] ?? 0;
|
|
433
|
-
if (normals === undefined)
|
|
434
|
-
continue;
|
|
435
|
-
interleaved[at + normalField] = normals[vertex * 3] ?? 0;
|
|
436
|
-
interleaved[at + normalField + 1] = normals[vertex * 3 + 1] ?? 0;
|
|
437
|
-
interleaved[at + normalField + 2] = normals[vertex * 3 + 2] ?? 0;
|
|
438
|
-
}
|
|
439
|
-
target.queue.writeBuffer(vertices, 0, interleaved);
|
|
474
|
+
target.queue.writeBuffer(positionsBuffer, 0, positions);
|
|
475
|
+
if (normals !== undefined)
|
|
476
|
+
target.queue.writeBuffer(normalsBuffer, 0, normals);
|
|
440
477
|
};
|
|
441
478
|
}
|
|
442
479
|
/**
|
|
@@ -481,8 +518,20 @@ instanced = false) {
|
|
|
481
518
|
const constants = [];
|
|
482
519
|
let offset = 0;
|
|
483
520
|
const layered = present['layers'] === true;
|
|
521
|
+
/* A dynamic mesh's positions and normals: buffers of their own, ahead of the rest. */
|
|
522
|
+
const dynamic = present['dynamic'] === true;
|
|
523
|
+
const layouts = [];
|
|
484
524
|
VERTEX_LAYOUT.forEach((declared, index) => {
|
|
485
525
|
const attribute = laidOut(declared, layered);
|
|
526
|
+
if (dynamic && isStreamed(attribute)) {
|
|
527
|
+
layouts.push({
|
|
528
|
+
arrayStride: attribute.components * 4,
|
|
529
|
+
attributes: [
|
|
530
|
+
{ shaderLocation: attribute.shaderLocation, offset: 0, format: attribute.format },
|
|
531
|
+
],
|
|
532
|
+
});
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
486
535
|
/*
|
|
487
536
|
* **The instanced pipeline reclaims the two skinning locations, and it is allowed to because
|
|
488
537
|
* it cannot skin.** Locations 11 and 12 are the joint indices and weights; the INSTANCED
|
|
@@ -530,7 +579,7 @@ instanced = false) {
|
|
|
530
579
|
format: attribute.format,
|
|
531
580
|
});
|
|
532
581
|
});
|
|
533
|
-
|
|
582
|
+
layouts.push({ arrayStride: offset, attributes: interleaved });
|
|
534
583
|
/*
|
|
535
584
|
* The constants buffer is always bound, even with nothing in it: the pipeline layout has
|
|
536
585
|
* to match the buffers a draw sets, and a mesh supplying every attribute would otherwise
|