@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,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skin's screen-space scattering on this backend: the targets, the kept draws and the two passes
|
|
3
|
+
* that spread them. `skinBlur.ts` is what the blur computes and why; this is where it runs.
|
|
4
|
+
*
|
|
5
|
+
* **What the renderer hands it.** Each skin draw, recorded into the frame with its diffuse taken
|
|
6
|
+
* out, is recorded a second time here with the diffuse alone. When the frame first draws something
|
|
7
|
+
* blended — or ends, if nothing is — the renderer ends its pass and calls `drawDiffuse`, which
|
|
8
|
+
* replays them into a half-float target against the frame's own depth; resolves the depth; and
|
|
9
|
+
* calls `spread`, which blurs across into a second target and then down, adding into the frame.
|
|
10
|
+
*
|
|
11
|
+
* **The draws are commands, not calls**, for the reason `LatePass` keeps its own pool: the frame
|
|
12
|
+
* graph's is spent at every flush, and these are replayed after the flush that ends the frame's
|
|
13
|
+
* pass. Each holds the per-draw ring slot its frame half used, and those stay valid until the
|
|
14
|
+
* encoder is replaced, which is after this has run.
|
|
15
|
+
*
|
|
16
|
+
* **What it costs**: every skin draw twice; two half-float targets the size of the frame, and a
|
|
17
|
+
* third multisampled one under multisampling; a depth resolve; and two full-screen passes that
|
|
18
|
+
* leave at once wherever no skin was drawn.
|
|
19
|
+
*/
|
|
20
|
+
import { SKIN_PROFILES } from '../../skinBlur.js';
|
|
21
|
+
import { SKINBLUR_BINDINGS, SKIN_BLUR_FRAG_WGSL } from '../../shaders/generated/skinBlur.wgsl.js';
|
|
22
|
+
import { SCENE_ALPHA_KEEPS } from '../../sceneCoverage.js';
|
|
23
|
+
import { createCommandPool, resetPool, takeCommand } from './drawCommand.js';
|
|
24
|
+
import { createPostStageLayout, postPipeline, postPipelineTargets } from './postPass.js';
|
|
25
|
+
import { DYNAMIC_ALIGNMENT } from './uniformRing.js';
|
|
26
|
+
const BINDINGS = SKINBLUR_BINDINGS.SKIN_BLUR_FRAG;
|
|
27
|
+
const SLOT = Math.ceil(BINDINGS.uniformSize / DYNAMIC_ALIGNMENT) * DYNAMIC_ALIGNMENT;
|
|
28
|
+
const USAGE_TARGET = 0x10 | 0x4; // RENDER_ATTACHMENT | TEXTURE_BINDING
|
|
29
|
+
const USAGE_UNIFORM_DST = 0x40 | 0x8; // UNIFORM | COPY_DST
|
|
30
|
+
/** The format the diffuse lands in: half floats, because it is linear light before any grade. */
|
|
31
|
+
export const SKIN_TARGET_FORMAT = 'rgba16float';
|
|
32
|
+
export class SkinScatterPass {
|
|
33
|
+
device;
|
|
34
|
+
layout;
|
|
35
|
+
uniforms;
|
|
36
|
+
staging = new Float32Array((SLOT * 2) / 4);
|
|
37
|
+
linear;
|
|
38
|
+
nearest;
|
|
39
|
+
commands = createCommandPool(16);
|
|
40
|
+
/** Each profile's scatter distance per channel, in metres: four floats a profile. */
|
|
41
|
+
profiles = new Float32Array(SKIN_PROFILES * 4);
|
|
42
|
+
diffuse = null;
|
|
43
|
+
diffuseMsaa = null;
|
|
44
|
+
across = null;
|
|
45
|
+
diffuseView = null;
|
|
46
|
+
diffuseMsaaView = null;
|
|
47
|
+
acrossView = null;
|
|
48
|
+
groupAcross = null;
|
|
49
|
+
groupDown = null;
|
|
50
|
+
groupsDepth = null;
|
|
51
|
+
constructor(device) {
|
|
52
|
+
this.device = device;
|
|
53
|
+
this.layout = createPostStageLayout(device, BINDINGS.uniforms, BINDINGS.uniformSize, [
|
|
54
|
+
{ binding: BINDINGS.textures.uSkin },
|
|
55
|
+
{ binding: BINDINGS.textures.uDepth, filterable: false },
|
|
56
|
+
]);
|
|
57
|
+
this.uniforms = device.createBuffer({
|
|
58
|
+
label: 'skin.blurUniforms',
|
|
59
|
+
size: SLOT * 2,
|
|
60
|
+
usage: USAGE_UNIFORM_DST,
|
|
61
|
+
});
|
|
62
|
+
this.linear = device.createSampler({
|
|
63
|
+
label: 'skin.linear',
|
|
64
|
+
magFilter: 'linear',
|
|
65
|
+
minFilter: 'linear',
|
|
66
|
+
addressModeU: 'clamp-to-edge',
|
|
67
|
+
addressModeV: 'clamp-to-edge',
|
|
68
|
+
});
|
|
69
|
+
this.nearest = device.createSampler({
|
|
70
|
+
label: 'skin.nearest',
|
|
71
|
+
addressModeU: 'clamp-to-edge',
|
|
72
|
+
addressModeV: 'clamp-to-edge',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/** Whether any skin was kept for this frame. */
|
|
76
|
+
get pending() {
|
|
77
|
+
return this.commands.taken > 0;
|
|
78
|
+
}
|
|
79
|
+
/** A command for one skin draw's diffuse half, filled by the caller. */
|
|
80
|
+
take() {
|
|
81
|
+
return this.commands.commands[takeCommand(this.commands)];
|
|
82
|
+
}
|
|
83
|
+
/** What one profile scatters, per channel, in metres: the last material to name it says. */
|
|
84
|
+
setProfile(index, r, g, b) {
|
|
85
|
+
const at = Math.min(Math.max(Math.round(index), 0), SKIN_PROFILES - 1) * 4;
|
|
86
|
+
this.profiles[at] = r;
|
|
87
|
+
this.profiles[at + 1] = g;
|
|
88
|
+
this.profiles[at + 2] = b;
|
|
89
|
+
}
|
|
90
|
+
/** The targets at the frame's size and sample count, made again only when either changes. */
|
|
91
|
+
size(width, height, samples) {
|
|
92
|
+
if (this.diffuse !== null &&
|
|
93
|
+
this.diffuse.width === width &&
|
|
94
|
+
this.diffuse.height === height &&
|
|
95
|
+
samples > 1 === (this.diffuseMsaa !== null) &&
|
|
96
|
+
(this.diffuseMsaa === null || this.diffuseMsaa.sampleCount === samples)) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
this.releaseTargets();
|
|
100
|
+
const make = (label, count) => this.device.createTexture({
|
|
101
|
+
label,
|
|
102
|
+
size: [width, height],
|
|
103
|
+
format: SKIN_TARGET_FORMAT,
|
|
104
|
+
sampleCount: count,
|
|
105
|
+
usage: count > 1 ? 0x10 : USAGE_TARGET,
|
|
106
|
+
});
|
|
107
|
+
this.diffuse = make('skin.diffuse', 1);
|
|
108
|
+
this.across = make('skin.across', 1);
|
|
109
|
+
this.diffuseMsaa = samples > 1 ? make('skin.diffuseMsaa', samples) : null;
|
|
110
|
+
this.diffuseView = this.diffuse.createView();
|
|
111
|
+
this.acrossView = this.across.createView();
|
|
112
|
+
this.diffuseMsaaView = this.diffuseMsaa?.createView() ?? null;
|
|
113
|
+
this.groupAcross = null;
|
|
114
|
+
this.groupDown = null;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Every kept draw's diffuse into the target, against the frame's own depth, which it reads and
|
|
118
|
+
* does not write. Called with no pass open on `encoder`.
|
|
119
|
+
*/
|
|
120
|
+
drawDiffuse(encoder, depth, issue, timestampWrites) {
|
|
121
|
+
const target = this.diffuseView;
|
|
122
|
+
if (target === null)
|
|
123
|
+
return;
|
|
124
|
+
const pass = encoder.beginRenderPass({
|
|
125
|
+
label: 'skin.diffuse',
|
|
126
|
+
timestampWrites,
|
|
127
|
+
colorAttachments: [
|
|
128
|
+
{
|
|
129
|
+
view: this.diffuseMsaaView ?? target,
|
|
130
|
+
resolveTarget: this.diffuseMsaaView === null ? undefined : target,
|
|
131
|
+
clearValue: [0, 0, 0, 0],
|
|
132
|
+
loadOp: 'clear',
|
|
133
|
+
storeOp: this.diffuseMsaaView === null ? 'store' : 'discard',
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
depthStencilAttachment: { view: depth, depthLoadOp: 'load', depthStoreOp: 'store' },
|
|
137
|
+
});
|
|
138
|
+
for (let i = 0; i < this.commands.taken; i += 1) {
|
|
139
|
+
issue(pass, this.commands.commands[i]);
|
|
140
|
+
}
|
|
141
|
+
pass.end();
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Across into the second target, then down and added into the frame's colour — `frame` the
|
|
145
|
+
* attachment its pass draws into, `resolve` where a multisampled one resolves, `samples` its
|
|
146
|
+
* count. `projection` is the frame's, `height` the frame's height in pixels.
|
|
147
|
+
*/
|
|
148
|
+
spread(encoder, pipelines, depth, frame, resolve, samples, projection, inverseProjection, timestampWrites) {
|
|
149
|
+
const diffuse = this.diffuse;
|
|
150
|
+
if (diffuse === null || this.diffuseView === null || this.acrossView === null)
|
|
151
|
+
return;
|
|
152
|
+
if (this.groupAcross === null || this.groupDown === null || this.groupsDepth !== depth) {
|
|
153
|
+
this.groupAcross = this.group('skin.blurAcross', this.diffuseView, depth);
|
|
154
|
+
this.groupDown = this.group('skin.blurDown', this.acrossView, depth);
|
|
155
|
+
this.groupsDepth = depth;
|
|
156
|
+
}
|
|
157
|
+
this.writeUniforms(diffuse.width, diffuse.height, projection, inverseProjection);
|
|
158
|
+
const across = postPipeline(pipelines, this.device, this.layout, 'skin.blurAcross', SKIN_BLUR_FRAG_WGSL, SKIN_TARGET_FORMAT);
|
|
159
|
+
const down = postPipelineTargets(pipelines, this.device, this.layout, `skin.blurDown:${pipelines.format}:${samples}`, SKIN_BLUR_FRAG_WGSL, [
|
|
160
|
+
{
|
|
161
|
+
format: pipelines.format,
|
|
162
|
+
/* Added: its colour onto the frame's, the frame's alpha kept. */
|
|
163
|
+
blend: {
|
|
164
|
+
color: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
|
|
165
|
+
alpha: SCENE_ALPHA_KEEPS,
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
], samples);
|
|
169
|
+
const first = encoder.beginRenderPass({
|
|
170
|
+
label: 'skin.blurAcross',
|
|
171
|
+
timestampWrites: timestampWrites(),
|
|
172
|
+
colorAttachments: [
|
|
173
|
+
{ view: this.acrossView, clearValue: [0, 0, 0, 0], loadOp: 'clear', storeOp: 'store' },
|
|
174
|
+
],
|
|
175
|
+
});
|
|
176
|
+
first.setPipeline(across);
|
|
177
|
+
first.setBindGroup(0, this.groupAcross, [0]);
|
|
178
|
+
first.draw(3);
|
|
179
|
+
first.end();
|
|
180
|
+
const second = encoder.beginRenderPass({
|
|
181
|
+
label: 'skin.blurDown',
|
|
182
|
+
timestampWrites: timestampWrites(),
|
|
183
|
+
colorAttachments: [
|
|
184
|
+
{
|
|
185
|
+
view: frame,
|
|
186
|
+
resolveTarget: resolve ?? undefined,
|
|
187
|
+
loadOp: 'load',
|
|
188
|
+
storeOp: 'store',
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
});
|
|
192
|
+
second.setPipeline(down);
|
|
193
|
+
second.setBindGroup(0, this.groupDown, [SLOT]);
|
|
194
|
+
second.draw(3);
|
|
195
|
+
second.end();
|
|
196
|
+
}
|
|
197
|
+
/** Spent at the frame's start: the kept draws were this frame's. */
|
|
198
|
+
reset() {
|
|
199
|
+
resetPool(this.commands);
|
|
200
|
+
}
|
|
201
|
+
dispose() {
|
|
202
|
+
this.releaseTargets();
|
|
203
|
+
this.uniforms.destroy();
|
|
204
|
+
}
|
|
205
|
+
group(label, skin, depth) {
|
|
206
|
+
return this.device.createBindGroup({
|
|
207
|
+
label,
|
|
208
|
+
layout: this.layout,
|
|
209
|
+
entries: [
|
|
210
|
+
{
|
|
211
|
+
binding: BINDINGS.uniforms,
|
|
212
|
+
resource: { buffer: this.uniforms, size: BINDINGS.uniformSize },
|
|
213
|
+
},
|
|
214
|
+
{ binding: BINDINGS.textures.uSkin.texture, resource: skin },
|
|
215
|
+
{ binding: BINDINGS.textures.uSkin.sampler, resource: this.linear },
|
|
216
|
+
{ binding: BINDINGS.textures.uDepth.texture, resource: depth },
|
|
217
|
+
{ binding: BINDINGS.textures.uDepth.sampler, resource: this.nearest },
|
|
218
|
+
],
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
/** Both axes' blocks: the step along each, and what they share. Column-major matrices. */
|
|
222
|
+
writeUniforms(width, height, projection, inverse) {
|
|
223
|
+
const f = this.staging;
|
|
224
|
+
const fields = BINDINGS.fields;
|
|
225
|
+
for (let axis = 0; axis < 2; axis += 1) {
|
|
226
|
+
const base = (axis * SLOT) / 4;
|
|
227
|
+
f[base + fields.uStep.offset / 4] = axis === 0 ? 1 / width : 0;
|
|
228
|
+
f[base + fields.uStep.offset / 4 + 1] = axis === 0 ? 0 : 1 / height;
|
|
229
|
+
/* The four terms that carry a depth back to view-space metres: the occlusion blur's own. */
|
|
230
|
+
const toZ = base + fields.uDepthToViewZ.offset / 4;
|
|
231
|
+
f[toZ] = inverse[10] ?? 0;
|
|
232
|
+
f[toZ + 1] = inverse[14] ?? 0;
|
|
233
|
+
f[toZ + 2] = inverse[11] ?? 0;
|
|
234
|
+
f[toZ + 3] = inverse[15] ?? 1;
|
|
235
|
+
/* Pixels a metre spans at a metre: the vertical focal length, which is the horizontal one
|
|
236
|
+
too wherever a pixel is square. */
|
|
237
|
+
f[base + fields.uFocal.offset / 4] = (projection[5] * height) / 2;
|
|
238
|
+
f.set(this.profiles, base + fields.uProfiles.offset / 4);
|
|
239
|
+
}
|
|
240
|
+
this.device.queue.writeBuffer(this.uniforms, 0, f);
|
|
241
|
+
}
|
|
242
|
+
releaseTargets() {
|
|
243
|
+
this.diffuse?.destroy();
|
|
244
|
+
this.diffuseMsaa?.destroy();
|
|
245
|
+
this.across?.destroy();
|
|
246
|
+
this.diffuse = null;
|
|
247
|
+
this.diffuseMsaa = null;
|
|
248
|
+
this.across = null;
|
|
249
|
+
this.diffuseView = null;
|
|
250
|
+
this.diffuseMsaaView = null;
|
|
251
|
+
this.acrossView = null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SurfaceTextureOptions } from '../../surfaceTexture.ts';
|
|
2
|
+
import type { CompressedTextureFormat, SurfaceSource } from '../../compressedSource.ts';
|
|
2
3
|
import type { PipelineCache } from './pipelineCache.ts';
|
|
3
4
|
/** How many levels an image of this size has, counting the one it arrives with. */
|
|
4
5
|
export declare function mipLevelCount(width: number, height: number): number;
|
|
@@ -59,7 +60,9 @@ export declare class GpuSurfaceTexture {
|
|
|
59
60
|
private effectsTable;
|
|
60
61
|
/** Its view, which the flat group binds beside this image when it is a material's albedo. */
|
|
61
62
|
effectsView: GPUTextureView | null;
|
|
62
|
-
|
|
63
|
+
/** The block format a compressed texture holds, or null for one uploaded from images. */
|
|
64
|
+
private readonly blockFormat;
|
|
65
|
+
constructor(device: GPUDevice, pipelines: PipelineCache, source: SurfaceSource | readonly SurfaceSource[], options?: SurfaceTextureOptions, compressed?: readonly CompressedTextureFormat[]);
|
|
63
66
|
/** A view of every layer, as the shaders' `texture_2d_array` bindings read it. */
|
|
64
67
|
private arrayView;
|
|
65
68
|
/** The view every binding of this image holds. A new one when `update` changes the size. */
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { SURFACE_EFFECT_TEXELS, packSurfaceEffects } from '../../surfaceEffects.js';
|
|
2
2
|
import { toHalfFloats } from '../../halfFloat.js';
|
|
3
|
-
import {
|
|
3
|
+
import { layerSize, refuseArrayUpdate, sourceSize } from '../../textureSource.js';
|
|
4
|
+
import { compressedLayers, planBlocks, refuseBlockUpdate } from '../../compressedSource.js';
|
|
5
|
+
import { createCompressedTexture } from './compressedUpload.js';
|
|
4
6
|
import { shaderModule } from './shaderModules.js';
|
|
5
7
|
/**
|
|
6
8
|
* A caller's image on the device, and the mip chain WebGPU will not build for it.
|
|
@@ -148,20 +150,41 @@ export class GpuSurfaceTexture {
|
|
|
148
150
|
effectsTable = null;
|
|
149
151
|
/** Its view, which the flat group binds beside this image when it is a material's albedo. */
|
|
150
152
|
effectsView = null;
|
|
151
|
-
|
|
153
|
+
/** The block format a compressed texture holds, or null for one uploaded from images. */
|
|
154
|
+
blockFormat;
|
|
155
|
+
constructor(device, pipelines, source, options = {}, compressed = []) {
|
|
152
156
|
this.device = device;
|
|
153
157
|
this.pipelines = pipelines;
|
|
154
|
-
const
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
this.mipmapped = options.mipmap ?? true;
|
|
158
|
+
const listed = (Array.isArray(source) ? source : [source]);
|
|
159
|
+
const blocks = compressedLayers(listed);
|
|
160
|
+
const srgb = (options.colorSpace ?? 'linear') === 'srgb';
|
|
158
161
|
/* `SRGB8_ALPHA8`'s equivalent. Decoded in the sampler, before filtering, which is the only
|
|
159
162
|
place it is correct — `surfaceTexture.ts` makes the argument in full. */
|
|
160
|
-
this.format =
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
this.format = srgb ? 'rgba8unorm-srgb' : 'rgba8unorm';
|
|
164
|
+
if (blocks !== null) {
|
|
165
|
+
/* Refused before anything is allocated; the stored chain, or level 0 alone where no chain
|
|
166
|
+
was asked for. See `compressedUpload.ts`. */
|
|
167
|
+
const plan = planBlocks(blocks, srgb, compressed);
|
|
168
|
+
const first = blocks[0];
|
|
169
|
+
this.layers = blocks.length;
|
|
170
|
+
this.blockFormat = first.format;
|
|
171
|
+
this.mipmapped = (options.mipmap ?? true) && plan.levels > 1;
|
|
172
|
+
this.width = first.width;
|
|
173
|
+
this.height = first.height;
|
|
174
|
+
this.levels = this.mipmapped ? plan.levels : 1;
|
|
175
|
+
this.texture = createCompressedTexture(device, plan.name, blocks, this.levels);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
const sources = listed;
|
|
179
|
+
const { width, height } = layerSize(sources);
|
|
180
|
+
this.layers = sources.length;
|
|
181
|
+
this.blockFormat = null;
|
|
182
|
+
this.mipmapped = options.mipmap ?? true;
|
|
183
|
+
this.width = width;
|
|
184
|
+
this.height = height;
|
|
185
|
+
this.levels = this.mipmapped ? mipLevelCount(width, height) : 1;
|
|
186
|
+
this.texture = this.allocate();
|
|
187
|
+
}
|
|
165
188
|
this.current = this.arrayView(this.texture);
|
|
166
189
|
const wrap = (options.wrap ?? 'repeat') === 'repeat' ? 'repeat' : 'clamp-to-edge';
|
|
167
190
|
/*
|
|
@@ -193,7 +216,8 @@ export class GpuSurfaceTexture {
|
|
|
193
216
|
? Math.max(1, Math.floor(options.anisotropy ?? DEFAULT_ANISOTROPY))
|
|
194
217
|
: 1,
|
|
195
218
|
});
|
|
196
|
-
|
|
219
|
+
if (blocks === null)
|
|
220
|
+
this.upload(listed);
|
|
197
221
|
/* Half floats, as `renderer.ts` uploads it: filterable, so the ordinary float layout takes it,
|
|
198
222
|
and read by `textureLoad` alone, so the filter never runs. */
|
|
199
223
|
const effects = options.effects ?? [];
|
|
@@ -236,6 +260,7 @@ export class GpuSurfaceTexture {
|
|
|
236
260
|
if (replaced === null)
|
|
237
261
|
return null;
|
|
238
262
|
refuseArrayUpdate(this.layers);
|
|
263
|
+
refuseBlockUpdate(this.blockFormat);
|
|
239
264
|
const { width, height } = sourceSize(source);
|
|
240
265
|
if (width === this.width && height === this.height) {
|
|
241
266
|
this.upload([source]);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A mesh's cloth binding as data, and the texel layouts both backends upload it in.
|
|
3
|
+
*
|
|
4
|
+
* **What a consumer hands over** is per render vertex: the simulation triangle it follows (three
|
|
5
|
+
* particle indices — the same index three times for a vertex that follows one particle), where on
|
|
6
|
+
* it (barycentric u and v for the second and third particle), how far along the triangle's normal,
|
|
7
|
+
* and how much it follows at all (0 skinned, 1 cloth — commonly painted into a vertex colour); and
|
|
8
|
+
* the particles' rest positions in the mesh's model space. A garment's port decodes all of this
|
|
9
|
+
* from the cooked data; the engine owns placing the vertices, in `shaders/clothBinding.ts`.
|
|
10
|
+
*
|
|
11
|
+
* **Textures wrap onto rows of at most 2048 texels**, the width WebGL2 guarantees, padded to whole
|
|
12
|
+
* rows: a garment of 40,000 vertices is 80,000 binding texels, 40 rows. Validated by name before a
|
|
13
|
+
* byte goes to a device, because an index past the particles reads the padding — a vertex pinned
|
|
14
|
+
* to the origin, which looks like a broken rig rather than a bad binding.
|
|
15
|
+
*/
|
|
16
|
+
/** A mesh's cloth binding, one entry a render vertex unless said otherwise. */
|
|
17
|
+
export interface ClothBindingData {
|
|
18
|
+
/** Three particle indices a vertex. */
|
|
19
|
+
readonly triangles: Uint32Array;
|
|
20
|
+
/** Barycentric u and v a vertex, for the second and third particle; the first takes the rest. */
|
|
21
|
+
readonly coordinates: Float32Array;
|
|
22
|
+
/** Metres along the triangle's normal. */
|
|
23
|
+
readonly offsets: Float32Array;
|
|
24
|
+
/** How much of the cloth's place a vertex takes: 0 skinned, 1 cloth. */
|
|
25
|
+
readonly weights: Float32Array;
|
|
26
|
+
/** The particles at rest, three floats each, in the mesh's model space. */
|
|
27
|
+
readonly rest: Float32Array;
|
|
28
|
+
}
|
|
29
|
+
/** The widest row a texture is wrapped onto. */
|
|
30
|
+
export declare const CLOTH_TEXTURE_WIDTH = 2048;
|
|
31
|
+
/** A texture of `texels`, wrapped onto rows. */
|
|
32
|
+
export declare function clothTextureSize(texels: number): {
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
};
|
|
36
|
+
/** Refuse a binding whose arrays disagree with a mesh of `vertices`, or that names a missing particle. */
|
|
37
|
+
export declare function validateClothBinding(data: ClothBindingData, vertices: number): void;
|
|
38
|
+
/** The binding as RGBA float texels, two a vertex, padded to whole rows. */
|
|
39
|
+
export declare function packClothBinding(data: ClothBindingData): Float32Array;
|
|
40
|
+
/** Particles, three floats each, into RGBA texels in `out`. Allocation-free, for every frame. */
|
|
41
|
+
export declare function packClothParticles(positions: Float32Array, out: Float32Array): void;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A mesh's cloth binding as data, and the texel layouts both backends upload it in.
|
|
3
|
+
*
|
|
4
|
+
* **What a consumer hands over** is per render vertex: the simulation triangle it follows (three
|
|
5
|
+
* particle indices — the same index three times for a vertex that follows one particle), where on
|
|
6
|
+
* it (barycentric u and v for the second and third particle), how far along the triangle's normal,
|
|
7
|
+
* and how much it follows at all (0 skinned, 1 cloth — commonly painted into a vertex colour); and
|
|
8
|
+
* the particles' rest positions in the mesh's model space. A garment's port decodes all of this
|
|
9
|
+
* from the cooked data; the engine owns placing the vertices, in `shaders/clothBinding.ts`.
|
|
10
|
+
*
|
|
11
|
+
* **Textures wrap onto rows of at most 2048 texels**, the width WebGL2 guarantees, padded to whole
|
|
12
|
+
* rows: a garment of 40,000 vertices is 80,000 binding texels, 40 rows. Validated by name before a
|
|
13
|
+
* byte goes to a device, because an index past the particles reads the padding — a vertex pinned
|
|
14
|
+
* to the origin, which looks like a broken rig rather than a bad binding.
|
|
15
|
+
*/
|
|
16
|
+
/** The widest row a texture is wrapped onto. */
|
|
17
|
+
export const CLOTH_TEXTURE_WIDTH = 2048;
|
|
18
|
+
/** A texture of `texels`, wrapped onto rows. */
|
|
19
|
+
export function clothTextureSize(texels) {
|
|
20
|
+
const width = Math.max(1, Math.min(CLOTH_TEXTURE_WIDTH, texels));
|
|
21
|
+
return { width, height: Math.max(1, Math.ceil(texels / width)) };
|
|
22
|
+
}
|
|
23
|
+
/** Refuse a binding whose arrays disagree with a mesh of `vertices`, or that names a missing particle. */
|
|
24
|
+
export function validateClothBinding(data, vertices) {
|
|
25
|
+
const count = data.weights.length;
|
|
26
|
+
const fail = (what) => {
|
|
27
|
+
throw new Error(`cloth binding: ${what}`);
|
|
28
|
+
};
|
|
29
|
+
if (count !== vertices)
|
|
30
|
+
fail(`binds ${count} vertices and the mesh has ${vertices}`);
|
|
31
|
+
if (data.triangles.length !== count * 3)
|
|
32
|
+
fail(`${data.triangles.length / 3} triangles for ${count} vertices`);
|
|
33
|
+
if (data.coordinates.length !== count * 2)
|
|
34
|
+
fail(`${data.coordinates.length / 2} coordinates for ${count} vertices`);
|
|
35
|
+
if (data.offsets.length !== count)
|
|
36
|
+
fail(`${data.offsets.length} offsets for ${count} vertices`);
|
|
37
|
+
const particles = data.rest.length / 3;
|
|
38
|
+
if (!Number.isInteger(particles) || particles === 0)
|
|
39
|
+
fail(`rest holds ${data.rest.length} floats`);
|
|
40
|
+
for (let i = 0; i < data.triangles.length; i++) {
|
|
41
|
+
if (data.triangles[i] >= particles) {
|
|
42
|
+
fail(`vertex ${Math.floor(i / 3)} names particle ${data.triangles[i]} of ${particles}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** The binding as RGBA float texels, two a vertex, padded to whole rows. */
|
|
47
|
+
export function packClothBinding(data) {
|
|
48
|
+
const vertices = data.weights.length;
|
|
49
|
+
const { width, height } = clothTextureSize(vertices * 2);
|
|
50
|
+
const out = new Float32Array(width * height * 4);
|
|
51
|
+
for (let v = 0; v < vertices; v++) {
|
|
52
|
+
const at = v * 8;
|
|
53
|
+
out[at] = data.triangles[v * 3];
|
|
54
|
+
out[at + 1] = data.triangles[v * 3 + 1];
|
|
55
|
+
out[at + 2] = data.triangles[v * 3 + 2];
|
|
56
|
+
out[at + 3] = data.weights[v];
|
|
57
|
+
out[at + 4] = data.coordinates[v * 2];
|
|
58
|
+
out[at + 5] = data.coordinates[v * 2 + 1];
|
|
59
|
+
out[at + 6] = data.offsets[v];
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
/** Particles, three floats each, into RGBA texels in `out`. Allocation-free, for every frame. */
|
|
64
|
+
export function packClothParticles(positions, out) {
|
|
65
|
+
const count = positions.length / 3;
|
|
66
|
+
for (let i = 0; i < count; i++) {
|
|
67
|
+
out[i * 4] = positions[i * 3];
|
|
68
|
+
out[i * 4 + 1] = positions[i * 3 + 1];
|
|
69
|
+
out[i * 4 + 2] = positions[i * 3 + 2];
|
|
70
|
+
out[i * 4 + 3] = 0;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a cloth-bound vertex goes: the reference for what the vertex stage computes.
|
|
3
|
+
*
|
|
4
|
+
* A render vertex bound to a simulation triangle (particles a, b, c) sits at its barycentric point
|
|
5
|
+
* — `a·(1 − u − v) + b·u + c·v` — raised `offset` along the triangle's normal; its normal turns as
|
|
6
|
+
* the triangle turned since rest. The turn is the rotation from the triangle's rest frame
|
|
7
|
+
* to its frame now, each frame built from its first edge and its normal, so a vertex beside the
|
|
8
|
+
* triangle, a fold's width away, moves as the cloth moves rather than as its skeleton does.
|
|
9
|
+
*
|
|
10
|
+
* **A vertex bound to one particle** — all three indices equal — sits on it and keeps its normal:
|
|
11
|
+
* a single point has no frame to turn by. That is how a binding says "follow this particle".
|
|
12
|
+
*
|
|
13
|
+
* **A reference, as `recon/motionVectors.ts` is**: nothing at run time calls it. The vertex stage in
|
|
14
|
+
* `shaders/clothBinding.ts` and the cloth motion stage in `shaders/recon/motion.wgsl.ts` are the
|
|
15
|
+
* same arithmetic for the device, and nothing but a device holds them to this — the source tests
|
|
16
|
+
* pin where the shader moves a vertex, not the numbers it moves it by. `demo/dev/skinnedCloth.html`
|
|
17
|
+
* is where the two backends were compared, and they agree to two levels in 255.
|
|
18
|
+
*/
|
|
19
|
+
/** Where a placement lands, written into caller-owned arrays. */
|
|
20
|
+
export interface ClothPlacement {
|
|
21
|
+
readonly position: Float64Array;
|
|
22
|
+
readonly normal: Float64Array;
|
|
23
|
+
}
|
|
24
|
+
/** Place one vertex: `now` and `rest` are particle positions, three floats each. */
|
|
25
|
+
export declare function clothPlace(now: ArrayLike<number>, rest: ArrayLike<number>, a: number, b: number, c: number, u: number, v: number, offset: number, restNormal: readonly [number, number, number], out: ClothPlacement): void;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a cloth-bound vertex goes: the reference for what the vertex stage computes.
|
|
3
|
+
*
|
|
4
|
+
* A render vertex bound to a simulation triangle (particles a, b, c) sits at its barycentric point
|
|
5
|
+
* — `a·(1 − u − v) + b·u + c·v` — raised `offset` along the triangle's normal; its normal turns as
|
|
6
|
+
* the triangle turned since rest. The turn is the rotation from the triangle's rest frame
|
|
7
|
+
* to its frame now, each frame built from its first edge and its normal, so a vertex beside the
|
|
8
|
+
* triangle, a fold's width away, moves as the cloth moves rather than as its skeleton does.
|
|
9
|
+
*
|
|
10
|
+
* **A vertex bound to one particle** — all three indices equal — sits on it and keeps its normal:
|
|
11
|
+
* a single point has no frame to turn by. That is how a binding says "follow this particle".
|
|
12
|
+
*
|
|
13
|
+
* **A reference, as `recon/motionVectors.ts` is**: nothing at run time calls it. The vertex stage in
|
|
14
|
+
* `shaders/clothBinding.ts` and the cloth motion stage in `shaders/recon/motion.wgsl.ts` are the
|
|
15
|
+
* same arithmetic for the device, and nothing but a device holds them to this — the source tests
|
|
16
|
+
* pin where the shader moves a vertex, not the numbers it moves it by. `demo/dev/skinnedCloth.html`
|
|
17
|
+
* is where the two backends were compared, and they agree to two levels in 255.
|
|
18
|
+
*/
|
|
19
|
+
const NOW = new Float64Array(9);
|
|
20
|
+
const REST = new Float64Array(9);
|
|
21
|
+
/** A triangle's frame — first edge, normal × edge, normal — as three columns, into `out`. */
|
|
22
|
+
function frame(particles, a, b, c, out) {
|
|
23
|
+
const ex = particles[b * 3] - particles[a * 3];
|
|
24
|
+
const ey = particles[b * 3 + 1] - particles[a * 3 + 1];
|
|
25
|
+
const ez = particles[b * 3 + 2] - particles[a * 3 + 2];
|
|
26
|
+
const fx = particles[c * 3] - particles[a * 3];
|
|
27
|
+
const fy = particles[c * 3 + 1] - particles[a * 3 + 1];
|
|
28
|
+
const fz = particles[c * 3 + 2] - particles[a * 3 + 2];
|
|
29
|
+
let nx = ey * fz - ez * fy;
|
|
30
|
+
let ny = ez * fx - ex * fz;
|
|
31
|
+
let nz = ex * fy - ey * fx;
|
|
32
|
+
const nl = Math.sqrt(nx * nx + ny * ny + nz * nz) || 1;
|
|
33
|
+
nx /= nl;
|
|
34
|
+
ny /= nl;
|
|
35
|
+
nz /= nl;
|
|
36
|
+
const el = Math.sqrt(ex * ex + ey * ey + ez * ez) || 1;
|
|
37
|
+
const tx = ex / el;
|
|
38
|
+
const ty = ey / el;
|
|
39
|
+
const tz = ez / el;
|
|
40
|
+
out[0] = tx;
|
|
41
|
+
out[1] = ty;
|
|
42
|
+
out[2] = tz;
|
|
43
|
+
out[3] = ny * tz - nz * ty;
|
|
44
|
+
out[4] = nz * tx - nx * tz;
|
|
45
|
+
out[5] = nx * ty - ny * tx;
|
|
46
|
+
out[6] = nx;
|
|
47
|
+
out[7] = ny;
|
|
48
|
+
out[8] = nz;
|
|
49
|
+
}
|
|
50
|
+
/** Place one vertex: `now` and `rest` are particle positions, three floats each. */
|
|
51
|
+
export function clothPlace(now, rest, a, b, c, u, v, offset, restNormal, out) {
|
|
52
|
+
if (a === b && b === c) {
|
|
53
|
+
out.position[0] = now[a * 3];
|
|
54
|
+
out.position[1] = now[a * 3 + 1];
|
|
55
|
+
out.position[2] = now[a * 3 + 2];
|
|
56
|
+
out.normal[0] = restNormal[0];
|
|
57
|
+
out.normal[1] = restNormal[1];
|
|
58
|
+
out.normal[2] = restNormal[2];
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
frame(now, a, b, c, NOW);
|
|
62
|
+
frame(rest, a, b, c, REST);
|
|
63
|
+
const w = 1 - u - v;
|
|
64
|
+
for (let k = 0; k < 3; k++) {
|
|
65
|
+
out.position[k] =
|
|
66
|
+
now[a * 3 + k] * w +
|
|
67
|
+
now[b * 3 + k] * u +
|
|
68
|
+
now[c * 3 + k] * v +
|
|
69
|
+
NOW[6 + k] * offset;
|
|
70
|
+
}
|
|
71
|
+
/* Turned by now × restᵀ: the rest normal in the rest frame's terms, rebuilt in the frame now. */
|
|
72
|
+
const t = REST[0] * restNormal[0] +
|
|
73
|
+
REST[1] * restNormal[1] +
|
|
74
|
+
REST[2] * restNormal[2];
|
|
75
|
+
const s = REST[3] * restNormal[0] +
|
|
76
|
+
REST[4] * restNormal[1] +
|
|
77
|
+
REST[5] * restNormal[2];
|
|
78
|
+
const n = REST[6] * restNormal[0] +
|
|
79
|
+
REST[7] * restNormal[1] +
|
|
80
|
+
REST[8] * restNormal[2];
|
|
81
|
+
for (let k = 0; k < 3; k++) {
|
|
82
|
+
out.normal[k] =
|
|
83
|
+
NOW[k] * t + NOW[3 + k] * s + NOW[6 + k] * n;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A block-compressed image a caller hands to `createSurfaceTexture`, and the one decision about it
|
|
3
|
+
* both backends share: whether this device takes it as blocks.
|
|
4
|
+
*
|
|
5
|
+
* **Uploaded as it is, or not at all.** A BC texture costs a byte a texel on the GPU (BC7) or half
|
|
6
|
+
* of one (BC1) against four for RGBA, and keeps the mip chain its author built rather than one a box
|
|
7
|
+
* filter makes. A device that cannot sample the format — most phones, which have ASTC and ETC2 —
|
|
8
|
+
* gets nothing from the blocks, and the engine does not decode them here: core ships no decoder.
|
|
9
|
+
* The loader in `@driftengine/assets` asks `uploadsCompressed` first and decodes at load where the
|
|
10
|
+
* answer is no; a caller handing a source this device cannot take is refused by name.
|
|
11
|
+
*
|
|
12
|
+
* Backend-neutral, because both backends must agree on what they accept before either touches a
|
|
13
|
+
* device. `BlockFormat` is the same union `@driftengine/drft` names `BcFormat`; core imports nothing
|
|
14
|
+
* from that package, and a format added there without a twin here fails where the loader passes one
|
|
15
|
+
* to the other, at compile time.
|
|
16
|
+
*/
|
|
17
|
+
/** BC1 to BC5 and BC7. BC6H and the signed forms are not among them. */
|
|
18
|
+
export type BlockFormat = 'bc1' | 'bc2' | 'bc3' | 'bc4' | 'bc5' | 'bc7';
|
|
19
|
+
/** A format as a device offers it: the colour space is part of the name, as WebGPU's are. */
|
|
20
|
+
export type CompressedTextureFormat = 'bc1' | 'bc1-srgb' | 'bc2' | 'bc2-srgb' | 'bc3' | 'bc3-srgb' | 'bc4' | 'bc5' | 'bc7' | 'bc7-srgb';
|
|
21
|
+
/** Block-compressed texels with their stored mip chain, level 0 first. */
|
|
22
|
+
export interface CompressedTextureSource {
|
|
23
|
+
readonly format: BlockFormat;
|
|
24
|
+
readonly width: number;
|
|
25
|
+
readonly height: number;
|
|
26
|
+
readonly levels: readonly Uint8Array[];
|
|
27
|
+
}
|
|
28
|
+
/** What `createSurfaceTexture` takes: an image, or blocks. */
|
|
29
|
+
export type SurfaceSource = TexImageSource | CompressedTextureSource;
|
|
30
|
+
/**
|
|
31
|
+
* The layers as blocks, or null where every one is an image. An array mixing the two is refused:
|
|
32
|
+
* one GPU texture has one format.
|
|
33
|
+
*/
|
|
34
|
+
export declare function compressedLayers(sources: readonly SurfaceSource[]): readonly CompressedTextureSource[] | null;
|
|
35
|
+
/** Bytes per 4x4 block. */
|
|
36
|
+
export declare const BLOCK_BYTES: Readonly<Record<BlockFormat, number>>;
|
|
37
|
+
/** Whether `source` is a compressed source rather than an image. */
|
|
38
|
+
export declare function isCompressedSource(source: unknown): source is CompressedTextureSource;
|
|
39
|
+
/** The format a device would have to offer, or null for one with no sRGB form. */
|
|
40
|
+
export declare function compressedFormatName(format: BlockFormat, srgb: boolean): CompressedTextureFormat | null;
|
|
41
|
+
/**
|
|
42
|
+
* Whether a `width` by `height` source in `format` uploads as blocks on a device offering
|
|
43
|
+
* `available`. Level 0 must be whole blocks each way: WebGPU refuses any other size for a BC
|
|
44
|
+
* texture, and WebGL2's S3TC extension says the same, so a 6x6 image is decoded rather than
|
|
45
|
+
* refused. What would make that wrong is a device that pads; none is relied on here.
|
|
46
|
+
*/
|
|
47
|
+
export declare function uploadsCompressed(format: BlockFormat, srgb: boolean, width: number, height: number, available: readonly CompressedTextureFormat[]): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* How many levels the layers carry, after checking each is the size its place requires and every
|
|
50
|
+
* layer matches the first. Refuses by name: a short level uploaded anyway is a device error at
|
|
51
|
+
* submit, which on WebGPU is a frame that draws nothing and says so somewhere else.
|
|
52
|
+
*/
|
|
53
|
+
export declare function compressedLevels(layers: readonly CompressedTextureSource[]): number;
|
|
54
|
+
/** What a refusal names: the format, its colour space, and what the device offers instead. */
|
|
55
|
+
export declare function compressedRefusal(format: BlockFormat, srgb: boolean, width: number, height: number, available: readonly CompressedTextureFormat[]): string;
|
|
56
|
+
/** The format name and level count blocks upload as here, or a refusal naming why they cannot. */
|
|
57
|
+
export declare function planBlocks(blocks: readonly CompressedTextureSource[], srgb: boolean, compressed: readonly CompressedTextureFormat[]): {
|
|
58
|
+
readonly name: CompressedTextureFormat;
|
|
59
|
+
readonly levels: number;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* `update` replaces pixels with an image, and a texture holding blocks has no pixels to replace:
|
|
63
|
+
* its format is the blocks', and an image would be a different texture. Refused rather than
|
|
64
|
+
* quietly reallocated, so the handle a draw holds keeps meaning what it meant.
|
|
65
|
+
*/
|
|
66
|
+
export declare function refuseBlockUpdate(format: BlockFormat | null): void;
|