@driftengine/core 4.13.6 → 4.14.1

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.
Files changed (91) hide show
  1. package/CHANGELOG.json +62 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.js +1 -0
  5. package/dist/render/backend/api.d.ts +19 -3
  6. package/dist/render/backend/webgl2/motionTarget.d.ts +39 -0
  7. package/dist/render/backend/webgl2/motionTarget.js +254 -0
  8. package/dist/render/backend/webgl2/pointSkinFeedback.d.ts +13 -3
  9. package/dist/render/backend/webgl2/pointSkinFeedback.js +45 -12
  10. package/dist/render/backend/webgl2/renderer.d.ts +101 -7
  11. package/dist/render/backend/webgl2/renderer.js +337 -9
  12. package/dist/render/backend/webgpu/pointSkinPass.d.ts +16 -3
  13. package/dist/render/backend/webgpu/pointSkinPass.js +79 -21
  14. package/dist/render/backend/webgpu/postPass.d.ts +6 -1
  15. package/dist/render/backend/webgpu/renderer.d.ts +41 -0
  16. package/dist/render/backend/webgpu/renderer.js +143 -5
  17. package/dist/render/baseDecals.d.ts +48 -0
  18. package/dist/render/baseDecals.js +71 -0
  19. package/dist/render/emissiveFlow.d.ts +40 -0
  20. package/dist/render/emissiveFlow.js +51 -0
  21. package/dist/render/fixtureAtlas.d.ts +8 -3
  22. package/dist/render/fixtureAtlas.js +17 -6
  23. package/dist/render/globalMedium.d.ts +17 -0
  24. package/dist/render/globalMedium.js +14 -0
  25. package/dist/render/globalMediumPass.js +3 -2
  26. package/dist/render/iridescentOverlay.d.ts +78 -0
  27. package/dist/render/iridescentOverlay.js +102 -0
  28. package/dist/render/mesh.d.ts +24 -0
  29. package/dist/render/mesh.js +79 -0
  30. package/dist/render/pointSkin.d.ts +18 -0
  31. package/dist/render/pointSkin.js +50 -5
  32. package/dist/render/recon/mover.d.ts +3 -2
  33. package/dist/render/recon/mover.js +3 -2
  34. package/dist/render/sceneTarget.d.ts +2 -0
  35. package/dist/render/sceneTarget.js +1 -1
  36. package/dist/render/shaders/flat/baseDecals.d.ts +17 -0
  37. package/dist/render/shaders/flat/baseDecals.js +71 -0
  38. package/dist/render/shaders/flat/emissiveFlow.d.ts +15 -0
  39. package/dist/render/shaders/flat/emissiveFlow.js +45 -0
  40. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  41. package/dist/render/shaders/flat/glassTint.js +3 -0
  42. package/dist/render/shaders/flat/index.d.ts +16 -0
  43. package/dist/render/shaders/flat/index.js +9 -0
  44. package/dist/render/shaders/flat/iridescentOverlay.d.ts +15 -0
  45. package/dist/render/shaders/flat/iridescentOverlay.js +65 -0
  46. package/dist/render/shaders/flat/layered.js +4 -4
  47. package/dist/render/shaders/flat/main.d.ts +1 -1
  48. package/dist/render/shaders/flat/main.js +12 -1
  49. package/dist/render/shaders/flat/mapReads.d.ts +1 -1
  50. package/dist/render/shaders/flat/mapReads.js +7 -3
  51. package/dist/render/shaders/flat/worldUv.js +6 -5
  52. package/dist/render/shaders/generated/flat.wgsl.d.ts +480 -16
  53. package/dist/render/shaders/generated/flat.wgsl.js +519 -55
  54. package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +15 -5
  55. package/dist/render/shaders/generated/temporalResolve.wgsl.js +15 -5
  56. package/dist/render/shaders/temporalResolve.js +25 -9
  57. package/dist/render/surfaceTexture.d.ts +32 -0
  58. package/dist/render/temporalPass.d.ts +3 -1
  59. package/dist/render/temporalPass.js +9 -1
  60. package/package.json +6 -6
  61. package/src/index.ts +3 -0
  62. package/src/render/backend/api.ts +19 -2
  63. package/src/render/backend/webgl2/motionTarget.ts +299 -0
  64. package/src/render/backend/webgl2/pointSkinFeedback.ts +49 -14
  65. package/src/render/backend/webgl2/renderer.ts +411 -9
  66. package/src/render/backend/webgpu/pointSkinPass.ts +90 -23
  67. package/src/render/backend/webgpu/renderer.ts +163 -3
  68. package/src/render/baseDecals.ts +93 -0
  69. package/src/render/emissiveFlow.ts +66 -0
  70. package/src/render/fixtureAtlas.ts +26 -7
  71. package/src/render/globalMedium.ts +31 -0
  72. package/src/render/globalMediumPass.ts +3 -7
  73. package/src/render/iridescentOverlay.ts +177 -0
  74. package/src/render/mesh.ts +82 -0
  75. package/src/render/pointSkin.ts +64 -5
  76. package/src/render/recon/mover.ts +3 -2
  77. package/src/render/sceneTarget.ts +3 -0
  78. package/src/render/shaders/flat/baseDecals.ts +71 -0
  79. package/src/render/shaders/flat/emissiveFlow.ts +45 -0
  80. package/src/render/shaders/flat/glassTint.ts +7 -1
  81. package/src/render/shaders/flat/index.ts +25 -0
  82. package/src/render/shaders/flat/iridescentOverlay.ts +65 -0
  83. package/src/render/shaders/flat/layered.ts +4 -4
  84. package/src/render/shaders/flat/main.ts +12 -1
  85. package/src/render/shaders/flat/mapReads.ts +7 -3
  86. package/src/render/shaders/flat/worldUv.ts +6 -5
  87. package/src/render/shaders/generated/flat.wgsl.ts +519 -55
  88. package/src/render/shaders/generated/temporalResolve.wgsl.ts +15 -5
  89. package/src/render/shaders/temporalResolve.ts +25 -9
  90. package/src/render/surfaceTexture.ts +32 -0
  91. package/src/render/temporalPass.ts +8 -0
@@ -8,7 +8,7 @@
8
8
  * for every resource a later write in the frame overwrites. A skin posed twice in a frame draws its
9
9
  * last pose everywhere, as a mesh rewritten twice with `updateMesh` does.
10
10
  */
11
- import { reachOf } from '../../pointSkin.js';
11
+ import { reachOf, widenReach } from '../../pointSkin.js';
12
12
  import { POINT_SKIN_WGSL, POINT_SKIN_WORKGROUP } from './shaders/pointSkin.wgsl.js';
13
13
  const STORAGE = 0x0080;
14
14
  const UNIFORM = 0x0040;
@@ -26,28 +26,31 @@ export function createPointSkinPipeline(device) {
26
26
  }
27
27
  export class GpuPointSkin {
28
28
  mesh;
29
+ /** Widened by each rewrite, never narrowed: see `widenReach`. */
29
30
  reach;
30
31
  count;
31
32
  pipeline;
32
33
  joints;
34
+ /** Params, points with their weights, joints named, normals: in that order. */
33
35
  buffers;
34
36
  group;
37
+ /** A rewrite's vertices laid out as the buffers hold them, grown to the largest rewrite. */
38
+ scratch = {
39
+ points: new Float32Array(0),
40
+ named: new Uint32Array(0),
41
+ normals: new Float32Array(0),
42
+ };
35
43
  constructor(device, pipeline, mesh, data) {
36
44
  this.mesh = mesh;
37
45
  this.pipeline = pipeline;
38
46
  this.reach = reachOf(data);
39
47
  const count = data.weights.length / 4;
40
48
  this.count = count;
41
- /* Each influence a vec4: its point, and its weight in w. */
42
- const points = new Float32Array(count * 16);
43
- for (let i = 0; i < count * 4; i++) {
44
- points.set(data.points.subarray(i * 3, i * 3 + 3), i * 4);
45
- points[i * 4 + 3] = data.weights[i];
46
- }
47
- const named = Uint32Array.from(data.joints);
48
- const normals = new Float32Array(count * 4);
49
- for (let v = 0; v < count; v++)
50
- normals.set(data.normals.subarray(v * 3, v * 3 + 3), v * 4);
49
+ const { points, named, normals } = laidOut(data, count, {
50
+ points: new Float32Array(count * 16),
51
+ named: new Uint32Array(count * 4),
52
+ normals: new Float32Array(count * 4),
53
+ });
51
54
  const params = new Uint32Array([count, 0, 0, 0]);
52
55
  const made = (label, contents, usage) => {
53
56
  const buffer = device.createBuffer({
@@ -58,22 +61,21 @@ export class GpuPointSkin {
58
61
  device.queue.writeBuffer(buffer, 0, contents);
59
62
  return buffer;
60
63
  };
61
- this.joints = device.createBuffer({
62
- label: 'pointSkin.joints',
63
- size: Math.max(1, this.reach.joints) * 64,
64
- usage: STORAGE | COPY_DST,
65
- });
66
- const buffers = [
64
+ this.joints = jointBuffer(device, this.reach.joints);
65
+ this.buffers = [
67
66
  made('pointSkin.params', params, UNIFORM),
68
67
  made('pointSkin.points', points, STORAGE),
69
68
  made('pointSkin.named', named, STORAGE),
70
69
  made('pointSkin.normals', normals, STORAGE),
71
70
  ];
72
- this.buffers = buffers;
73
- const [positionsOut, normalsOut] = mesh.vertexBuffers;
74
- this.group = device.createBindGroup({
71
+ this.group = this.bind(device);
72
+ }
73
+ bind(device) {
74
+ const buffers = this.buffers;
75
+ const [positionsOut, normalsOut] = this.mesh.vertexBuffers;
76
+ return device.createBindGroup({
75
77
  label: 'pointSkin.pose',
76
- layout: pipeline.getBindGroupLayout(0),
78
+ layout: this.pipeline.getBindGroupLayout(0),
77
79
  entries: [
78
80
  { binding: 0, resource: { buffer: buffers[0] } },
79
81
  { binding: 1, resource: { buffer: this.joints } },
@@ -85,6 +87,36 @@ export class GpuPointSkin {
85
87
  ],
86
88
  });
87
89
  }
90
+ /**
91
+ * Rewrite vertices `first` onward, as many as `data` holds, in place: three queue writes at their
92
+ * offsets. **A queue write reaches every dispatch submitted after it**, so the renderer submits a
93
+ * pose already recorded before calling this, and the rewrite reaches the next pose alone, as it
94
+ * does on WebGL2. A joint none of the skin named before grows the joints buffer, which is made
95
+ * again with its bind group; the old one goes once the work already submitted is done with it.
96
+ */
97
+ rewrite(device, first, data) {
98
+ const count = data.weights.length / 4;
99
+ const scratch = this.scratch;
100
+ if (scratch.named.length < count * 4) {
101
+ this.scratch = {
102
+ points: new Float32Array(count * 16),
103
+ named: new Uint32Array(count * 4),
104
+ normals: new Float32Array(count * 4),
105
+ };
106
+ }
107
+ const { points, named, normals } = laidOut(data, count, this.scratch);
108
+ const buffers = this.buffers;
109
+ device.queue.writeBuffer(buffers[1], first * 64, points, 0, count * 16);
110
+ device.queue.writeBuffer(buffers[2], first * 16, named, 0, count * 4);
111
+ device.queue.writeBuffer(buffers[3], first * 16, normals, 0, count * 4);
112
+ const joints = this.reach.joints;
113
+ this.reach = widenReach(this.reach, data);
114
+ if (Math.max(1, this.reach.joints) > Math.max(1, joints)) {
115
+ this.joints.destroy();
116
+ this.joints = jointBuffer(device, this.reach.joints);
117
+ this.group = this.bind(device);
118
+ }
119
+ }
88
120
  /** Write the pose's joints and record the dispatch that poses every vertex into `encoder`. */
89
121
  pose(device, encoder, matrices) {
90
122
  if (this.reach.joints > 0) {
@@ -102,3 +134,29 @@ export class GpuPointSkin {
102
134
  buffer.destroy();
103
135
  }
104
136
  }
137
+ function jointBuffer(device, joints) {
138
+ return device.createBuffer({
139
+ label: 'pointSkin.joints',
140
+ size: Math.max(1, joints) * 64,
141
+ usage: STORAGE | COPY_DST,
142
+ });
143
+ }
144
+ /**
145
+ * `count` vertices of `data` as the buffers hold them, into `out`'s leading entries: each influence
146
+ * a vec4 of its point and its weight in w, the joints as integers, each normal a vec4.
147
+ */
148
+ function laidOut(data, count, out) {
149
+ for (let i = 0; i < count * 4; i++) {
150
+ out.points[i * 4] = data.points[i * 3];
151
+ out.points[i * 4 + 1] = data.points[i * 3 + 1];
152
+ out.points[i * 4 + 2] = data.points[i * 3 + 2];
153
+ out.points[i * 4 + 3] = data.weights[i];
154
+ out.named[i] = data.joints[i];
155
+ }
156
+ for (let v = 0; v < count; v++) {
157
+ out.normals[v * 4] = data.normals[v * 3];
158
+ out.normals[v * 4 + 1] = data.normals[v * 3 + 1];
159
+ out.normals[v * 4 + 2] = data.normals[v * 3 + 2];
160
+ }
161
+ return out;
162
+ }
@@ -215,7 +215,7 @@ sampleCount?: number): GPURenderPipeline;
215
215
  * draws a plausible picture.
216
216
  */
217
217
  export declare const TAA_FRAG_FIELDS: UniformFields;
218
- export declare const TAA_FRAG_SIZE: 112;
218
+ export declare const TAA_FRAG_SIZE: 128;
219
219
  export declare const TAA_UNIFORMS: 1;
220
220
  export declare const TAA_TEXTURES: {
221
221
  readonly uScene: {
@@ -243,6 +243,11 @@ export declare const TAA_TEXTURES: {
243
243
  readonly sampler: 41;
244
244
  readonly type: "sampler2D";
245
245
  };
246
+ readonly uMotion: {
247
+ readonly texture: 42;
248
+ readonly sampler: 43;
249
+ readonly type: "sampler2D";
250
+ };
246
251
  };
247
252
  /**
248
253
  * The order-independent resolve's inputs. It has no uniform block of its own — the whole pass is
@@ -26,6 +26,7 @@ import { GpuPointSkin } from './pointSkinPass.ts';
26
26
  import { type PointSkinData } from '../../pointSkin.ts';
27
27
  import type { ComputeHost } from './computeHost.ts';
28
28
  import { type SurfaceOverlay } from '../../surfaceOverlay.ts';
29
+ import { type BaseDecal } from '../../baseDecals.ts';
29
30
  import type { ClothBindingData } from '../../clothBindingData.ts';
30
31
  import type { CompressedTextureFormat, CompressedTextureSource, SurfaceSource } from '../../compressedSource.ts';
31
32
  import { GpuSdfText } from './sdfTextPass.ts';
@@ -1215,6 +1216,12 @@ export declare class WebGPURenderer implements RendererApi {
1215
1216
  private warnedLayersModelMap;
1216
1217
  /** `uDetail`, the material's tiled detail packed by `packSurfaceDetail`. */
1217
1218
  private readonly detailFloats;
1219
+ /** `uEmissiveFlow`, a material's glow moved and masked, packed by `packEmissiveFlow`. */
1220
+ private readonly flowFloats;
1221
+ /** `uIridescence`, a material's iridescent overlay packed by `packIridescentOverlay`. */
1222
+ private readonly iridescentFloats;
1223
+ /** The refusals of an overlay already said, each once. */
1224
+ private readonly warnedIridescent;
1218
1225
  /** The detail refusals already said, each once. See `detailOfMaterial`. */
1219
1226
  private readonly warnedDetail;
1220
1227
  private warnedLayersTriplanar;
@@ -2142,6 +2149,8 @@ export declare class WebGPURenderer implements RendererApi {
2142
2149
  /** The moving layer's own matrix on, the first time a frame draws that layer with one. */
2143
2150
  private enableMovingSun;
2144
2151
  /** One of the lit stage's switches on, in every cache a lit pipeline can come from. */
2152
+ /** A material's iridescent overlay, or null where it is malformed, said once. */
2153
+ private iridescentOf;
2145
2154
  private enableLit;
2146
2155
  /** One resolution every glass caster is read through, so recording one allocates nothing. */
2147
2156
  private readonly glassCasterScratch;
@@ -2419,6 +2428,13 @@ export declare class WebGPURenderer implements RendererApi {
2419
2428
  * a destroyed buffer in a submit loses the whole command buffer. Nothing else reads them, so once
2420
2429
  * the rewrites are submitted, which only moves them earlier, the buffers can go at once.
2421
2430
  */
2431
+ /**
2432
+ * Rewrite part of a skin in place. **A pose already recorded is submitted first**, since a queue
2433
+ * write reaches every dispatch submitted after it and would otherwise reach a pose the caller made
2434
+ * before the rewrite; then the pose after it alone reads the rewrite, as on WebGL2. A rewrite with
2435
+ * nothing pending submits nothing.
2436
+ */
2437
+ updatePointSkin(skin: GpuPointSkin, first: number, data: PointSkinData): void;
2422
2438
  disposePointSkin(skin: GpuPointSkin): void;
2423
2439
  /**
2424
2440
  * Rewrite a mesh's colours. Refused in words on a mesh that did not ask for `dynamicColors`,
@@ -4217,6 +4233,14 @@ export declare class WebGPURenderer implements RendererApi {
4217
4233
  /** The profiles as packed and the cookie images, kept so either can rebuild the atlas. */
4218
4234
  private iesAtlas;
4219
4235
  private cookieImages;
4236
+ /** The images the frame's base decals lay, in the atlas's band of them. See `setBaseDecalImages`. */
4237
+ private decalImages;
4238
+ /** The decals as last laid, packed again whenever the cookies move their band. */
4239
+ private baseDecalList;
4240
+ /** `uBaseDecals`, held until the next `setBaseDecals`, across passes. */
4241
+ private readonly baseDecalFloats;
4242
+ /** The refusals of a decal or its image already said, each once. */
4243
+ private readonly warnedBaseDecals;
4220
4244
  /** The scene's DriftLight field, and its two volumes once it is whole. See `createLightField`. */
4221
4245
  private lightField;
4222
4246
  private driftIndex;
@@ -4388,6 +4412,23 @@ export declare class WebGPURenderer implements RendererApi {
4388
4412
  * call a tile fills the row without a canvas in between.
4389
4413
  */
4390
4414
  setSpotCookies(images: readonly TexImageSource[]): void;
4415
+ /**
4416
+ * The images the frame's base decals lay, each a `BASE_DECAL_TILE` square. The same count again
4417
+ * copies the squares into the atlas and nothing else; a different count builds it again. See
4418
+ * `renderer.ts` and `baseDecals.ts`. **Once a frame, before the draws they mark**: a copy reaches
4419
+ * every draw of the frame it is made in, as any queue write does.
4420
+ */
4421
+ setBaseDecalImages(images: readonly TexImageSource[]): void;
4422
+ /** The decal images into their squares of the atlas. */
4423
+ private copyDecalImages;
4424
+ /** Whether an image is a decal's square, said once where it is not. */
4425
+ private decalImageFits;
4426
+ /**
4427
+ * The decals laid into the base colours of the draws after it, until the next call, across
4428
+ * passes. In the material block, as the overlay is, and written again where each pass starts it.
4429
+ * See `renderer.ts` and `baseDecals.ts`.
4430
+ */
4431
+ setBaseDecals(decals: readonly BaseDecal[]): void;
4391
4432
  /** Let the atlas go, to be built again from what is in hand the next time it is bound. */
4392
4433
  private forgetFixtureAtlas;
4393
4434
  /**
@@ -42,7 +42,7 @@ import { ProbeSweeps } from '../../probeSweeps.js';
42
42
  import { ggxMaxLevelFor, irradianceLevelFor, octahedralEdgeFor } from '../../prefilterEnvMap.js';
43
43
  import { environmentTexels } from './environmentTexels.js';
44
44
  import { packIesAtlas } from '../../iesProfile.js';
45
- import { fixtureAtlasLayout, fixtureAtlasTexels } from '../../fixtureAtlas.js';
45
+ import { decalBandTop, fixtureAtlasLayout, fixtureAtlasTexels } from '../../fixtureAtlas.js';
46
46
  import { resolveSurfaceScene } from '../../surfaceScene.js';
47
47
  import { COOKIE_TILE } from '../../lightBudget.js';
48
48
  import { FULL_LIGHT_BUDGET } from '../../uniformVectorBudget.js';
@@ -64,7 +64,7 @@ import { gpuCompressedFormats } from './compressedUpload.js';
64
64
  import { GpuClothBinding, GpuClothParticles, clothStandIn } from './clothTextures.js';
65
65
  import { GroupShelf } from './groupShelf.js';
66
66
  import { GpuPointSkin, createPointSkinPipeline } from './pointSkinPass.js';
67
- import { poseBounds, validatePointSkin, validatePose, } from '../../pointSkin.js';
67
+ import { poseBounds, validatePointSkin, validatePointSkinRange, validatePose, } from '../../pointSkin.js';
68
68
  import { MODEL_PARAM_FLOATS, eyeAxisInWorld, packModel, skinProfileDistance, } from '../../surfaceModel.js';
69
69
  import { OVERLAY_FLOATS, overlayLays, packSurfaceOverlay, } from '../../surfaceOverlay.js';
70
70
  import { PROJECTION_FLOATS, packSurfaceProjection } from '../../surfaceProjection.js';
@@ -74,6 +74,10 @@ import { orthographicDepthSpan } from '../../lightMatrix.js';
74
74
  import { LAYER_FLOATS, layerMaskMap, layeredProjection, layersFitBeside, packSurfaceLayers, } from '../../surfaceLayers.js';
75
75
  import { DETAIL_FLOATS, detailOf, detailRefusal, detailRefusalWarning, packSurfaceDetail, } from '../../surfaceDetail.js';
76
76
  import { LOOK_FLOATS, asksLayerLooks, packLayerLooks } from '../../surfaceLayerLooks.js';
77
+ import { EMISSIVE_FLOW_FLOATS, packEmissiveFlow } from '../../emissiveFlow.js';
78
+ import { BASE_DECAL_FLOATS, BASE_DECAL_TILE, packBaseDecals, } from '../../baseDecals.js';
79
+ import { sourceSize } from '../../textureSource.js';
80
+ import { IRIDESCENT_FLOATS, iridescentRefusal, packIridescentOverlay, } from '../../iridescentOverlay.js';
77
81
  import { frameReflectionsRefused, resolveFrameReflections, } from '../../frameReflections.js';
78
82
  import { REFLECTION_SURFACE_FORMAT, ReflectionSurfacePass } from './reflectionSurfacePass.js';
79
83
  import { validateClothBinding } from '../../clothBindingData.js';
@@ -106,7 +110,7 @@ import { PARTICLE_INDICES } from '../../shaders/particle.js';
106
110
  import { createScatterDeform, resolveScatterDeform, } from '../../scatterDeform.js';
107
111
  import { createResolvedPointLights, resolvePointLights, } from '../../lightBudget.js';
108
112
  import { resolveRenderQuality } from '../../renderQuality.js';
109
- import { DEFAULT_GLOBAL_MEDIUM, WEAK_GPU_MEDIUM_STEPS, createResolvedMediumLamps, mediumActive, mediumTargetSize, resolveGlobalMedium, resolveGlobalMediumSettings, resolveMediumLamps, } from '../../globalMedium.js';
113
+ import { mediumSkyFill, DEFAULT_GLOBAL_MEDIUM, WEAK_GPU_MEDIUM_STEPS, createResolvedMediumLamps, mediumActive, mediumTargetSize, resolveGlobalMedium, resolveGlobalMediumSettings, resolveMediumLamps, } from '../../globalMedium.js';
110
114
  import { castsDepth, glassOf } from '../../shadowCasters.js';
111
115
  import { materialHasMaps, noteMapsWithoutUvs } from '../../mapsWithoutUvs.js';
112
116
  import { VIEW_MODEL_DEPTH_SHARE, viewModelDepthRange } from '../../viewModel.js';
@@ -157,6 +161,8 @@ import { fitProbeGrid } from '../../gi/probeGridFit.js';
157
161
  * `fitProbeGrid` widens it uniformly when the budget bites, so a large scene gets a coarser grid
158
162
  * rather than a truncated one.
159
163
  */
164
+ /** The ambient a medium is lit by in a frame bound with no environment: none. */
165
+ const MEDIUM_NO_AMBIENT = [0, 0, 0];
160
166
  const INDIRECT_PROBE_SPACING = 2;
161
167
  /** What the trace is given when a frame never said what was lighting it. */
162
168
  const NO_SUN = [0, 0, 0];
@@ -2283,6 +2289,12 @@ export class WebGPURenderer {
2283
2289
  warnedLayersModelMap = false;
2284
2290
  /** `uDetail`, the material's tiled detail packed by `packSurfaceDetail`. */
2285
2291
  detailFloats = new Float32Array(DETAIL_FLOATS);
2292
+ /** `uEmissiveFlow`, a material's glow moved and masked, packed by `packEmissiveFlow`. */
2293
+ flowFloats = new Float32Array(EMISSIVE_FLOW_FLOATS);
2294
+ /** `uIridescence`, a material's iridescent overlay packed by `packIridescentOverlay`. */
2295
+ iridescentFloats = new Float32Array(IRIDESCENT_FLOATS);
2296
+ /** The refusals of an overlay already said, each once. */
2297
+ warnedIridescent = new Set();
2286
2298
  /** The detail refusals already said, each once. See `detailOfMaterial`. */
2287
2299
  warnedDetail = new Set();
2288
2300
  warnedLayersTriplanar = false;
@@ -2750,6 +2762,18 @@ export class WebGPURenderer {
2750
2762
  f[emissive] = asked?.[0] ?? 1;
2751
2763
  f[emissive + 1] = asked?.[1] ?? 1;
2752
2764
  f[emissive + 2] = asked?.[2] ?? 1;
2765
+ /* And its glow's scroll and mask, the first time a material asks: emissiveFlow.ts. */
2766
+ if (packEmissiveFlow(material, this.flowFloats))
2767
+ this.enableLit('EMISSIVE_FLOW');
2768
+ f.set(this.flowFloats, this.materialField('uEmissiveFlow'));
2769
+ /* And its iridescent overlay, the first time a material lays one: iridescentOverlay.ts. */
2770
+ const iridescent = this.iridescentOf(material);
2771
+ packIridescentOverlay(iridescent, material ?? {}, this.iridescentFloats);
2772
+ f.set(this.iridescentFloats, this.materialField('uIridescence'));
2773
+ /* And whether the frame's decals mark it. See `SurfaceMaterial.receivesDecals`. */
2774
+ f[this.materialField('uDecalReceiver')] = material?.receivesDecals === false ? 1 : 0;
2775
+ if (iridescent !== null)
2776
+ this.enableLit('IRIDESCENT_OVERLAY');
2753
2777
  /* The model's numbers and whether its map is bound. See `packModel`. */
2754
2778
  packModel(material?.model ?? null, ownModelMap !== null, this.modelParams, material?.physicalSpecular === true);
2755
2779
  /* The first physical highlight asked for is when the lit pipelines start carrying one. */
@@ -5148,6 +5172,20 @@ export class WebGPURenderer {
5148
5172
  }
5149
5173
  }
5150
5174
  /** One of the lit stage's switches on, in every cache a lit pipeline can come from. */
5175
+ /** A material's iridescent overlay, or null where it is malformed, said once. */
5176
+ iridescentOf(material) {
5177
+ const overlay = material?.iridescentOverlay ?? null;
5178
+ if (overlay === null)
5179
+ return null;
5180
+ const refusal = iridescentRefusal(overlay);
5181
+ if (refusal === null)
5182
+ return overlay;
5183
+ if (!this.warnedIridescent.has(refusal)) {
5184
+ this.warnedIridescent.add(refusal);
5185
+ console.warn(`Renderer: an iridescent overlay is refused: ${refusal}. Drawn without it.`);
5186
+ }
5187
+ return null;
5188
+ }
5151
5189
  enableLit(feature) {
5152
5190
  if (this.pipelines.litSwitches[feature])
5153
5191
  return;
@@ -6278,6 +6316,9 @@ export class WebGPURenderer {
6278
6316
  { binding: TAA_TEXTURES.uFlicker },
6279
6317
  { binding: TAA_TEXTURES.uFlickerMotion },
6280
6318
  { binding: TAA_TEXTURES.uDepth, filterable: false },
6319
+ /* WebGL2's movers' motion (`motionTarget.ts`); here the depth stands in and is never read,
6320
+ `uMotionOn` being 0, since this backend follows movers through its reconstruction. */
6321
+ { binding: TAA_TEXTURES.uMotion, filterable: false },
6281
6322
  ]);
6282
6323
  this.taaUniforms = device.createBuffer({
6283
6324
  label: 'post.taaUniforms',
@@ -7120,6 +7161,20 @@ export class WebGPURenderer {
7120
7161
  * a destroyed buffer in a submit loses the whole command buffer. Nothing else reads them, so once
7121
7162
  * the rewrites are submitted, which only moves them earlier, the buffers can go at once.
7122
7163
  */
7164
+ /**
7165
+ * Rewrite part of a skin in place. **A pose already recorded is submitted first**, since a queue
7166
+ * write reaches every dispatch submitted after it and would otherwise reach a pose the caller made
7167
+ * before the rewrite; then the pose after it alone reads the rewrite, as on WebGL2. A rewrite with
7168
+ * nothing pending submits nothing.
7169
+ */
7170
+ updatePointSkin(skin, first, data) {
7171
+ validatePointSkinRange(data, first, skin.mesh.vertexCount);
7172
+ if (this.surface.lost)
7173
+ return;
7174
+ if (this.pendingRewrites !== null)
7175
+ this.submit([]);
7176
+ skin.rewrite(this.surface.device, first, data);
7177
+ }
7123
7178
  disposePointSkin(skin) {
7124
7179
  if (this.pendingRewrites !== null && !this.surface.lost)
7125
7180
  this.submit([]);
@@ -7277,6 +7332,13 @@ export class WebGPURenderer {
7277
7332
  f.fill(0, at('uLayerLooks'), at('uLayerLooks') + LOOK_FLOATS);
7278
7333
  /* Nor detail. */
7279
7334
  f.fill(0, at('uDetail'), at('uDetail') + DETAIL_FLOATS);
7335
+ /* Nor a glow that moves or is masked. */
7336
+ f.fill(0, at('uEmissiveFlow'), at('uEmissiveFlow') + EMISSIVE_FLOW_FLOATS);
7337
+ /* Nor an iridescent overlay. */
7338
+ f.fill(0, at('uIridescence'), at('uIridescence') + IRIDESCENT_FLOATS);
7339
+ /* The frame's decals as last laid, held across passes, and every surface receiving them. */
7340
+ f.set(this.baseDecalFloats, at('uBaseDecals'));
7341
+ f[at('uDecalReceiver')] = 0;
7280
7342
  f[at('uCutout')] = 0;
7281
7343
  f[at('uCutout') + 1] = 0;
7282
7344
  this.cutoutResolveStaged = 'hard';
@@ -11318,6 +11380,8 @@ export class WebGPURenderer {
11318
11380
  f[depthRow + 3] = 1;
11319
11381
  }
11320
11382
  f[at('uAntiFlicker')] = 1;
11383
+ /* The camera's motion alone: this backend follows a mover through its reconstruction. */
11384
+ f[at('uMotionOn')] = 0;
11321
11385
  device.queue.writeBuffer(this.taaUniforms, 0, this.taaStaging);
11322
11386
  const pass = encoder.beginRenderPass({
11323
11387
  label: 'post.taa',
@@ -11726,7 +11790,7 @@ export class WebGPURenderer {
11726
11790
  f.set(this.frameEye, at('uCameraPos'));
11727
11791
  f.set(env?.directionalDir ?? [0, 1, 0], at('uSunDir'));
11728
11792
  f.set(env?.directionalColor ?? [0, 0, 0], at('uSunColor'));
11729
- f.set(env?.ambient ?? [0, 0, 0], at('uAmbient'));
11793
+ f.set(mediumSkyFill(options, env?.ambient ?? MEDIUM_NO_AMBIENT), at('uAmbient'));
11730
11794
  f[at('uDensity')] = options.density;
11731
11795
  const height = at('uHeight');
11732
11796
  f[height] = options.heightFalloff;
@@ -14658,6 +14722,14 @@ export class WebGPURenderer {
14658
14722
  /** The profiles as packed and the cookie images, kept so either can rebuild the atlas. */
14659
14723
  iesAtlas = packIesAtlas([]);
14660
14724
  cookieImages = [];
14725
+ /** The images the frame's base decals lay, in the atlas's band of them. See `setBaseDecalImages`. */
14726
+ decalImages = [];
14727
+ /** The decals as last laid, packed again whenever the cookies move their band. */
14728
+ baseDecalList = [];
14729
+ /** `uBaseDecals`, held until the next `setBaseDecals`, across passes. */
14730
+ baseDecalFloats = new Float32Array(BASE_DECAL_FLOATS);
14731
+ /** The refusals of a decal or its image already said, each once. */
14732
+ warnedBaseDecals = new Set();
14661
14733
  /** The scene's DriftLight field, and its two volumes once it is whole. See `createLightField`. */
14662
14734
  lightField = null;
14663
14735
  driftIndex = null;
@@ -15153,6 +15225,70 @@ export class WebGPURenderer {
15153
15225
  this.cookieImages = images.slice();
15154
15226
  this.forgetFixtureAtlas();
15155
15227
  this.rebuildFlatBindGroup();
15228
+ /* The decals' band starts below the cookies, so their rows move with them. */
15229
+ if (this.baseDecalList.length > 0)
15230
+ this.setBaseDecals(this.baseDecalList);
15231
+ }
15232
+ /**
15233
+ * The images the frame's base decals lay, each a `BASE_DECAL_TILE` square. The same count again
15234
+ * copies the squares into the atlas and nothing else; a different count builds it again. See
15235
+ * `renderer.ts` and `baseDecals.ts`. **Once a frame, before the draws they mark**: a copy reaches
15236
+ * every draw of the frame it is made in, as any queue write does.
15237
+ */
15238
+ setBaseDecalImages(images) {
15239
+ if (this.surface.lost)
15240
+ return;
15241
+ if (images.length === this.decalImages.length && this.fixtureTexture !== null) {
15242
+ this.decalImages = images;
15243
+ this.copyDecalImages(this.fixtureTexture);
15244
+ return;
15245
+ }
15246
+ this.decalImages = images.slice();
15247
+ this.forgetFixtureAtlas();
15248
+ this.rebuildFlatBindGroup();
15249
+ this.setBaseDecals(this.baseDecalList);
15250
+ }
15251
+ /** The decal images into their squares of the atlas. */
15252
+ copyDecalImages(texture) {
15253
+ const top = decalBandTop(this.cookieImages.length);
15254
+ for (let tile = 0; tile < this.decalImages.length; tile++) {
15255
+ const source = this.decalImages[tile];
15256
+ if (source === undefined || !this.decalImageFits(source))
15257
+ continue;
15258
+ this.surface.device.queue.copyExternalImageToTexture({ source: source }, { texture, origin: { x: tile * BASE_DECAL_TILE, y: top } }, { width: BASE_DECAL_TILE, height: BASE_DECAL_TILE });
15259
+ }
15260
+ }
15261
+ /** Whether an image is a decal's square, said once where it is not. */
15262
+ decalImageFits(image) {
15263
+ const { width, height } = sourceSize(image);
15264
+ if (width === BASE_DECAL_TILE && height === BASE_DECAL_TILE)
15265
+ return true;
15266
+ const refusal = `a base decal image of ${width} by ${height}; it is a square of ${BASE_DECAL_TILE} ` +
15267
+ 'texels, and is left clear';
15268
+ if (!this.warnedBaseDecals.has(refusal)) {
15269
+ this.warnedBaseDecals.add(refusal);
15270
+ console.warn(`WebGPU: ${refusal}.`);
15271
+ }
15272
+ return false;
15273
+ }
15274
+ /**
15275
+ * The decals laid into the base colours of the draws after it, until the next call, across
15276
+ * passes. In the material block, as the overlay is, and written again where each pass starts it.
15277
+ * See `renderer.ts` and `baseDecals.ts`.
15278
+ */
15279
+ setBaseDecals(decals) {
15280
+ if (this.surface.lost)
15281
+ return;
15282
+ this.baseDecalList = decals;
15283
+ const refusal = packBaseDecals(decals, this.decalImages.length, decalBandTop(this.cookieImages.length), this.baseDecalFloats);
15284
+ if (refusal !== null && !this.warnedBaseDecals.has(refusal)) {
15285
+ this.warnedBaseDecals.add(refusal);
15286
+ console.warn(`WebGPU: ${refusal}.`);
15287
+ }
15288
+ if (this.baseDecalFloats[0] > 0)
15289
+ this.enableLit('BASE_DECALS');
15290
+ this.perFrameFloats.set(this.baseDecalFloats, this.materialField('uBaseDecals'));
15291
+ this.materials.dirty();
15156
15292
  }
15157
15293
  /** Let the atlas go, to be built again from what is in hand the next time it is bound. */
15158
15294
  forgetFixtureAtlas() {
@@ -15172,7 +15308,7 @@ export class WebGPURenderer {
15172
15308
  if (this.fixtureView !== null)
15173
15309
  return this.fixtureView;
15174
15310
  const { device } = this.surface;
15175
- const layout = fixtureAtlasLayout(this.iesAtlas.height, this.cookieImages.length);
15311
+ const layout = fixtureAtlasLayout(this.iesAtlas.height, this.cookieImages.length, this.decalImages.length);
15176
15312
  const texture = device.createTexture({
15177
15313
  label: 'fixture.atlas',
15178
15314
  size: [layout.width, layout.height],
@@ -15188,6 +15324,7 @@ export class WebGPURenderer {
15188
15324
  continue;
15189
15325
  device.queue.copyExternalImageToTexture({ source: source }, { texture, origin: { x: tile * COOKIE_TILE, y: 0 } }, { width: COOKIE_TILE, height: COOKIE_TILE });
15190
15326
  }
15327
+ this.copyDecalImages(texture);
15191
15328
  this.fixtureTexture = texture;
15192
15329
  this.fixtureView = texture.createView();
15193
15330
  this.iesRows = this.iesAtlas.height;
@@ -15903,6 +16040,7 @@ export class WebGPURenderer {
15903
16040
  ...entry(TAA_TEXTURES.uFlicker, record),
15904
16041
  ...entry(TAA_TEXTURES.uFlickerMotion, motion),
15905
16042
  ...entry(TAA_TEXTURES.uDepth, this.resolvedDepthView, true),
16043
+ ...entry(TAA_TEXTURES.uMotion, this.resolvedDepthView, true),
15906
16044
  ],
15907
16045
  });
15908
16046
  }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Decals laid into the base colour of what they land on, before it is lit: `setBaseDecals` and
3
+ * `setBaseDecalImages`, packed into `uBaseDecals` for the lit stage's `BASE_DECALS` switch.
4
+ *
5
+ * **What they are for, and why `DecalProjector` is not it.** A projected decal multiplies the
6
+ * finished pixel, so where the surface under it glows, a lettered floor or a lit panel, the mark
7
+ * dims the glow too. A shadow a body throws on a lit floor, a footprint, a stain is a change in what
8
+ * the surface *is*: it darkens the colour light falls on and leaves what the surface emits, how
9
+ * rough it is and its normal as they are. So each of these is a box, as a `DecalBox` is, an image
10
+ * read across its face, and the base colour moved toward the image's colour by its alpha.
11
+ *
12
+ * **The images ride in the fixture atlas**, a band of `BASE_DECAL_TILE` squares between the cookies
13
+ * and the photometric profiles (`fixtureAtlas.ts`), because the lit stage has no texture binding
14
+ * left (`textureUnitBudget.test.ts`). An image is that square, read as a picture: its colour is
15
+ * decoded from sRGB and its alpha is as it is. Rewriting them once a frame is the use they are for,
16
+ * and costs an upload of the squares; changing how many there are builds the atlas again.
17
+ *
18
+ * **What it gives up**: eight a frame, the uniforms they take being the limit (thirty-four vectors,
19
+ * so WebGL2 refuses them, in words, on a part whose lit stage has no room); the GPU-driven pipeline
20
+ * does not draw them; and a surface opts out by its material (`receivesDecals: false`), not by a
21
+ * mask of what each decal may mark.
22
+ */
23
+ import { type DecalBox } from './decalProjector.ts';
24
+ /** Decals a frame. */
25
+ export declare const MAX_BASE_DECALS = 8;
26
+ /** Texels across an image's square in the atlas. */
27
+ export declare const BASE_DECAL_TILE = 128;
28
+ /** Floats in `uBaseDecals`: a header, then four vectors a decal. */
29
+ export declare const BASE_DECAL_FLOATS: number;
30
+ /** One decal: a box, which image it lays, and how much. */
31
+ export interface BaseDecal extends DecalBox {
32
+ /** Which of `setBaseDecalImages`' images it lays, from 0. */
33
+ readonly image: number;
34
+ /** How much of the image's alpha lands, 0 to 1. Default 1. */
35
+ readonly opacity?: number;
36
+ /**
37
+ * How far from facing back along `forward` a surface may be turned and still be marked, as a
38
+ * cosine of the geometric normal against it. Default 0.2, so a floor is marked and the side of a
39
+ * step it stands on is not smeared. -1 marks every surface in the box.
40
+ */
41
+ readonly facingCos?: number;
42
+ }
43
+ /**
44
+ * Pack `decals` against `images` laid in the atlas band starting at row `bandTop` into `out`, and
45
+ * return why one was left out, or null where none was: past the eighth, an image there is none of,
46
+ * or a box with no size or no orientation. The rest are packed in order.
47
+ */
48
+ export declare function packBaseDecals(decals: readonly BaseDecal[], images: number, bandTop: number, out: Float32Array): string | null;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Decals laid into the base colour of what they land on, before it is lit: `setBaseDecals` and
3
+ * `setBaseDecalImages`, packed into `uBaseDecals` for the lit stage's `BASE_DECALS` switch.
4
+ *
5
+ * **What they are for, and why `DecalProjector` is not it.** A projected decal multiplies the
6
+ * finished pixel, so where the surface under it glows, a lettered floor or a lit panel, the mark
7
+ * dims the glow too. A shadow a body throws on a lit floor, a footprint, a stain is a change in what
8
+ * the surface *is*: it darkens the colour light falls on and leaves what the surface emits, how
9
+ * rough it is and its normal as they are. So each of these is a box, as a `DecalBox` is, an image
10
+ * read across its face, and the base colour moved toward the image's colour by its alpha.
11
+ *
12
+ * **The images ride in the fixture atlas**, a band of `BASE_DECAL_TILE` squares between the cookies
13
+ * and the photometric profiles (`fixtureAtlas.ts`), because the lit stage has no texture binding
14
+ * left (`textureUnitBudget.test.ts`). An image is that square, read as a picture: its colour is
15
+ * decoded from sRGB and its alpha is as it is. Rewriting them once a frame is the use they are for,
16
+ * and costs an upload of the squares; changing how many there are builds the atlas again.
17
+ *
18
+ * **What it gives up**: eight a frame, the uniforms they take being the limit (thirty-four vectors,
19
+ * so WebGL2 refuses them, in words, on a part whose lit stage has no room); the GPU-driven pipeline
20
+ * does not draw them; and a surface opts out by its material (`receivesDecals: false`), not by a
21
+ * mask of what each decal may mark.
22
+ */
23
+ import { worldToDecalMatrix } from './decalProjector.js';
24
+ /** Decals a frame. */
25
+ export const MAX_BASE_DECALS = 8;
26
+ /** Texels across an image's square in the atlas. */
27
+ export const BASE_DECAL_TILE = 128;
28
+ /** Floats in `uBaseDecals`: a header, then four vectors a decal. */
29
+ export const BASE_DECAL_FLOATS = (1 + MAX_BASE_DECALS * 4) * 4;
30
+ const MATRIX = new Float32Array(16);
31
+ /**
32
+ * Pack `decals` against `images` laid in the atlas band starting at row `bandTop` into `out`, and
33
+ * return why one was left out, or null where none was: past the eighth, an image there is none of,
34
+ * or a box with no size or no orientation. The rest are packed in order.
35
+ */
36
+ export function packBaseDecals(decals, images, bandTop, out) {
37
+ out.fill(0);
38
+ let refusal = decals.length > MAX_BASE_DECALS
39
+ ? `${decals.length} base decals; a frame lays the first ${MAX_BASE_DECALS}`
40
+ : null;
41
+ let kept = 0;
42
+ for (let i = 0; i < decals.length && kept < MAX_BASE_DECALS; i++) {
43
+ const decal = decals[i];
44
+ if (!(Number.isInteger(decal.image) && decal.image >= 0 && decal.image < images)) {
45
+ refusal ??= `a base decal lays image ${decal.image} and there are ${images}`;
46
+ continue;
47
+ }
48
+ try {
49
+ worldToDecalMatrix(MATRIX, decal);
50
+ }
51
+ catch (cause) {
52
+ refusal ??= cause instanceof Error ? cause.message : String(cause);
53
+ continue;
54
+ }
55
+ const at = 4 + kept * 16;
56
+ for (let row = 0; row < 3; row++) {
57
+ out[at + row * 4] = MATRIX[row];
58
+ out[at + row * 4 + 1] = MATRIX[4 + row];
59
+ out[at + row * 4 + 2] = MATRIX[8 + row];
60
+ out[at + row * 4 + 3] = MATRIX[12 + row];
61
+ }
62
+ out[at + 12] = decal.image;
63
+ out[at + 13] = Math.min(1, Math.max(0, decal.opacity ?? 1));
64
+ out[at + 14] = decal.facingCos ?? 0.2;
65
+ kept += 1;
66
+ }
67
+ out[0] = kept;
68
+ out[1] = bandTop;
69
+ out[2] = BASE_DECAL_TILE;
70
+ return refusal;
71
+ }