@driftengine/core 4.1.3 → 4.1.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.
Files changed (53) hide show
  1. package/CHANGELOG.json +3796 -3739
  2. package/README.md +1 -1
  3. package/dist/index.js +12 -2
  4. package/dist/render/backend/webgl2/renderer.d.ts +15 -0
  5. package/dist/render/backend/webgl2/renderer.js +28 -7
  6. package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +46 -0
  7. package/dist/render/backend/webgpu/gpuDrivenPass.js +105 -0
  8. package/dist/render/backend/webgpu/pointShadowPass.d.ts +4 -0
  9. package/dist/render/backend/webgpu/pointShadowPass.js +10 -0
  10. package/dist/render/backend/webgpu/renderer.js +17 -4
  11. package/dist/render/depthConvention.d.ts +19 -0
  12. package/dist/render/depthConvention.js +21 -0
  13. package/dist/render/livePointShadowSet.js +8 -0
  14. package/dist/render/pointShadowBudget.js +59 -5
  15. package/dist/render/pointShadowImage.d.ts +40 -0
  16. package/dist/render/pointShadowImage.js +20 -0
  17. package/dist/render/pointShadowMap.d.ts +4 -0
  18. package/dist/render/pointShadowMap.js +10 -0
  19. package/dist/render/pointShadowSystem.js +9 -0
  20. package/dist/render/renderQuality.d.ts +27 -4
  21. package/dist/render/renderQuality.js +1 -1
  22. package/dist/render/shaders/flat/main.d.ts +1 -1
  23. package/dist/render/shaders/flat/main.js +104 -10
  24. package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
  25. package/dist/render/shaders/flat/pointShadow.js +84 -46
  26. package/dist/render/shaders/flat/preamble.js +70 -9
  27. package/dist/render/shaders/generated/flat.wgsl.d.ts +16 -16
  28. package/dist/render/shaders/generated/flat.wgsl.js +32 -32
  29. package/dist/render/shaders/sky.d.ts +10 -6
  30. package/dist/render/shaders/sky.js +35 -2
  31. package/dist/render/textLayout.d.ts +58 -0
  32. package/dist/render/textLayout.js +83 -0
  33. package/dist/render/textRenderer.js +7 -3
  34. package/package.json +6 -6
  35. package/src/index.ts +12 -2
  36. package/src/render/backend/webgl2/renderer.ts +28 -7
  37. package/src/render/backend/webgpu/gpuDrivenPass.ts +105 -0
  38. package/src/render/backend/webgpu/pointShadowPass.ts +13 -0
  39. package/src/render/backend/webgpu/renderer.ts +26 -4
  40. package/src/render/depthConvention.ts +22 -0
  41. package/src/render/livePointShadowSet.ts +7 -0
  42. package/src/render/pointShadowBudget.ts +58 -5
  43. package/src/render/pointShadowImage.ts +51 -0
  44. package/src/render/pointShadowMap.ts +13 -0
  45. package/src/render/pointShadowSystem.ts +8 -0
  46. package/src/render/renderQuality.ts +28 -5
  47. package/src/render/shaders/flat/main.ts +104 -10
  48. package/src/render/shaders/flat/pointShadow.ts +84 -46
  49. package/src/render/shaders/flat/preamble.ts +70 -9
  50. package/src/render/shaders/generated/flat.wgsl.ts +32 -32
  51. package/src/render/shaders/sky.ts +37 -2
  52. package/src/render/textLayout.ts +88 -0
  53. package/src/render/textRenderer.ts +22 -3
@@ -111,6 +111,18 @@ export declare class PointShadowImage {
111
111
  private pendingRange;
112
112
  private pendingNear;
113
113
  private pendingSourceRadius;
114
+ /**
115
+ * Where this image was rendered from, which is what a sampler has to shoot its ray from.
116
+ *
117
+ * **Not the light's position**, and the difference is the whole point: `matchesSource` lets a
118
+ * light drift up to the caller's rebake tolerance from here before the image is re-rendered,
119
+ * so for any light that wanders these two are apart on nearly every frame. A shader that
120
+ * builds its direction and its distance from the live light asks this picture a question the
121
+ * picture cannot answer. See `ResolvedPointShadows.origins`.
122
+ */
123
+ get originX(): number;
124
+ get originY(): number;
125
+ get originZ(): number;
114
126
  /** Whether this map holds an image worth sampling. False until a bake completes. */
115
127
  get hasBaked(): boolean;
116
128
  /** How present the image is, 0 to 1. The shader mixes the occlusion in by it. */
@@ -222,6 +234,10 @@ export interface PointShadowSource {
222
234
  readonly far: number;
223
235
  readonly near: number;
224
236
  readonly sourceRadius: number;
237
+ /** Where the image was rendered from. See `PointShadowImage.originX`. */
238
+ readonly originX: number;
239
+ readonly originY: number;
240
+ readonly originZ: number;
225
241
  /** Whether this map holds an image worth sampling. */
226
242
  readonly hasBaked: boolean;
227
243
  /** How present the image is, 0 to 1. */
@@ -253,6 +269,28 @@ export interface ResolvedPointShadows {
253
269
  readonly far: Float32Array;
254
270
  readonly near: Float32Array;
255
271
  readonly sourceRadius: Float32Array;
272
+ /**
273
+ * The projection each bound map was rendered under: origin in `xyz`, far plane in `w`, four
274
+ * floats a light. The origin is **not** the light's position.
275
+ *
276
+ * A light is allowed to drift `pointShadowRebakeDistance` from its image before the image is
277
+ * re-rendered — that tolerance is what stops a flame re-baking six faces of the static world
278
+ * every frame — so for anything that flickers these two are apart on nearly every frame. The
279
+ * shader builds both its sample direction and the distance it compares from this point, so the
280
+ * ray it shoots is the ray the picture was drawn along and the drift costs nothing.
281
+ *
282
+ * **It used to build them from the live light**, and the drift went straight into the
283
+ * comparison: `dist` moved one for one with it against a bias of a few centimetres, and the
284
+ * single-tap blocker search moved across the occluder's silhouette, swapping `occluderDistance`
285
+ * between the caster and nothing. Reported on a brazier at night as a hard square under the
286
+ * fire, flashing in time with the flame while the camera stood still.
287
+ *
288
+ * **The far plane rides in `w` rather than in an array of its own**, because a default-block
289
+ * `float[N]` spends a whole uniform vector per element: declared separately the origin cost
290
+ * sixteen rows on each of the two sets, which the budget ladder answers on a 256-vector
291
+ * device by halving `MAX_LIGHTS`. See `uPointShadowProjection` in the preamble.
292
+ */
293
+ readonly projections: Float32Array;
256
294
  /** How present each bound map is, so a re-baked one arrives rather than appears. */
257
295
  readonly presence: Float32Array;
258
296
  /** The live pair, by the same light index: a light owns at most one of the two. */
@@ -260,6 +298,8 @@ export interface ResolvedPointShadows {
260
298
  readonly liveFar: Float32Array;
261
299
  readonly liveNear: Float32Array;
262
300
  readonly liveSourceRadius: Float32Array;
301
+ /** The live pair's own, by the same rule and for the same reason. */
302
+ readonly liveProjections: Float32Array;
263
303
  readonly liveWeights: Float32Array;
264
304
  }
265
305
  export declare function createResolvedPointShadows(lights: number): ResolvedPointShadows;
@@ -160,6 +160,24 @@ export class PointShadowImage {
160
160
  pendingRange = 0;
161
161
  pendingNear = 0;
162
162
  pendingSourceRadius = 0;
163
+ /**
164
+ * Where this image was rendered from, which is what a sampler has to shoot its ray from.
165
+ *
166
+ * **Not the light's position**, and the difference is the whole point: `matchesSource` lets a
167
+ * light drift up to the caller's rebake tolerance from here before the image is re-rendered,
168
+ * so for any light that wanders these two are apart on nearly every frame. A shader that
169
+ * builds its direction and its distance from the live light asks this picture a question the
170
+ * picture cannot answer. See `ResolvedPointShadows.origins`.
171
+ */
172
+ get originX() {
173
+ return this.x;
174
+ }
175
+ get originY() {
176
+ return this.y;
177
+ }
178
+ get originZ() {
179
+ return this.z;
180
+ }
163
181
  /** Whether this map holds an image worth sampling. False until a bake completes. */
164
182
  get hasBaked() {
165
183
  return this.baked;
@@ -329,11 +347,13 @@ export function createResolvedPointShadows(lights) {
329
347
  far: new Float32Array(lights),
330
348
  near: new Float32Array(lights),
331
349
  sourceRadius: new Float32Array(lights),
350
+ projections: new Float32Array(lights * 4),
332
351
  presence: new Float32Array(lights),
333
352
  liveLayers: new Int32Array(lights).fill(-1),
334
353
  liveFar: new Float32Array(lights),
335
354
  liveNear: new Float32Array(lights),
336
355
  liveSourceRadius: new Float32Array(lights),
356
+ liveProjections: new Float32Array(lights * 4),
337
357
  liveWeights: new Float32Array(lights),
338
358
  };
339
359
  }
@@ -46,6 +46,10 @@ export declare class PointShadowMap {
46
46
  get far(): number;
47
47
  get near(): number;
48
48
  get sourceRadius(): number;
49
+ /** Where the image was rendered from, not where the light is now. See the image. */
50
+ get originX(): number;
51
+ get originY(): number;
52
+ get originZ(): number;
49
53
  /** Whether this map holds an image worth sampling. False until a bake completes. */
50
54
  get hasBaked(): boolean;
51
55
  /** How present the image is, 0 to 1. The shader mixes the occlusion in by it. */
@@ -54,6 +54,16 @@ export class PointShadowMap {
54
54
  get sourceRadius() {
55
55
  return this.image.sourceRadius;
56
56
  }
57
+ /** Where the image was rendered from, not where the light is now. See the image. */
58
+ get originX() {
59
+ return this.image.originX;
60
+ }
61
+ get originY() {
62
+ return this.image.originY;
63
+ }
64
+ get originZ() {
65
+ return this.image.originZ;
66
+ }
57
67
  /** Whether this map holds an image worth sampling. False until a bake completes. */
58
68
  get hasBaked() {
59
69
  return this.image.hasBaked;
@@ -272,6 +272,10 @@ export class PointShadowSystem {
272
272
  out.far.fill(1);
273
273
  out.near.fill(0.01);
274
274
  out.sourceRadius.fill(0);
275
+ /* Origin zero, far plane one: the same neutral the separate `far` array is filled with. */
276
+ out.projections.fill(0);
277
+ for (let k = 0; k < MAX_POINT_LIGHTS; k++)
278
+ out.projections[k * 4 + 3] = 1;
275
279
  out.presence.fill(0);
276
280
  for (let k = 0; k < MAX_POINT_LIGHTS; k++) {
277
281
  const map = this.mapForShaderSlot(k);
@@ -299,6 +303,11 @@ export class PointShadowSystem {
299
303
  out.far[light] = map.far;
300
304
  out.near[light] = map.near;
301
305
  out.sourceRadius[light] = map.sourceRadius;
306
+ /* Where the image was rendered from, not where the light is now. See `projections`. */
307
+ out.projections[light * 4] = map.originX;
308
+ out.projections[light * 4 + 1] = map.originY;
309
+ out.projections[light * 4 + 2] = map.originZ;
310
+ out.projections[light * 4 + 3] = map.far;
302
311
  out.presence[light] = map.presence;
303
312
  out.layers[light] = map.layer;
304
313
  }
@@ -154,10 +154,33 @@ export interface RenderQuality {
154
154
  * sampled lights flickering at once accounted for 174 of the frame's 296 draw
155
155
  * calls, and for the game being unplayable exactly where lamps cluster.
156
156
  *
157
- * The default clears a flame's own wander with room to spare, because drift is
158
- * measured from the baked origin and a wander can be caught at either extreme
159
- * of its travel. A light that really moves still re-bakes; it just does so once
160
- * per this much movement instead of once per frame.
157
+ * **It was 0.4 until 4.1.5, chosen to clear a flame's whole wander with room to spare** — so
158
+ * a flame never re-baked at all, and its shadow of the static world stood still while the live
159
+ * map under the same light, which is re-rendered every frame because it holds the movers, swung
160
+ * with the flame. One light, two shadows, visibly disagreeing about where it was. Reported as
161
+ * the runner's shadow following the fire and the brazier's not.
162
+ *
163
+ * **A millimetre instead, which is as near to "whenever it moved" as makes sense**, so a shadow
164
+ * travels with its light rather than freezing between re-bakes. A centimetre was tried first and
165
+ * left a visible vibration: a light wandering on a curve moves less than that in a frame near
166
+ * the turning points of its travel, so the shadow ran smoothly through the fast part and stepped
167
+ * through the slow part. What the tolerance is still for is not re-baking on arithmetic noise.
168
+ *
169
+ * A light that does not move never goes stale at any positive value, so the whole cost falls on
170
+ * the lights that need it, and it is bounded rather than proportional: one such light takes a
171
+ * whole cube in a frame and the rest dribble under the ordinary face budget.
172
+ *
173
+ * **What makes that affordable is not this number.** The 174-of-296 draw calls this paragraph
174
+ * used to cite was measured before `pointShadowFacesPerFrame` capped the whole static bake, and
175
+ * before `planPointShadowBakes` learned to put a chronically stale map behind every cold and
176
+ * settling one. A flame now spends leftover budget and can never hold another light's first
177
+ * image hostage. One such light also finishes its cube inside a single frame rather than
178
+ * dribbling it over three, which is what makes the motion smooth instead of stepped; see
179
+ * `pointShadowBudget.ts`.
180
+ *
181
+ * **What would make it wrong** is a caster set heavy enough that six passes over it does not
182
+ * fit the frame, where a stepped shadow is the better trade and this should go back up.
183
+ * `gpuTiming` is how to tell which, and it is a number rather than an opinion.
161
184
  */
162
185
  readonly pointShadowRebakeDistance: number;
163
186
  /**
@@ -58,7 +58,7 @@ export const DEFAULT_RENDER_QUALITY = Object.freeze({
58
58
  directionalShadowDepthLayers: 2,
59
59
  directionalShadowMaxDistance: 6,
60
60
  directionalShadowMaxSlope: 3,
61
- pointShadowRebakeDistance: 0.4,
61
+ pointShadowRebakeDistance: 0.001,
62
62
  pointShadowFacesPerFrame: 2,
63
63
  /*
64
64
  * Twelve, which is `LIVE_POINT_SHADOW_MAPS` cubemaps of six faces: exactly what the live