@driftengine/core 4.8.3 → 4.8.5
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 +82 -0
- package/README.md +1 -1
- package/dist/build/shaderComments.js +0 -0
- package/dist/geometry/meshConcat.js +2 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.js +16 -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/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 +136 -3
- package/dist/render/backend/webgl2/renderer.js +604 -63
- 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/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 +16 -6
- package/dist/render/backend/webgpu/depthPass.js +39 -9
- package/dist/render/backend/webgpu/drawCommand.js +6 -2
- package/dist/render/backend/webgpu/flatPass.d.ts +38 -4
- package/dist/render/backend/webgpu/flatPass.js +109 -12
- package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +74 -0
- package/dist/render/backend/webgpu/gpuSkinnedCloth.js +452 -0
- package/dist/render/backend/webgpu/motionPass.d.ts +20 -0
- package/dist/render/backend/webgpu/motionPass.js +106 -9
- package/dist/render/backend/webgpu/postPass.d.ts +3 -1
- package/dist/render/backend/webgpu/postPass.js +6 -3
- package/dist/render/backend/webgpu/renderer.d.ts +113 -5
- package/dist/render/backend/webgpu/renderer.js +498 -48
- 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/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 +2 -0
- package/dist/render/cutoutCaster.js +2 -0
- package/dist/render/cutoutDither.d.ts +51 -0
- package/dist/render/cutoutDither.js +90 -0
- 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/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/mesh.d.ts +7 -0
- package/dist/render/mesh.js +24 -0
- package/dist/render/recon/motionVectors.d.ts +4 -2
- package/dist/render/recon/motionVectors.js +6 -3
- package/dist/render/renderQuality.d.ts +20 -0
- package/dist/render/renderQuality.js +5 -0
- package/dist/render/rendererHarness.js +4 -0
- package/dist/render/sceneTarget.d.ts +12 -0
- package/dist/render/sceneTarget.js +14 -0
- package/dist/render/shaders/clothBinding.d.ts +22 -0
- package/dist/render/shaders/clothBinding.js +80 -0
- package/dist/render/shaders/depth.js +42 -6
- 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 +36 -2
- 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 +38 -16
- package/dist/render/shaders/generated/depth.wgsl.js +38 -16
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1790 -1704
- package/dist/render/shaders/generated/flat.wgsl.js +1828 -1742
- 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/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +102 -0
- 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/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/surfaceModel.d.ts +129 -0
- package/dist/render/surfaceModel.js +152 -0
- package/dist/render/surfaceTexture.d.ts +44 -4
- package/dist/render/surfaceTexture.js +60 -46
- package/dist/render/vertexDefaults.js +7 -0
- package/package.json +6 -6
- package/src/build/shaderComments.ts +0 -0
- package/src/geometry/meshConcat.ts +2 -0
- package/src/index.ts +40 -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/select.ts +8 -0
- package/src/render/backend/webgl2/clothTextures.ts +120 -0
- package/src/render/backend/webgl2/renderer.ts +693 -64
- package/src/render/backend/webgl2/skinScatter.ts +309 -0
- package/src/render/backend/webgpu/buffers.ts +137 -63
- 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 +39 -7
- package/src/render/backend/webgpu/drawCommand.ts +6 -2
- package/src/render/backend/webgpu/flatPass.ts +145 -7
- package/src/render/backend/webgpu/gpuSkinnedCloth.ts +502 -0
- package/src/render/backend/webgpu/motionPass.ts +128 -9
- package/src/render/backend/webgpu/postPass.ts +5 -1
- package/src/render/backend/webgpu/renderer.ts +663 -43
- 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/clothBindingData.ts +89 -0
- package/src/render/clothPlacement.ts +114 -0
- package/src/render/compressedSource.ts +198 -0
- package/src/render/cutoutCaster.ts +5 -0
- package/src/render/cutoutDither.ts +99 -0
- package/src/render/eyeRefraction.ts +72 -0
- package/src/render/glCompressed.ts +86 -0
- package/src/render/hairLobes.ts +188 -0
- package/src/render/lightBudget.ts +25 -0
- package/src/render/mesh.ts +23 -0
- package/src/render/recon/motionVectors.ts +6 -3
- package/src/render/renderQuality.ts +28 -0
- package/src/render/rendererHarness.ts +4 -0
- package/src/render/sceneTarget.ts +21 -0
- package/src/render/shaders/clothBinding.ts +81 -0
- package/src/render/shaders/depth.ts +42 -6
- 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 +36 -2
- 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 +38 -16
- package/src/render/shaders/generated/flat.wgsl.ts +1828 -1742
- package/src/render/shaders/generated/skinBlur.wgsl.ts +56 -0
- package/src/render/shaders/recon/motion.wgsl.ts +102 -0
- package/src/render/shaders/skinBlur.ts +105 -0
- package/src/render/shaders/skinning.ts +31 -4
- 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/surfaceModel.ts +274 -0
- package/src/render/surfaceTexture.ts +123 -63
- package/src/render/vertexDefaults.ts +7 -0
- 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,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A cloth binding's textures on WebGPU: the binding and the rest particles once, a mesh's; the
|
|
3
|
+
* particles every frame, a character's — the twin of `webgl2/clothTextures.ts`.
|
|
4
|
+
*
|
|
5
|
+
* `rgba32float`, read by `textureLoad` in the vertex stage, so bound as `unfilterable-float` and
|
|
6
|
+
* never sampled. Two particle textures that swap on each update, so last frame's positions stay for
|
|
7
|
+
* a motion vector with no copy. **Written with `writeTexture`, whose queue order is the trap the
|
|
8
|
+
* per-draw rings exist for**: an update lands before every draw of the frame, so a character's
|
|
9
|
+
* particles are updated once a frame, before its draws, never between two draws of it.
|
|
10
|
+
*
|
|
11
|
+
* `clothStandIn` is what a skinned pipeline binds where no cloth is set: its layout declares the
|
|
12
|
+
* three textures whatever the draw, and WebGPU binds every declared texture.
|
|
13
|
+
*/
|
|
14
|
+
import { clothTextureSize, packClothBinding, packClothParticles } from '../../clothBindingData.ts';
|
|
15
|
+
import type { ClothBindingData } from '../../clothBindingData.ts';
|
|
16
|
+
|
|
17
|
+
const USAGE = 0x4 | 0x2; // TEXTURE_BINDING | COPY_DST
|
|
18
|
+
const STORAGE_BINDING = 0x8;
|
|
19
|
+
|
|
20
|
+
function floatTexture(
|
|
21
|
+
device: GPUDevice,
|
|
22
|
+
label: string,
|
|
23
|
+
width: number,
|
|
24
|
+
height: number,
|
|
25
|
+
texels: Float32Array | null,
|
|
26
|
+
storage = false,
|
|
27
|
+
): GPUTexture {
|
|
28
|
+
const texture = device.createTexture({
|
|
29
|
+
label,
|
|
30
|
+
size: [width, height],
|
|
31
|
+
format: 'rgba32float',
|
|
32
|
+
usage: storage ? USAGE | STORAGE_BINDING : USAGE,
|
|
33
|
+
});
|
|
34
|
+
if (texels !== null) {
|
|
35
|
+
device.queue.writeTexture(
|
|
36
|
+
{ texture },
|
|
37
|
+
texels as Float32Array<ArrayBuffer>,
|
|
38
|
+
{ bytesPerRow: width * 16, rowsPerImage: height },
|
|
39
|
+
{ width, height },
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return texture;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The three views a skinned bind group takes for its cloth. */
|
|
46
|
+
export interface ClothViews {
|
|
47
|
+
readonly binding: GPUTextureView;
|
|
48
|
+
readonly particles: GPUTextureView;
|
|
49
|
+
readonly rest: GPUTextureView;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** A one-texel float texture, as all three views, for every draw with no cloth. */
|
|
53
|
+
export function clothStandIn(device: GPUDevice): { texture: GPUTexture; views: ClothViews } {
|
|
54
|
+
const texture = floatTexture(device, 'cloth.standIn', 1, 1, new Float32Array(4));
|
|
55
|
+
const view = texture.createView();
|
|
56
|
+
return { texture, views: { binding: view, particles: view, rest: view } };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** A mesh's binding and its particles at rest. */
|
|
60
|
+
export class GpuClothBinding {
|
|
61
|
+
readonly binding: GPUTexture;
|
|
62
|
+
readonly rest: GPUTexture;
|
|
63
|
+
readonly bindingView: GPUTextureView;
|
|
64
|
+
readonly restView: GPUTextureView;
|
|
65
|
+
readonly particles: number;
|
|
66
|
+
|
|
67
|
+
constructor(device: GPUDevice, data: ClothBindingData) {
|
|
68
|
+
const size = clothTextureSize(data.weights.length * 2);
|
|
69
|
+
this.binding = floatTexture(
|
|
70
|
+
device,
|
|
71
|
+
'cloth.binding',
|
|
72
|
+
size.width,
|
|
73
|
+
size.height,
|
|
74
|
+
packClothBinding(data),
|
|
75
|
+
);
|
|
76
|
+
this.particles = data.rest.length / 3;
|
|
77
|
+
const rest = clothTextureSize(this.particles);
|
|
78
|
+
const texels = new Float32Array(rest.width * rest.height * 4);
|
|
79
|
+
packClothParticles(data.rest, texels);
|
|
80
|
+
this.rest = floatTexture(device, 'cloth.rest', rest.width, rest.height, texels);
|
|
81
|
+
this.bindingView = this.binding.createView();
|
|
82
|
+
this.restView = this.rest.createView();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Its textures, for the renderer to retire once no draw of this frame can read them. */
|
|
86
|
+
get textures(): readonly GPUTexture[] {
|
|
87
|
+
return [this.binding, this.rest];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* One character's particles: this frame's and the last, in two textures that swap. Written from
|
|
93
|
+
* the CPU with `update`, or — `storage` — by the device solver's publish pass, `publish` then
|
|
94
|
+
* saying which texture it writes.
|
|
95
|
+
*/
|
|
96
|
+
export class GpuClothParticles {
|
|
97
|
+
readonly count: number;
|
|
98
|
+
/** Texels a row, which the publish pass wraps a particle index by. */
|
|
99
|
+
readonly width: number;
|
|
100
|
+
private readonly height: number;
|
|
101
|
+
private readonly staging: Float32Array<ArrayBuffer>;
|
|
102
|
+
/** Both textures, for the renderer to retire once no draw of this frame can read them. */
|
|
103
|
+
readonly textures: readonly [GPUTexture, GPUTexture];
|
|
104
|
+
private readonly views: [GPUTextureView, GPUTextureView];
|
|
105
|
+
private latest = 0;
|
|
106
|
+
/** The frame each texture was last written for, so a motion knows whether the other is last frame's. */
|
|
107
|
+
private readonly written = [-1, -1];
|
|
108
|
+
private paired: GpuClothBinding | null = null;
|
|
109
|
+
private pairedViews: ClothViews[] = [];
|
|
110
|
+
|
|
111
|
+
constructor(device: GPUDevice, count: number, storage = false) {
|
|
112
|
+
this.count = count;
|
|
113
|
+
const size = clothTextureSize(count);
|
|
114
|
+
this.width = size.width;
|
|
115
|
+
this.height = size.height;
|
|
116
|
+
this.staging = new Float32Array(size.width * size.height * 4);
|
|
117
|
+
this.textures = [
|
|
118
|
+
floatTexture(device, 'cloth.particles', size.width, size.height, this.staging, storage),
|
|
119
|
+
floatTexture(device, 'cloth.particles', size.width, size.height, this.staging, storage),
|
|
120
|
+
];
|
|
121
|
+
this.views = [this.textures[0].createView(), this.textures[1].createView()];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** This frame's particles. */
|
|
125
|
+
get current(): GPUTextureView {
|
|
126
|
+
return this.views[this.latest] as GPUTextureView;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Last frame's. */
|
|
130
|
+
get previous(): GPUTextureView {
|
|
131
|
+
return this.views[1 - this.latest] as GPUTextureView;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Where they were last frame, for a motion vector at `frame`: the other texture where it was
|
|
136
|
+
* written the frame before this one's update, and this frame's otherwise — particles not updated
|
|
137
|
+
* this frame did not move, and a first update has no past to measure from.
|
|
138
|
+
*/
|
|
139
|
+
previousAt(frame: number): GPUTextureView {
|
|
140
|
+
const fresh =
|
|
141
|
+
this.written[this.latest] === frame && this.written[1 - this.latest] === frame - 1;
|
|
142
|
+
return fresh ? this.previous : this.current;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** A texture's view, by index: the publish pass binds both, one at a time. */
|
|
146
|
+
viewOf(k: number): GPUTextureView {
|
|
147
|
+
return this.views[k] as GPUTextureView;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The device is about to write the particles of `frame`: the older texture becomes this frame's,
|
|
152
|
+
* as an `update` makes it, and its index is returned for the pass that writes it.
|
|
153
|
+
*/
|
|
154
|
+
publish(frame: number): number {
|
|
155
|
+
this.latest = 1 - this.latest;
|
|
156
|
+
this.written[this.latest] = frame;
|
|
157
|
+
return this.latest;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Write `positions` as the particles of `frame`; last frame's stays. */
|
|
161
|
+
update(device: GPUDevice, positions: Float32Array, frame: number): void {
|
|
162
|
+
if (positions.length !== this.count * 3) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`cloth: ${this.count} particles and an update of ${positions.length / 3}; a cloth's ` +
|
|
165
|
+
'particle count is fixed when its particles are created',
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
packClothParticles(positions, this.staging);
|
|
169
|
+
this.latest = 1 - this.latest;
|
|
170
|
+
this.written[this.latest] = frame;
|
|
171
|
+
device.queue.writeTexture(
|
|
172
|
+
{ texture: this.textures[this.latest] as GPUTexture },
|
|
173
|
+
this.staging,
|
|
174
|
+
{ bytesPerRow: this.width * 16, rowsPerImage: this.height },
|
|
175
|
+
{ width: this.width, height: this.height },
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* The three views a draw of `binding` with these particles binds, kept so a cached bind group
|
|
181
|
+
* compares equal frame after frame. Two objects, one for each texture that is current.
|
|
182
|
+
*/
|
|
183
|
+
viewsWith(binding: GpuClothBinding): ClothViews {
|
|
184
|
+
if (this.paired !== binding) {
|
|
185
|
+
this.paired = binding;
|
|
186
|
+
this.pairedViews = [0, 1].map((k) => ({
|
|
187
|
+
binding: binding.bindingView,
|
|
188
|
+
particles: this.views[k] as GPUTextureView,
|
|
189
|
+
rest: binding.restView,
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
return this.pairedViews[this.latest] as ClothViews;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BC on WebGPU: which formats this device samples, and the upload of a stored chain.
|
|
3
|
+
*
|
|
4
|
+
* **One feature covers every BC format**, `texture-compression-bc`, which `select.ts` asks for
|
|
5
|
+
* wherever the adapter offers it and never demands: a device without it is still a device, and the
|
|
6
|
+
* loader decodes for it. Desktop adapters have it; phones have ASTC and ETC2 instead.
|
|
7
|
+
*
|
|
8
|
+
* **Written with `writeTexture`, level by level**, because `copyExternalImageToTexture` takes images
|
|
9
|
+
* and a compressed format cannot be a render attachment, so neither the image upload nor the mip
|
|
10
|
+
* blit applies. Each level's copy is whole blocks: a level smaller than a block is still one, and
|
|
11
|
+
* its copy extent is the block's four texels — WebGPU's physical size — rather than the one or two
|
|
12
|
+
* the level nominally has.
|
|
13
|
+
*/
|
|
14
|
+
import type { CompressedTextureFormat, CompressedTextureSource } from '../../compressedSource.ts';
|
|
15
|
+
import { BLOCK_BYTES } from '../../compressedSource.ts';
|
|
16
|
+
|
|
17
|
+
const GPU_FORMAT: Readonly<Record<CompressedTextureFormat, GPUTextureFormat>> = {
|
|
18
|
+
bc1: 'bc1-rgba-unorm',
|
|
19
|
+
'bc1-srgb': 'bc1-rgba-unorm-srgb',
|
|
20
|
+
bc2: 'bc2-rgba-unorm',
|
|
21
|
+
'bc2-srgb': 'bc2-rgba-unorm-srgb',
|
|
22
|
+
bc3: 'bc3-rgba-unorm',
|
|
23
|
+
'bc3-srgb': 'bc3-rgba-unorm-srgb',
|
|
24
|
+
bc4: 'bc4-r-unorm',
|
|
25
|
+
bc5: 'bc5-rg-unorm',
|
|
26
|
+
bc7: 'bc7-rgba-unorm',
|
|
27
|
+
'bc7-srgb': 'bc7-rgba-unorm-srgb',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Every BC format, where the device carries the feature; none where it does not. */
|
|
31
|
+
export function gpuCompressedFormats(device: GPUDevice): CompressedTextureFormat[] {
|
|
32
|
+
return device.features.has('texture-compression-bc')
|
|
33
|
+
? (Object.keys(GPU_FORMAT) as CompressedTextureFormat[])
|
|
34
|
+
: [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A texture holding `layers` as `name`, `levels` levels of each, written as they are. */
|
|
38
|
+
export function createCompressedTexture(
|
|
39
|
+
device: GPUDevice,
|
|
40
|
+
name: CompressedTextureFormat,
|
|
41
|
+
layers: readonly CompressedTextureSource[],
|
|
42
|
+
levels: number,
|
|
43
|
+
): GPUTexture {
|
|
44
|
+
const { format, width, height } = layers[0] as CompressedTextureSource;
|
|
45
|
+
const texture = device.createTexture({
|
|
46
|
+
label: 'surface.texture.bc',
|
|
47
|
+
size: [width, height, layers.length],
|
|
48
|
+
format: GPU_FORMAT[name],
|
|
49
|
+
mipLevelCount: levels,
|
|
50
|
+
usage: 0x2 | 0x4, // COPY_DST | TEXTURE_BINDING
|
|
51
|
+
});
|
|
52
|
+
for (let level = 0; level < levels; level++) {
|
|
53
|
+
const across = Math.ceil(Math.max(1, width >> level) / 4);
|
|
54
|
+
const down = Math.ceil(Math.max(1, height >> level) / 4);
|
|
55
|
+
for (let layer = 0; layer < layers.length; layer++) {
|
|
56
|
+
device.queue.writeTexture(
|
|
57
|
+
{ texture, mipLevel: level, origin: [0, 0, layer] },
|
|
58
|
+
(layers[layer] as CompressedTextureSource).levels[level] as Uint8Array<ArrayBuffer>,
|
|
59
|
+
{ bytesPerRow: across * BLOCK_BYTES[format], rowsPerImage: down },
|
|
60
|
+
{ width: across * 4, height: down * 4, depthOrArrayLayers: 1 },
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return texture;
|
|
65
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What compute a consumer opts into needs from the WebGPU renderer: its device, whether the device
|
|
3
|
+
* is gone, which frame a change made now belongs to, and a place to retire textures the frame being
|
|
4
|
+
* recorded may still read.
|
|
5
|
+
*
|
|
6
|
+
* **Why a door rather than renderer methods.** A skinned cloth's solver is fifteen kilobytes
|
|
7
|
+
* gzipped of compute, WGSL and the CPU solver beneath it, and a renderer method that constructs it
|
|
8
|
+
* is a static reference no bundler can shake — every game would ship a garment solver whether or
|
|
9
|
+
* not it draws a garment. Behind this, `createSkinnedCloth` is a barrel function, and only a game
|
|
10
|
+
* that imports it carries it. **What it gives up**: a function reaching the renderer through a door
|
|
11
|
+
* the public surface does not show; the door stays this narrow so that is all it reaches.
|
|
12
|
+
*/
|
|
13
|
+
export interface ComputeHost {
|
|
14
|
+
device(): GPUDevice;
|
|
15
|
+
lost(): boolean;
|
|
16
|
+
/** The frame a change made now is drawn in: this one inside a frame, the next between frames. */
|
|
17
|
+
changeFrame(): number;
|
|
18
|
+
retire(textures: readonly GPUTexture[]): void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The host of a renderer that has one: the WebGPU backend, and nothing else. */
|
|
22
|
+
export function computeHostOf(renderer: object): ComputeHost | null {
|
|
23
|
+
const asked = (renderer as { computeHost?: () => ComputeHost }).computeHost;
|
|
24
|
+
return typeof asked === 'function' ? asked.call(renderer) : null;
|
|
25
|
+
}
|
|
@@ -9,8 +9,10 @@ import {
|
|
|
9
9
|
DEPTH_VERT_WGSL,
|
|
10
10
|
} from '../../shaders/generated/depth.wgsl.ts';
|
|
11
11
|
import { vertexBufferLayouts } from './buffers.ts';
|
|
12
|
+
import { vertexConstants } from './flatPass.ts';
|
|
12
13
|
import type { PipelineCache } from './pipelineCache.ts';
|
|
13
14
|
import { shaderModule } from './shaderModules.ts';
|
|
15
|
+
import type { ClothViews } from './clothTextures.ts';
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* The shadow pass: the world drawn from the light, into a depth map.
|
|
@@ -43,6 +45,12 @@ const FRAG_BINDING = DEPTH_BINDINGS.DEPTH_FRAG.uniforms;
|
|
|
43
45
|
const PREVIOUS = DEPTH_BINDINGS.DEPTH_FRAG.textures.uPreviousShadowMap;
|
|
44
46
|
/** Where the skinned vertex stage reads the joint palette. Generated, not chosen here. */
|
|
45
47
|
export const DEPTH_PALETTE_BINDING = DEPTH_BINDINGS.DEPTH_SKINNED_VERT.textures.uJointPalette;
|
|
48
|
+
/** And the cloth binding's three textures, which every skinned caster declares. */
|
|
49
|
+
const CLOTH_TEXTURES = [
|
|
50
|
+
DEPTH_BINDINGS.DEPTH_SKINNED_VERT.textures.uClothBinding,
|
|
51
|
+
DEPTH_BINDINGS.DEPTH_SKINNED_VERT.textures.uClothParticles,
|
|
52
|
+
DEPTH_BINDINGS.DEPTH_SKINNED_VERT.textures.uClothRest,
|
|
53
|
+
] as const;
|
|
46
54
|
|
|
47
55
|
const VISIBILITY_VERTEX = 0x1;
|
|
48
56
|
const VISIBILITY_FRAGMENT = 0x2;
|
|
@@ -82,13 +90,11 @@ export function createDepthBindGroupLayout(
|
|
|
82
90
|
coordinate with nothing to filter, which is what `flatPass.ts` binds for the same
|
|
83
91
|
palette. naga declares a sampler for it and nothing ever reads one. */
|
|
84
92
|
...(skinned
|
|
85
|
-
? [
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
},
|
|
91
|
-
]
|
|
93
|
+
? [DEPTH_PALETTE_BINDING, ...CLOTH_TEXTURES].map((binding) => ({
|
|
94
|
+
binding: binding.texture,
|
|
95
|
+
visibility: VISIBILITY_VERTEX,
|
|
96
|
+
texture: { sampleType: 'unfilterable-float' as GPUTextureSampleType },
|
|
97
|
+
}))
|
|
92
98
|
: []),
|
|
93
99
|
{
|
|
94
100
|
binding: VERT_BINDING,
|
|
@@ -166,6 +172,8 @@ export function createDepthBindGroup(
|
|
|
166
172
|
palette: GPUTextureView | null = null,
|
|
167
173
|
/** The cutout map and its sampler, for a layout built with `cutout`. */
|
|
168
174
|
cutout: { readonly view: GPUTextureView; readonly sampler: GPUSampler } | null = null,
|
|
175
|
+
/** A skinned group's cloth views: the caster's, or the stand-in where it has none. */
|
|
176
|
+
cloth: ClothViews | null = null,
|
|
169
177
|
): GPUBindGroup {
|
|
170
178
|
return device.createBindGroup({
|
|
171
179
|
label:
|
|
@@ -187,6 +195,13 @@ export function createDepthBindGroup(
|
|
|
187
195
|
{ binding: PREVIOUS.texture, resource: previous },
|
|
188
196
|
{ binding: PREVIOUS.sampler, resource: sampler },
|
|
189
197
|
...(palette !== null ? [{ binding: DEPTH_PALETTE_BINDING.texture, resource: palette }] : []),
|
|
198
|
+
...(palette !== null
|
|
199
|
+
? [
|
|
200
|
+
{ binding: CLOTH_TEXTURES[0].texture, resource: clothOf(cloth).binding },
|
|
201
|
+
{ binding: CLOTH_TEXTURES[1].texture, resource: clothOf(cloth).particles },
|
|
202
|
+
{ binding: CLOTH_TEXTURES[2].texture, resource: clothOf(cloth).rest },
|
|
203
|
+
]
|
|
204
|
+
: []),
|
|
190
205
|
...(cutout !== null
|
|
191
206
|
? [
|
|
192
207
|
{ binding: CUTOUT_MAP.texture, resource: cutout.view },
|
|
@@ -234,6 +249,13 @@ export function depthPipeline(
|
|
|
234
249
|
instanced = false,
|
|
235
250
|
/** Whether this pipeline casts a cutout: the variant that reads a map and discards by it. */
|
|
236
251
|
cutout = false,
|
|
252
|
+
/**
|
|
253
|
+
* Whether a skinned caster reads its second four influences, as its visible draw does. The key
|
|
254
|
+
* must carry it, so the shadow parts from nothing the body does. See `skinning.ts`.
|
|
255
|
+
*/
|
|
256
|
+
skinEight = false,
|
|
257
|
+
/** Whether a skinned caster is placed by a cloth binding, as its visible draw is. The key carries it. */
|
|
258
|
+
cloth = false,
|
|
237
259
|
): GPURenderPipeline {
|
|
238
260
|
return cache.get(key, () => ({
|
|
239
261
|
label: key,
|
|
@@ -253,6 +275,9 @@ export function depthPipeline(
|
|
|
253
275
|
),
|
|
254
276
|
entryPoint: 'main',
|
|
255
277
|
buffers: vertexBufferLayouts(present, instanced),
|
|
278
|
+
...(skinned && !instanced && !cutout
|
|
279
|
+
? { constants: vertexConstants(DEPTH_BINDINGS.DEPTH_SKINNED_VERT, skinEight, cloth) }
|
|
280
|
+
: {}),
|
|
256
281
|
},
|
|
257
282
|
fragment: {
|
|
258
283
|
module: shaderModule(
|
|
@@ -333,3 +358,10 @@ export function depthPipeline(
|
|
|
333
358
|
},
|
|
334
359
|
}));
|
|
335
360
|
}
|
|
361
|
+
|
|
362
|
+
function clothOf(cloth: ClothViews | null): ClothViews {
|
|
363
|
+
if (cloth === null) {
|
|
364
|
+
throw new Error('depthPass: a skinned bind group needs cloth views, the stand-in at the least');
|
|
365
|
+
}
|
|
366
|
+
return cloth;
|
|
367
|
+
}
|
|
@@ -11,8 +11,12 @@
|
|
|
11
11
|
* edits rather than twenty-three implementations.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
|
|
14
|
+
/**
|
|
15
|
+
* WebGPU's guaranteed `maxVertexBuffers`, so no verb can outgrow it. The most any binds is five — a
|
|
16
|
+
* dynamic mesh's positions, normals, rows and constants, and an instanced batch's placements — and
|
|
17
|
+
* this said three, then four, while text and bolts already bound four.
|
|
18
|
+
*/
|
|
19
|
+
const VERTEX_BUFFER_SLOTS = 8;
|
|
16
20
|
|
|
17
21
|
export interface DrawCommand {
|
|
18
22
|
pipeline: GPURenderPipeline | null;
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { SURFACE_MODEL_SWITCH } from '../../surfaceModel.ts';
|
|
2
|
+
import type { SurfaceModelKind } from '../../surfaceModel.ts';
|
|
3
|
+
import type { SkinHalf } from '../../shaders/flat/models.ts';
|
|
1
4
|
import { DEPTH_COMPARE, DEPTH_FORMAT, depthOffsetForLayer } from '../../depthConvention.ts';
|
|
2
5
|
import {
|
|
3
6
|
FLAT_BINDINGS,
|
|
@@ -6,6 +9,7 @@ import {
|
|
|
6
9
|
} from '../../shaders/generated/flat.wgsl.ts';
|
|
7
10
|
import { vertexBufferLayouts } from './buffers.ts';
|
|
8
11
|
import type { LitSwitch, PipelineCache } from './pipelineCache.ts';
|
|
12
|
+
import type { ClothViews } from './clothTextures.ts';
|
|
9
13
|
import { shaderModule } from './shaderModules.ts';
|
|
10
14
|
import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.ts';
|
|
11
15
|
|
|
@@ -15,6 +19,7 @@ const SURFACE_MAPS: ReadonlySet<string> = new Set([
|
|
|
15
19
|
'uNormalMap',
|
|
16
20
|
'uOrmMap',
|
|
17
21
|
'uEmissiveMap',
|
|
22
|
+
'uModelMap',
|
|
18
23
|
]);
|
|
19
24
|
|
|
20
25
|
/**
|
|
@@ -146,6 +151,38 @@ export function flatVertexBindings(
|
|
|
146
151
|
}
|
|
147
152
|
|
|
148
153
|
const PLAIN_VERTEX = flatVertexBindings(false);
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* A vertex stage's constants, keyed by each override's **id** as the fragment's are: `SKIN_EIGHT` in
|
|
157
|
+
* a skinned stage, nothing in any other. Every override a stage declares is set, and one this does
|
|
158
|
+
* not know is refused here rather than defaulted. Shared with the shadow and glass-tint casters,
|
|
159
|
+
* whose skinned stages declare the same switch.
|
|
160
|
+
*/
|
|
161
|
+
export function vertexConstants(
|
|
162
|
+
bindings: StageBindings,
|
|
163
|
+
skinEight: boolean,
|
|
164
|
+
cloth = false,
|
|
165
|
+
): Record<string, number> {
|
|
166
|
+
const constants: Record<string, number> = {};
|
|
167
|
+
for (const [name, id] of Object.entries(bindings.overrides ?? {})) {
|
|
168
|
+
if (name === 'SKIN_EIGHT') constants[String(id)] = skinEight ? 1 : 0;
|
|
169
|
+
else if (name === 'CLOTH_BOUND') constants[String(id)] = cloth ? 1 : 0;
|
|
170
|
+
else throw new Error(`flatPass: a vertex stage declares a switch "${name}" nothing sets`);
|
|
171
|
+
}
|
|
172
|
+
return constants;
|
|
173
|
+
}
|
|
174
|
+
/** Where a skinned stage declares the cloth binding's three textures: binding, particles, rest. */
|
|
175
|
+
function clothBindings(skinned: boolean, morphed: boolean): [number, number, number] {
|
|
176
|
+
const textures = flatVertexBindings(skinned, morphed).textures;
|
|
177
|
+
const at = (name: string): number => {
|
|
178
|
+
const found = textures[name];
|
|
179
|
+
if (found === undefined)
|
|
180
|
+
throw new Error(`flatPass: the skinned vertex shader declares no ${name}`);
|
|
181
|
+
return found.texture;
|
|
182
|
+
};
|
|
183
|
+
return [at('uClothBinding'), at('uClothParticles'), at('uClothRest')];
|
|
184
|
+
}
|
|
185
|
+
|
|
149
186
|
/*
|
|
150
187
|
* **A variant may drop a field; it may never move one.**
|
|
151
188
|
*
|
|
@@ -208,13 +245,33 @@ export function flatFragmentBindings(variant: FlatVariant): StageBindings {
|
|
|
208
245
|
* "GLASS_SHADOWS" not found*, and the frame drew nothing. Every override the variant declares is
|
|
209
246
|
* set, so a switch added to the shader and not to the cache is refused here rather than defaulted.
|
|
210
247
|
*/
|
|
211
|
-
function flatFragmentConstants(
|
|
248
|
+
function flatFragmentConstants(
|
|
249
|
+
variant: FlatVariant,
|
|
250
|
+
cache: PipelineCache,
|
|
251
|
+
model: SurfaceModelKind | null,
|
|
252
|
+
skinPass: SkinHalf = 'whole',
|
|
253
|
+
): Record<string, number> {
|
|
212
254
|
const overrides = flatFragmentBindings(variant).overrides;
|
|
213
255
|
if (overrides?.['GLASS_SHADOWS'] === undefined) {
|
|
214
256
|
throw new Error(`flatPass: variant "${variant}" has no glass switch`);
|
|
215
257
|
}
|
|
258
|
+
const modelSwitch = model === null ? null : SURFACE_MODEL_SWITCH[model];
|
|
216
259
|
const constants: Record<string, number> = {};
|
|
217
260
|
for (const [name, id] of Object.entries(overrides)) {
|
|
261
|
+
/* A model's switch is the pipeline's own, set from its key rather than from the cache. */
|
|
262
|
+
if (name.startsWith('MODEL_')) {
|
|
263
|
+
constants[String(id)] = name === modelSwitch ? 1 : 0;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
/* Skin's two halves under the screen-space blur, set from the key as a model's switch is. */
|
|
267
|
+
if (name === 'SKIN_SCREEN' || name === 'SKIN_DIFFUSE') {
|
|
268
|
+
constants[String(id)] =
|
|
269
|
+
(name === 'SKIN_SCREEN' && skinPass === 'scene') ||
|
|
270
|
+
(name === 'SKIN_DIFFUSE' && skinPass === 'diffuse')
|
|
271
|
+
? 1
|
|
272
|
+
: 0;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
218
275
|
if (!(name in cache.litSwitches)) {
|
|
219
276
|
throw new Error(`flatPass: variant "${variant}" declares a switch "${name}" no cache sets`);
|
|
220
277
|
}
|
|
@@ -395,6 +452,15 @@ export function createFlatBindGroupLayout(
|
|
|
395
452
|
visibility: VISIBILITY_VERTEX,
|
|
396
453
|
texture: { sampleType: 'unfilterable-float' },
|
|
397
454
|
});
|
|
455
|
+
/* The cloth binding's three textures, declared by every skinned stage whether a draw sets a
|
|
456
|
+
cloth or not; see `clothBinding.ts`. */
|
|
457
|
+
for (const binding of clothBindings(skinned, morphed)) {
|
|
458
|
+
entries.push({
|
|
459
|
+
binding,
|
|
460
|
+
visibility: VISIBILITY_VERTEX,
|
|
461
|
+
texture: { sampleType: 'unfilterable-float' },
|
|
462
|
+
});
|
|
463
|
+
}
|
|
398
464
|
}
|
|
399
465
|
|
|
400
466
|
return device.createBindGroupLayout({
|
|
@@ -408,7 +474,7 @@ export function createFlatBindGroupLayout(
|
|
|
408
474
|
*
|
|
409
475
|
* `resolve` is asked for a view by the shader's own name for it, so a caller supplies the
|
|
410
476
|
* shadow maps it has and a stand-in for the rest. **Every declared binding must be filled
|
|
411
|
-
* even when the shader never samples it** —
|
|
477
|
+
* even when the shader never samples it** — the albedo's flag is zero for nearly all geometry
|
|
412
478
|
* and WebGPU still requires something there, which is why `renderer.ts` keeps an
|
|
413
479
|
* `emptyTexture2D` for the same reason on the other side.
|
|
414
480
|
*/
|
|
@@ -423,6 +489,8 @@ export function createFlatBindGroup(
|
|
|
423
489
|
palette: GPUTextureView | null = null,
|
|
424
490
|
/** The delta view for a morphed bind group, or null for an unmorphed one. */
|
|
425
491
|
deltas: GPUTextureView | null = null,
|
|
492
|
+
/** A skinned bind group's cloth views — the draw's, or the stand-in where it sets none. */
|
|
493
|
+
cloth: ClothViews | null = null,
|
|
426
494
|
): GPUBindGroup {
|
|
427
495
|
const fragment = flatFragmentBindings(variant);
|
|
428
496
|
const entries: GPUBindGroupEntry[] = [
|
|
@@ -458,6 +526,15 @@ export function createFlatBindGroup(
|
|
|
458
526
|
throw new Error('flatPass: the skinned vertex shader declares no uJointPalette');
|
|
459
527
|
}
|
|
460
528
|
entries.push({ binding: binding.texture, resource: palette });
|
|
529
|
+
if (cloth === null) {
|
|
530
|
+
throw new Error(
|
|
531
|
+
'flatPass: a skinned bind group needs cloth views, the stand-in at the least',
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
const [bindingAt, particlesAt, restAt] = clothBindings(true, deltas !== null);
|
|
535
|
+
entries.push({ binding: bindingAt, resource: cloth.binding });
|
|
536
|
+
entries.push({ binding: particlesAt, resource: cloth.particles });
|
|
537
|
+
entries.push({ binding: restAt, resource: cloth.rest });
|
|
461
538
|
}
|
|
462
539
|
|
|
463
540
|
return device.createBindGroup({
|
|
@@ -517,6 +594,22 @@ export function flatPipeline(
|
|
|
517
594
|
oit: OitTarget = 'none',
|
|
518
595
|
/** Culls nothing, for a surface seen from both faces. The key must carry it: `|2s`. */
|
|
519
596
|
doubleSided = false,
|
|
597
|
+
/**
|
|
598
|
+
* Turns the fragment's alpha into the pixel's samples, for a dithered cutout in a multisampled
|
|
599
|
+
* frame with no temporal resolve. The key must carry it: `|a2c`. See `cutoutDither.ts`.
|
|
600
|
+
*/
|
|
601
|
+
alphaToCoverage = false,
|
|
602
|
+
/**
|
|
603
|
+
* Whether a skinned pipeline reads the second four influences: the vertex stage's `SKIN_EIGHT`.
|
|
604
|
+
* The key must carry it: `|s8`. See `skinning.ts`.
|
|
605
|
+
*/
|
|
606
|
+
skinEight = false,
|
|
607
|
+
/** Whether its vertex stage places vertices by a cloth binding. See `clothBinding.ts`. */
|
|
608
|
+
cloth = false,
|
|
609
|
+
/** Which shading model its fragment stage shades by, null the standard. The key must carry it. */
|
|
610
|
+
model: SurfaceModelKind | null = null,
|
|
611
|
+
/** Which half of a skin it draws. The key must carry it: `|ss` or `|sd`. See `SkinHalf`. */
|
|
612
|
+
skinPass: SkinHalf = 'whole',
|
|
520
613
|
): GPURenderPipeline {
|
|
521
614
|
return cache.get(
|
|
522
615
|
key,
|
|
@@ -536,6 +629,11 @@ export function flatPipeline(
|
|
|
536
629
|
instanced,
|
|
537
630
|
oit,
|
|
538
631
|
doubleSided,
|
|
632
|
+
alphaToCoverage,
|
|
633
|
+
skinEight,
|
|
634
|
+
cloth,
|
|
635
|
+
model,
|
|
636
|
+
skinPass,
|
|
539
637
|
),
|
|
540
638
|
true,
|
|
541
639
|
);
|
|
@@ -562,11 +660,36 @@ export function flatPipelineAsync(
|
|
|
562
660
|
skinned = false,
|
|
563
661
|
/** Whether this pipeline reads morph deltas. See `FlatVertexOptions`. */
|
|
564
662
|
morphed = false,
|
|
663
|
+
/** Whether the skinned stage reads the second four influences. See `flatPipeline`. */
|
|
664
|
+
skinEight = false,
|
|
665
|
+
/** Whether its vertex stage places vertices by a cloth binding. See `clothBinding.ts`. */
|
|
666
|
+
cloth = false,
|
|
667
|
+
/** Which shading model its fragment stage shades by. See `flatPipeline`. */
|
|
668
|
+
model: SurfaceModelKind | null = null,
|
|
565
669
|
): Promise<GPURenderPipeline> {
|
|
566
670
|
return cache.getAsync(
|
|
567
671
|
key,
|
|
568
672
|
() =>
|
|
569
|
-
flatDescriptor(
|
|
673
|
+
flatDescriptor(
|
|
674
|
+
cache,
|
|
675
|
+
device,
|
|
676
|
+
layout,
|
|
677
|
+
variant,
|
|
678
|
+
key,
|
|
679
|
+
present,
|
|
680
|
+
translucent,
|
|
681
|
+
skinned,
|
|
682
|
+
morphed,
|
|
683
|
+
true,
|
|
684
|
+
0,
|
|
685
|
+
false,
|
|
686
|
+
'none',
|
|
687
|
+
false,
|
|
688
|
+
false,
|
|
689
|
+
skinEight,
|
|
690
|
+
cloth,
|
|
691
|
+
model,
|
|
692
|
+
),
|
|
570
693
|
true,
|
|
571
694
|
);
|
|
572
695
|
}
|
|
@@ -637,8 +760,15 @@ function flatDescriptor(
|
|
|
637
760
|
oit: OitTarget = 'none',
|
|
638
761
|
/** Culls nothing: a surface seen from both faces. */
|
|
639
762
|
doubleSided = false,
|
|
763
|
+
alphaToCoverage = false,
|
|
764
|
+
skinEight = false,
|
|
765
|
+
/** Whether its vertex stage places vertices by a cloth binding. See `clothBinding.ts`. */
|
|
766
|
+
cloth = false,
|
|
767
|
+
model: SurfaceModelKind | null = null,
|
|
768
|
+
skinPass: SkinHalf = 'whole',
|
|
640
769
|
): GPURenderPipelineDescriptor {
|
|
641
770
|
const offset = depthOffsetForLayer(depthLayer);
|
|
771
|
+
const diffuseAlone = skinPass === 'diffuse';
|
|
642
772
|
return {
|
|
643
773
|
label: key,
|
|
644
774
|
layout: device.createPipelineLayout({ bindGroupLayouts: [layout] }),
|
|
@@ -654,6 +784,7 @@ function flatDescriptor(
|
|
|
654
784
|
}),
|
|
655
785
|
entryPoint: 'main',
|
|
656
786
|
buffers: vertexBufferLayouts(present, instanced),
|
|
787
|
+
constants: vertexConstants(flatVertexBindings(skinned, morphed, instanced), skinEight, cloth),
|
|
657
788
|
},
|
|
658
789
|
fragment: {
|
|
659
790
|
module: shaderModule(device, {
|
|
@@ -662,7 +793,7 @@ function flatDescriptor(
|
|
|
662
793
|
}),
|
|
663
794
|
entryPoint: 'main',
|
|
664
795
|
/* Every variant declares it, so every lit pipeline may set it: see `PipelineCache`. */
|
|
665
|
-
constants: flatFragmentConstants(variant, cache),
|
|
796
|
+
constants: flatFragmentConstants(variant, cache, model, skinPass),
|
|
666
797
|
targets: [
|
|
667
798
|
oitTarget(oit) ??
|
|
668
799
|
(translucent === 'additive'
|
|
@@ -689,7 +820,13 @@ function flatDescriptor(
|
|
|
689
820
|
],
|
|
690
821
|
},
|
|
691
822
|
primitive: { topology: 'triangle-list', cullMode: doubleSided ? 'none' : 'back' },
|
|
692
|
-
|
|
823
|
+
/* Coverage only where a sample count can carry it: WebGPU refuses it at one sample. */
|
|
824
|
+
multisample: {
|
|
825
|
+
count: cache.sampleCount,
|
|
826
|
+
...(alphaToCoverage && cache.sampleCount > 1 && !diffuseAlone
|
|
827
|
+
? { alphaToCoverageEnabled: true }
|
|
828
|
+
: {}),
|
|
829
|
+
},
|
|
693
830
|
/*
|
|
694
831
|
* `less`, matching the WebGL2 path's `LESS` rather than `LEQUAL`, and for the reason
|
|
695
832
|
* `drawMesh` documents there: under `LEQUAL` two coplanar surfaces do not merely tie,
|
|
@@ -697,8 +834,9 @@ function flatDescriptor(
|
|
|
697
834
|
*/
|
|
698
835
|
depthStencil: {
|
|
699
836
|
format: DEPTH_FORMAT,
|
|
700
|
-
|
|
701
|
-
|
|
837
|
+
/* The diffuse half is the same surface again: it finds the depth the frame's half wrote. */
|
|
838
|
+
depthWriteEnabled: depthWrite && !diffuseAlone,
|
|
839
|
+
depthCompare: diffuseAlone ? 'equal' : DEPTH_COMPARE,
|
|
702
840
|
/*
|
|
703
841
|
* Baked in, where the other backend toggles it around the draw. Both read
|
|
704
842
|
* `depthOffsetForLayer`, so a layer is worth the same depth on either one, and a pipeline
|