@driftengine/core 4.8.7 → 4.9.0

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 (163) hide show
  1. package/CHANGELOG.json +61 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +5 -0
  4. package/dist/index.js +7 -0
  5. package/dist/render/atmosphere.d.ts +13 -0
  6. package/dist/render/atmosphere.js +9 -1
  7. package/dist/render/backend/api.d.ts +9 -1
  8. package/dist/render/backend/createRenderer.d.ts +6 -0
  9. package/dist/render/backend/createRenderer.js +4 -1
  10. package/dist/render/backend/webgl2/renderer.d.ts +90 -2
  11. package/dist/render/backend/webgl2/renderer.js +297 -16
  12. package/dist/render/backend/webgpu/device.d.ts +7 -1
  13. package/dist/render/backend/webgpu/device.js +31 -3
  14. package/dist/render/backend/webgpu/flatPass.d.ts +16 -1
  15. package/dist/render/backend/webgpu/flatPass.js +58 -26
  16. package/dist/render/backend/webgpu/postPass.d.ts +51 -9
  17. package/dist/render/backend/webgpu/postPass.js +16 -2
  18. package/dist/render/backend/webgpu/reflectionSurfacePass.d.ts +30 -0
  19. package/dist/render/backend/webgpu/reflectionSurfacePass.js +100 -0
  20. package/dist/render/backend/webgpu/renderer.d.ts +131 -2
  21. package/dist/render/backend/webgpu/renderer.js +586 -70
  22. package/dist/render/displayRange.d.ts +31 -0
  23. package/dist/render/displayRange.js +47 -0
  24. package/dist/render/filmicCurve.d.ts +74 -0
  25. package/dist/render/filmicCurve.js +203 -0
  26. package/dist/render/fog.d.ts +4 -1
  27. package/dist/render/fog.js +12 -2
  28. package/dist/render/frame/resources.d.ts +2 -2
  29. package/dist/render/frame/resources.js +6 -0
  30. package/dist/render/frameReflections.d.ts +40 -0
  31. package/dist/render/frameReflections.js +48 -0
  32. package/dist/render/pass.d.ts +49 -0
  33. package/dist/render/reflectionSurfaceTargets.d.ts +28 -0
  34. package/dist/render/reflectionSurfaceTargets.js +99 -0
  35. package/dist/render/renderQuality.d.ts +39 -8
  36. package/dist/render/renderQuality.js +4 -0
  37. package/dist/render/sceneTarget.d.ts +5 -0
  38. package/dist/render/sceneTarget.js +24 -1
  39. package/dist/render/screenSpaceReflection.d.ts +5 -0
  40. package/dist/render/screenSpaceReflection.js +5 -0
  41. package/dist/render/shaders/arcane.d.ts +1 -1
  42. package/dist/render/shaders/bolt.d.ts +1 -1
  43. package/dist/render/shaders/caustics.d.ts +1 -1
  44. package/dist/render/shaders/film.d.ts +1 -1
  45. package/dist/render/shaders/filmLook.js +2 -1
  46. package/dist/render/shaders/fire.d.ts +1 -1
  47. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  48. package/dist/render/shaders/flat/glassTint.js +2 -0
  49. package/dist/render/shaders/flat/index.d.ts +10 -0
  50. package/dist/render/shaders/flat/index.js +6 -0
  51. package/dist/render/shaders/flat/main.d.ts +1 -1
  52. package/dist/render/shaders/flat/main.js +28 -0
  53. package/dist/render/shaders/flat/overlay.d.ts +16 -0
  54. package/dist/render/shaders/flat/overlay.js +117 -0
  55. package/dist/render/shaders/flat/pointShadow.js +1 -1
  56. package/dist/render/shaders/flat/reflectionSurface.d.ts +21 -0
  57. package/dist/render/shaders/flat/reflectionSurface.js +61 -0
  58. package/dist/render/shaders/fog.d.ts +1 -1
  59. package/dist/render/shaders/fog.js +9 -2
  60. package/dist/render/shaders/fringe.d.ts +29 -0
  61. package/dist/render/shaders/fringe.js +55 -0
  62. package/dist/render/shaders/generated/arcane.wgsl.d.ts +1 -1
  63. package/dist/render/shaders/generated/arcane.wgsl.js +1 -1
  64. package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
  65. package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
  66. package/dist/render/shaders/generated/caustics.wgsl.d.ts +1 -1
  67. package/dist/render/shaders/generated/caustics.wgsl.js +1 -1
  68. package/dist/render/shaders/generated/film.wgsl.d.ts +1 -1
  69. package/dist/render/shaders/generated/film.wgsl.js +1 -1
  70. package/dist/render/shaders/generated/fire.wgsl.d.ts +1 -1
  71. package/dist/render/shaders/generated/fire.wgsl.js +1 -1
  72. package/dist/render/shaders/generated/flat.wgsl.d.ts +176 -16
  73. package/dist/render/shaders/generated/flat.wgsl.js +214 -54
  74. package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
  75. package/dist/render/shaders/generated/line.wgsl.js +1 -1
  76. package/dist/render/shaders/generated/particle.wgsl.d.ts +4 -4
  77. package/dist/render/shaders/generated/particle.wgsl.js +4 -4
  78. package/dist/render/shaders/generated/rush.wgsl.d.ts +56 -26
  79. package/dist/render/shaders/generated/rush.wgsl.js +56 -26
  80. package/dist/render/shaders/generated/scatter.wgsl.d.ts +1 -1
  81. package/dist/render/shaders/generated/scatter.wgsl.js +1 -1
  82. package/dist/render/shaders/generated/smoke.wgsl.d.ts +1 -1
  83. package/dist/render/shaders/generated/smoke.wgsl.js +1 -1
  84. package/dist/render/shaders/generated/ssrResolve.wgsl.d.ts +29 -0
  85. package/dist/render/shaders/generated/ssrResolve.wgsl.js +29 -0
  86. package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +22 -2
  87. package/dist/render/shaders/generated/ssrTrace.wgsl.js +22 -2
  88. package/dist/render/shaders/generated/water.wgsl.d.ts +1 -1
  89. package/dist/render/shaders/generated/water.wgsl.js +1 -1
  90. package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +2 -2
  91. package/dist/render/shaders/gpudriven/fog.wgsl.js +11 -3
  92. package/dist/render/shaders/line.d.ts +1 -1
  93. package/dist/render/shaders/particle.d.ts +3 -3
  94. package/dist/render/shaders/rush.js +54 -11
  95. package/dist/render/shaders/scatter.d.ts +1 -1
  96. package/dist/render/shaders/smoke.d.ts +1 -1
  97. package/dist/render/shaders/ssrResolve.d.ts +17 -0
  98. package/dist/render/shaders/ssrResolve.js +51 -0
  99. package/dist/render/shaders/ssrTrace.js +26 -0
  100. package/dist/render/shaders/water.d.ts +1 -1
  101. package/dist/render/ssrPass.d.ts +16 -0
  102. package/dist/render/ssrPass.js +106 -1
  103. package/dist/render/surfaceOverlay.d.ts +132 -0
  104. package/dist/render/surfaceOverlay.js +85 -0
  105. package/dist/render/surfaceTexture.d.ts +19 -0
  106. package/dist/render/surfaceTexture.js +69 -0
  107. package/dist/render/temporalAa.d.ts +14 -0
  108. package/dist/render/temporalAa.js +20 -0
  109. package/dist/render/vertexDefaults.d.ts +17 -0
  110. package/dist/render/vertexDefaults.js +24 -0
  111. package/package.json +6 -6
  112. package/src/index.ts +16 -0
  113. package/src/render/atmosphere.ts +22 -1
  114. package/src/render/backend/api.ts +9 -0
  115. package/src/render/backend/createRenderer.ts +18 -1
  116. package/src/render/backend/webgl2/renderer.ts +357 -13
  117. package/src/render/backend/webgpu/device.ts +42 -3
  118. package/src/render/backend/webgpu/flatPass.ts +63 -26
  119. package/src/render/backend/webgpu/postPass.ts +18 -0
  120. package/src/render/backend/webgpu/reflectionSurfacePass.ts +114 -0
  121. package/src/render/backend/webgpu/renderer.ts +735 -83
  122. package/src/render/displayRange.ts +61 -0
  123. package/src/render/filmicCurve.ts +229 -0
  124. package/src/render/fog.ts +19 -3
  125. package/src/render/frame/resources.ts +6 -0
  126. package/src/render/frameReflections.ts +72 -0
  127. package/src/render/pass.ts +53 -1
  128. package/src/render/reflectionSurfaceTargets.ts +105 -0
  129. package/src/render/renderQuality.ts +44 -8
  130. package/src/render/sceneTarget.ts +30 -2
  131. package/src/render/screenSpaceReflection.ts +5 -0
  132. package/src/render/shaders/filmLook.ts +2 -1
  133. package/src/render/shaders/flat/glassTint.ts +5 -1
  134. package/src/render/shaders/flat/index.ts +16 -0
  135. package/src/render/shaders/flat/main.ts +28 -0
  136. package/src/render/shaders/flat/overlay.ts +117 -0
  137. package/src/render/shaders/flat/pointShadow.ts +1 -1
  138. package/src/render/shaders/flat/reflectionSurface.ts +61 -0
  139. package/src/render/shaders/fog.ts +9 -2
  140. package/src/render/shaders/fringe.ts +59 -0
  141. package/src/render/shaders/generated/arcane.wgsl.ts +1 -1
  142. package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
  143. package/src/render/shaders/generated/caustics.wgsl.ts +1 -1
  144. package/src/render/shaders/generated/film.wgsl.ts +1 -1
  145. package/src/render/shaders/generated/fire.wgsl.ts +1 -1
  146. package/src/render/shaders/generated/flat.wgsl.ts +214 -54
  147. package/src/render/shaders/generated/line.wgsl.ts +1 -1
  148. package/src/render/shaders/generated/particle.wgsl.ts +4 -4
  149. package/src/render/shaders/generated/rush.wgsl.ts +56 -26
  150. package/src/render/shaders/generated/scatter.wgsl.ts +1 -1
  151. package/src/render/shaders/generated/smoke.wgsl.ts +1 -1
  152. package/src/render/shaders/generated/ssrResolve.wgsl.ts +30 -0
  153. package/src/render/shaders/generated/ssrTrace.wgsl.ts +22 -2
  154. package/src/render/shaders/generated/water.wgsl.ts +1 -1
  155. package/src/render/shaders/gpudriven/fog.wgsl.ts +11 -3
  156. package/src/render/shaders/rush.ts +54 -11
  157. package/src/render/shaders/ssrResolve.ts +52 -0
  158. package/src/render/shaders/ssrTrace.ts +26 -0
  159. package/src/render/ssrPass.ts +138 -1
  160. package/src/render/surfaceOverlay.ts +195 -0
  161. package/src/render/surfaceTexture.ts +67 -0
  162. package/src/render/temporalAa.ts +20 -0
  163. package/src/render/vertexDefaults.ts +28 -0
package/CHANGELOG.json CHANGED
@@ -1,4 +1,65 @@
1
1
  [
2
+ {
3
+ "version": "4.9.0",
4
+ "date": "2026-10-07",
5
+ "entries": [
6
+ {
7
+ "kind": "added",
8
+ "short": "`screenSpaceReflections`: every opaque lit surface reflects the frame by its own material, the probe's share swapped for what a ray finds, softened by roughness and left to the probes past a ceiling.",
9
+ "text": "A renderer built with `screenSpaceReflections` reflects the frame in every opaque lit surface by that surface's own material, where `ReflectiveSurface` reflects it by a box's. The lit stage leaves, per pixel, how much of the environment it showed and the tint a reflection lands with; the screen-space trace gains a material mode that marches every such pixel against the frame's depth and swaps the probe's share for what it finds. The swap is exact because the lit stage's blend is a mix: the frame holds the environment times the tint times the weight, and a found reflection belongs there with the same tint and weight, so nothing else in the pixel moves. A rougher surface shows a softer reflection, blurred by its roughness, and past `maxRoughness` it keeps the probes' reflection alone. One floor mesh with one ORM map therefore mirrors the figures standing on its glossy panels and barely shows them on its matte ones. What a surface reflects is what the lit stage already reflected: a metal by its metalness, a dielectric where `setSurfaceReflectivity` or its material asks. `true` takes `DEFAULT_FRAME_REFLECTIONS`; a `FrameReflections` sets the ceiling, the march and the blur. WebGPU writes the two maps by replaying the opaque lit draws without their lamps, since a second attachment on the frame's pass would bind every pipeline in it; WebGL2 writes them beside the colour, enabled around the opaque lit draws alone and behind a lit switch, so a renderer without the option carries nothing for it. It needs `screenEffects`, `hdrScene` and one sample, and says so once where it has none. On `demo/dev/frameReflections.html` the trace moves 30,138 floor pixels and none above them, a ceiling below every stripe matches the frame without the trace to the pixel, and the two backends differ by 41 pixels."
10
+ },
11
+ {
12
+ "kind": "added",
13
+ "short": "`setSurfaceOverlay` lays a rim of light, a dissolve with a glowing edge and wrinkle normals by six region weights over a draw's materials, its images regions of one atlas.",
14
+ "text": "`renderer.setSurfaceOverlay(overlay)` lays something over the surfaces of the draws that follow, beyond their materials, so one character's whole set of materials changes together while another's does not; per draw, reset by `bindMeshPass`. A `rim` glows along the silhouette: an edge `(1 - N·V)^falloff` leaning toward normals that face up, raised to a contrast, times a noise laid in screen space and scrolled on the environment's `surfaceTime`, a slow pulse and a mask in the mesh's uv, added to emission and not gated on the night. A `dissolve` cuts the surface away where a noise in its uv falls under a threshold, with a glowing band at the edge and a colour laid through the same noise. A `wrinkle` blends a second normal map into the surface's by six region weights read through two masks. Every image is a region of one atlas (`OverlayRegion`), read in the slot the frame's refraction copy takes, which an opaque surface never reads; a draw that refracts keeps its refraction and reads none of the overlay's images, and says so once. A lit switch, off until an overlay is set. WebGL2 carries the overlay's fifteen uniform vectors where its device has room and refuses it in words where it does not, as on a 256-vector part at the eight-light budget, and reads a compressed atlas as none. On `demo/dev/surfaceOverlay.html` every control moves only the object it names, and the two backends agree to the pixel."
15
+ },
16
+ {
17
+ "kind": "added",
18
+ "short": "`outputTransform: 'filmic'` and `setFilmicCurve`: a film curve shaped by five numbers that holds mid grey at mid grey, applied by the composite.",
19
+ "text": "`outputTransform: 'filmic'` is a film curve in the ACEScg working space: a straight segment through mid grey meeting a soft toe below and a shoulder above, shaped by a slope, a toe, a shoulder and two clips, with the ACES reference's glow and red modifier around it. `setFilmicCurve` sets the numbers and a renderer starts with `DEFAULT_FILMIC_CURVE`; a curve that runs backwards or divides by zero is clamped and said once rather than refused. 0.18 of scene light comes out at 0.18, where `'aces'` gives about 0.11: 118 of 255 on both backends, and every one of sixteen grey and colour patches agrees with a consumer's own transcription of the reference. It is the composite's, so it needs `screenEffects` and `hdrScene`; without them every pass grades with `'aces'` and the renderer says so once. Bound to DriftScript as `drift/render.filmic`."
20
+ },
21
+ {
22
+ "kind": "added",
23
+ "short": "`highDynamicRange` draws to a display's own range on WebGPU, `displayRange` says which range the frame went out in, and `setDisplayLuminance` lets highlights reach the display's peak.",
24
+ "text": "`highDynamicRange: true` asks for a canvas whose highlights run past paper white as far as the display can show. It is had where the renderer is on WebGPU, the frame keeps its light past white (`screenEffects` and `hdrScene`) and the display reports a high range: the canvas is configured `rgba16float` with extended tone mapping and read back to confirm it. `renderer.displayRange` says which range the frame goes out in, `'high'` or `'standard'`, and `displayRangeReason` says why; WebGL2 draws into an eight-bit canvas and always answers `'standard'`. `setDisplayLuminance(paperWhite, peak)` gives the ratio highlights may reach: paper white stays where it was and so does everything below it, the filmic shoulder rolls off toward the peak, and a colour grade colours a highlight without clipping it back to white. On a standard display it changes nothing. A host without a browser display query answers it with `createRenderer`'s `highDynamicRangeDisplay`."
25
+ },
26
+ {
27
+ "kind": "added",
28
+ "short": "`setCameraMotionBlur(scale, maxShare)` blurs each drawn object by its own motion under DriftTR on WebGPU: a mover smears while the still stage behind it stays sharp.",
29
+ "text": "Camera motion blur now reads each pixel's motion from the reconstruction's motion target, so a mover with a previous placement or pose smears along its own path while the stage standing still behind it does not, and `setCameraMotionBlur` takes a maximum as a share of the frame. On `demo/dev/objectBlur.html` a box crossing a still camera's view smears 8 pixels, 1 with the blur off, 5 capped. It is WebGPU's under DriftTR alone, since that is where per-object motion is kept; WebGL2, and WebGPU without reconstruction, blur by the camera's motion alone and say so once when given a mover."
30
+ },
31
+ {
32
+ "kind": "added",
33
+ "short": "`setChromaticAberration(intensity, start)` pulls the colour channels apart toward the frame's edges past a start radius, at three wavelengths, on both backends.",
34
+ "text": "`setChromaticAberration(intensity, start)` is a lens fringe in the composite: the three channels sampled apart toward the frame's edges, scaled by their wavelengths, from a start radius outward, so the middle of the frame is untouched. Held until changed; 0 is none. On `demo/dev/toneCurve.html?fringe=` it moves 13,480 pixels, none inside the start, and the two backends agree. It needs `screenEffects`, and says so once."
35
+ },
36
+ {
37
+ "kind": "added",
38
+ "short": "`Atmosphere.fogStart` keeps the exponential fog's first metres clear, integrated along each ray's own path in the lit stage and the GPU-driven pipeline alike.",
39
+ "text": "`Atmosphere.fogStart` holds the exponential height fog off for its first metres: the medium begins where the ray has travelled that far, integrated along the ray's own path, so a stage reads clear up close and hazes with distance as a fog start does in a scene authored elsewhere. 0, or absent, is the fog as before. On `demo/dev/fogStart.html` the measured columns match values worked by hand on both backends, and the GPU-driven pipeline's fog takes it too."
40
+ },
41
+ {
42
+ "kind": "added",
43
+ "short": "A registered pass reads the frame: `reads: ['colorSnapshot']` hands it the colour drawn so far as `sceneColor`, beside the depth's `sceneDepth`, on both backends.",
44
+ "text": "A pass that declares `reads: ['depthSnapshot']` is handed the frame's depth as `sceneDepth` — in the `PrepareContext` on WebGPU, an `r32float` texture, and in the `PassContext` on WebGL2 — and one that declares `reads: ['colorSnapshot']` the colour with everything drawn before it as `sceneColor`, copied afresh at each such pass's draw. A distortion samples the scene behind it with it; a full-screen pass drawn last reads the whole frame and writes over it in scene light, before the tone curve. Under DriftTR the pass runs after the upscale over a copy of the reconstructed frame. On `demo/dev/framePass.html` a pass inverting the frame reads the same values on both backends, with multisampling too."
45
+ },
46
+ {
47
+ "kind": "changed",
48
+ "short": "The worker factories are at declared subpaths, `@driftengine/assets/bcWorkers` and `@driftengine/physics/workers`, where a bundler and Node can both find them.",
49
+ "text": "`spawnBcWorker` and the physics island pool's `createIslandPool` were reached by a path into a package's `src`, which a published package's `exports` does not have to allow and a strict resolver refuses. Each now has a subpath of its own in its package's `exports`, `@driftengine/assets/bcWorkers` and `@driftengine/physics/workers`, built to `dist` like the barrels, and the messages that name them say so. The barrels still do not reach them, which is what keeps a worker out of a game that never names one."
50
+ },
51
+ {
52
+ "kind": "fixed",
53
+ "short": "A scene capture or a probe bake inside a frame no longer turns that frame black on WebGPU, nor switches off its skin spread, nor steps the temporal jitter twice.",
54
+ "text": "Three defects at the boundary a capture or a probe bake opens in the middle of a frame. On WebGPU, `captureScene` and `bakeProbe` saved the open pass before flushing the frame graph, which opens the frame's pass, so a capture after any draw restored the wrong pass and the frame came out black. On both backends, the latch that spreads skin once a frame was set before the check that refuses a mirror, a probe or a capture, so a blended draw inside a capture switched the frame's skin spread off. And the temporal history stepped its jitter each time the camera was bound rather than once a frame, so a frame that bound it twice drew two frames' jitter. Each has a test, and `scripts/capture-check.mjs` captures at the start of a frame, among its draws and at its end, against controls taken the same way."
55
+ },
56
+ {
57
+ "kind": "fixed",
58
+ "short": "WebGPU asks a device for the textures and samplers the environment probe's widest lit variant really binds, sixteen and twelve, counted from the generated bindings.",
59
+ "text": "The check that decides whether a device can build the environment probe's permutation compared its limits against seven sampled textures and four samplers, typed when the probe was added. The widest lit variant had since grown to sixteen and twelve with the material maps, glass, the fixture atlas and DriftLight, so a device under those would have been admitted to a permutation it cannot bind. The counts now come from the generated bindings, a shared sampler counted once."
60
+ }
61
+ ]
62
+ },
2
63
  {
3
64
  "version": "4.8.7",
4
65
  "date": "2026-10-06",
package/README.md CHANGED
@@ -6,7 +6,7 @@ carries the loop, the renderer, cameras, input, the scene graph, geometry, text
6
6
  and the whole of [`@driftengine/physics`](https://driftengine.dev/docs/simulation/rigid-bodies),
7
7
  which it re-exports. The rest of the engine is separate packages you add when you need them.
8
8
 
9
- **Cost: 828.1 KB gzipped, importing `createRenderer`.** Measured by `scripts/size-gate.test.mjs`,
9
+ **Cost: 858.8 KB gzipped, importing `createRenderer`.** Measured by `scripts/size-gate.test.mjs`,
10
10
  which fails the build if the figure drifts by more than 3%.
11
11
 
12
12
  The manual is at **[driftengine.dev/docs](https://driftengine.dev/docs)**, with an example running
package/dist/index.d.ts CHANGED
@@ -145,6 +145,8 @@ export { placeCascade } from './render/gi/globalField.ts';
145
145
  export type { ReflectionOptions, ReflectiveSample } from './render/gi/reflection.ts';
146
146
  export { GRADE_PLACEHOLDER_SIZE, MAX_GRADE_SIZE, identityGradeLut } from './render/colourGrade.ts';
147
147
  export type { ColourGradeLut } from './render/colourGrade.ts';
148
+ export { DEFAULT_FILMIC_CURVE } from './render/filmicCurve.ts';
149
+ export type { FilmicCurve } from './render/filmicCurve.ts';
148
150
  /**
149
151
  * How bloom answers past its threshold — a ramp in place of the subtraction, and a tint for each
150
152
  * of the pyramid's `BLOOM_LEVELS` octaves — the third argument of `setBloom`.
@@ -157,6 +159,9 @@ export type { BoltHandle, CausticsHandle, ClothBindingHandle, ClothParticlesHand
157
159
  export type { ClothBindingData } from './render/clothBindingData.ts';
158
160
  export { anisotropicModel, eyeModel, hairModel, lightmapModel, skinModel, } from './render/surfaceModel.ts';
159
161
  export type { AnisotropicModel, EyeModel, HairModel, LightmapModel, SkinModel, SurfaceModel, SurfaceModelKind, } from './render/surfaceModel.ts';
162
+ export { DEFAULT_FRAME_REFLECTIONS } from './render/frameReflections.ts';
163
+ export type { FrameReflections } from './render/frameReflections.ts';
164
+ export type { OverlayRegion, SurfaceDissolve, SurfaceOverlay, SurfaceRim, SurfaceWrinkle, } from './render/surfaceOverlay.ts';
160
165
  export { WHOLE_PAGE } from './render/lightmap.ts';
161
166
  export type { LightmapPage, LightmapRegion } from './render/lightmap.ts';
162
167
  export { createSkinnedCloth } from './render/skinnedClothRun.ts';
package/dist/index.js CHANGED
@@ -209,6 +209,7 @@ export { placeCascade } from './render/gi/globalField.js';
209
209
  * take the identity, bend it, hand it back — and because it is what the placeholder is.
210
210
  */
211
211
  export { GRADE_PLACEHOLDER_SIZE, MAX_GRADE_SIZE, identityGradeLut } from './render/colourGrade.js';
212
+ export { DEFAULT_FILMIC_CURVE } from './render/filmicCurve.js';
212
213
  /**
213
214
  * How bloom answers past its threshold — a ramp in place of the subtraction, and a tint for each
214
215
  * of the pyramid's `BLOOM_LEVELS` octaves — the third argument of `setBloom`.
@@ -221,6 +222,12 @@ export { probeDevice } from './render/backend/probe.js';
221
222
  * its own, compiled the first time a draw asks.
222
223
  */
223
224
  export { anisotropicModel, eyeModel, hairModel, lightmapModel, skinModel, } from './render/surfaceModel.js';
225
+ /*
226
+ * What a draw lays over its surface beyond its material — a rim of light, a dissolve, wrinkle
227
+ * normals by region — set per draw with `setSurfaceOverlay`, its images regions of one atlas.
228
+ */
229
+ /* Every opaque lit surface reflecting the frame by its own material: `screenSpaceReflections`. */
230
+ export { DEFAULT_FRAME_REFLECTIONS } from './render/frameReflections.js';
224
231
  /*
225
232
  * A baked lightmap: the page a consumer decoded, which `createLightmap` uploads as a lightmapped
226
233
  * material's `modelMap`, and the region of it a material or an instance reads.
@@ -45,6 +45,19 @@ export interface Atmosphere {
45
45
  /** Linear mode only: where haze begins, and where it is total. */
46
46
  fogNear?: number;
47
47
  fogFar?: number;
48
+ /**
49
+ * The medium only: how far along the view ray it begins, in metres. Absent or 0 is at the
50
+ * camera, as every world before it was drawn.
51
+ *
52
+ * The air nearer than this is perfectly clear, and what lies beyond is fogged by the air past
53
+ * this point alone — so a faint, bright haze can lie over a distant crowd while the figures a
54
+ * few metres from the camera stay sharp, which no density can do when the medium starts at the
55
+ * eye: there it veils the whole frame, lifting the shadows and draining the colour before it
56
+ * reaches anything far enough to want it. Unlike `linear`'s `fogNear`, nothing past it is a
57
+ * ramp: it is the same extinction, integrated from further out. **What it gives up** is the air
58
+ * between the camera and the start, which a real medium has; that is the point of it.
59
+ */
60
+ fogStart?: number;
48
61
  underwater: UnderwaterAtmosphere | null;
49
62
  }
50
63
  /**
@@ -46,7 +46,15 @@ export function resolveAtmosphere(atmosphere, cameraY, underwaterEnabled, out) {
46
46
  out.fogHeightFalloff = atmosphere.fogHeightFalloff;
47
47
  out.fogEyeY = cameraY;
48
48
  out.fogMode = atmosphere.fogMode === 'linear' ? 1 : 0;
49
- out.fogNear = atmosphere.fogNear ?? 0;
49
+ /*
50
+ * **One slot, two meanings, and never both**: the ramp's near distance in linear mode, and in
51
+ * the medium where it begins (`fogStart`), which the medium never read before — so a world that
52
+ * left a `fogNear` on an exponential fog starts nothing, and draws exactly as it did.
53
+ */
54
+ out.fogNear =
55
+ atmosphere.fogMode === 'linear'
56
+ ? (atmosphere.fogNear ?? 0)
57
+ : Math.max(0, Number.isFinite(atmosphere.fogStart) ? (atmosphere.fogStart ?? 0) : 0);
50
58
  out.fogFar = atmosphere.fogFar ?? 1;
51
59
  /*
52
60
  * The air medium stands in when nothing is submerged. The shaders multiply by the factor
@@ -7,6 +7,7 @@ import type { ProbeBakeOptions } from '../reflectionProbe.ts';
7
7
  import type { Renderer, TranslucentMeshOptions } from './webgl2/renderer.ts';
8
8
  import type { InstancedOptions, MeshInstances } from '../instances.ts';
9
9
  import type { SurfaceMaterial } from '../surfaceTexture.ts';
10
+ import type { SurfaceOverlay } from '../surfaceOverlay.ts';
10
11
  import type { SurfaceSource } from '../compressedSource.ts';
11
12
  import type { LightmapPage } from '../lightmap.ts';
12
13
  import type { DeviceParticles } from '../particlePool.ts';
@@ -189,7 +190,7 @@ export interface MeshOptions {
189
190
  }
190
191
  export type RendererApi = Omit<{
191
192
  [K in keyof Renderer]: Renderer[K];
192
- }, 'gpuTimer' | 'createMesh' | 'createMeshIncremental' | 'updateMesh' | 'drawMesh' | 'disposeMesh' | 'drawTranslucentMesh' | 'drawLightVolume' | 'drawFilm' | 'createScatter' | 'drawScatter' | 'uploadScatter' | 'disposeScatter' | 'createInstanced' | 'uploadInstanced' | 'drawInstanced' | 'drawTranslucentInstanced' | 'disposeInstanced' | 'createWater' | 'drawWater' | 'disposeWater' | 'createPlumes' | 'drawPlumes' | 'disposePlumes' | 'setPlumeScale' | 'createCaustics' | 'drawCaustics' | 'disposeCaustics' | 'createParticles' | 'drawParticles' | 'drawDeviceParticles' | 'disposeParticles' | 'createWindStreaks' | 'drawWindStreaks' | 'disposeWindStreaks' | 'createFlock' | 'drawFlock' | 'disposeFlock' | 'createBolts' | 'drawBolts' | 'disposeBolts' | 'createLines' | 'drawLines' | 'disposeLines' | 'createText' | 'setText' | 'setPlate' | 'drawText' | 'textWidth' | 'disposeText' | 'createSdfText' | 'setSdfText' | 'drawSdfText' | 'disposeSdfText' | 'createSurfaceTexture' | 'createSurfaceTextureArray' | 'createLightmap' | 'createSceneCapture' | 'captureScene' | 'updateSurfaceTexture' | 'disposeSurfaceTexture' | 'setSurfaceTexture' | 'setMaterial' | 'createClothBinding' | 'createClothParticles' | 'updateClothParticles' | 'setCloth' | 'disposeClothBinding' | 'disposeClothParticles'> & {
193
+ }, 'gpuTimer' | 'createMesh' | 'createMeshIncremental' | 'updateMesh' | 'drawMesh' | 'disposeMesh' | 'drawTranslucentMesh' | 'drawLightVolume' | 'drawFilm' | 'createScatter' | 'drawScatter' | 'uploadScatter' | 'disposeScatter' | 'createInstanced' | 'uploadInstanced' | 'drawInstanced' | 'drawTranslucentInstanced' | 'disposeInstanced' | 'createWater' | 'drawWater' | 'disposeWater' | 'createPlumes' | 'drawPlumes' | 'disposePlumes' | 'setPlumeScale' | 'createCaustics' | 'drawCaustics' | 'disposeCaustics' | 'createParticles' | 'drawParticles' | 'drawDeviceParticles' | 'disposeParticles' | 'createWindStreaks' | 'drawWindStreaks' | 'disposeWindStreaks' | 'createFlock' | 'drawFlock' | 'disposeFlock' | 'createBolts' | 'drawBolts' | 'disposeBolts' | 'createLines' | 'drawLines' | 'disposeLines' | 'createText' | 'setText' | 'setPlate' | 'drawText' | 'textWidth' | 'disposeText' | 'createSdfText' | 'setSdfText' | 'drawSdfText' | 'disposeSdfText' | 'createSurfaceTexture' | 'createSurfaceTextureArray' | 'createLightmap' | 'createSceneCapture' | 'captureScene' | 'updateSurfaceTexture' | 'disposeSurfaceTexture' | 'setSurfaceTexture' | 'setMaterial' | 'setSurfaceOverlay' | 'createClothBinding' | 'createClothParticles' | 'updateClothParticles' | 'setCloth' | 'disposeClothBinding' | 'disposeClothParticles'> & {
193
194
  /**
194
195
  * A mesh's cloth binding: which simulation triangle each of its vertices follows, where on it and
195
196
  * by how much (`ClothBindingData`). The mesh must be skinned — a binding blends each vertex
@@ -213,6 +214,13 @@ export type RendererApi = Omit<{
213
214
  setCloth(binding: ClothBindingHandle | null, particles?: ClothParticlesHandle | null): void;
214
215
  disposeClothBinding(binding: ClothBindingHandle): void;
215
216
  disposeClothParticles(particles: ClothParticlesHandle): void;
217
+ /**
218
+ * What the following draws lay over their surfaces beyond their materials — a rim of light, a
219
+ * dissolve with a glowing edge, wrinkle normals by region — or null for none, which every pass
220
+ * starts with. Per draw, so one character's whole set of materials changes together. Its images
221
+ * are regions of one atlas (`SurfaceOverlay.maps`). See `surfaceOverlay.ts`.
222
+ */
223
+ setSurfaceOverlay(overlay: SurfaceOverlay | null): void;
216
224
  /**
217
225
  * Upload a caller's image, or a BC image's blocks with their stored chain where
218
226
  * `compressedFormats` has the format. See `SurfaceTextureHandle` for the boundary this guards.
@@ -132,6 +132,12 @@ export interface CreateRendererOptions {
132
132
  * badge is looked at on a deployed build.
133
133
  */
134
134
  readonly splash?: boolean | SplashOptions;
135
+ /**
136
+ * Whether the display shows a high dynamic range, asked only where `highDynamicRange` was. The
137
+ * browser's `(dynamic-range: high)` query by default; a host with no DOM, or one that knows
138
+ * better about its own display, answers instead. See `displayRange.ts`.
139
+ */
140
+ readonly highDynamicRangeDisplay?: () => boolean;
135
141
  }
136
142
  /** What was built, and which path built it. */
137
143
  export interface CreatedRenderer {
@@ -4,6 +4,7 @@ import { BACKEND_TIMEOUT_MS, forcedBackend, selectBackend } from './select.js';
4
4
  import { TIMED_OUT, withDeadline } from './deadline.js';
5
5
  import { mountSplash } from '../../ui/splash.js';
6
6
  import { gpuDrivenRefusal, gpuDrivenSupported } from '../gpudriven/pipeline.js';
7
+ import { browserHighDynamicRange, chooseDisplayRange } from '../displayRange.js';
7
8
  /**
8
9
  * What a caller who says nothing gets.
9
10
  *
@@ -177,7 +178,9 @@ export async function createRenderer(canvas, quality = {}, options = {}) {
177
178
  const { createGpuSurface } = await import('./webgpu/device.js');
178
179
  const { WebGPURenderer } = await import('./webgpu/renderer.js');
179
180
  stage = 'creating the surface';
180
- const surface = createGpuSurface(canvas, device);
181
+ const profile = resolveRenderQuality(qualityFor('webgpu'));
182
+ const surface = createGpuSurface(canvas, device, chooseDisplayRange(profile.highDynamicRange, profile.screenEffects && profile.hdrScene, profile.highDynamicRange &&
183
+ (options.highDynamicRangeDisplay ?? browserHighDynamicRange)()));
181
184
  stage = 'constructing the renderer';
182
185
  return { surface, WebGPURenderer };
183
186
  })();
@@ -12,6 +12,7 @@ import type { ComputeDefinition, ComputeHandle } from '../../compute.ts';
12
12
  import type { PassDefinition, PassHandle } from '../../pass.ts';
13
13
  import type { Vec3 } from '../../../math/color.ts';
14
14
  import { GpuTimer } from '../../gpuTimer.ts';
15
+ import { type SurfaceOverlay } from '../../surfaceOverlay.ts';
15
16
  import type { ColourGradeLut } from '../../colourGrade.ts';
16
17
  import { type BloomResponse } from '../../bloomChain.ts';
17
18
  import { LightField } from '../../driftLight/lightField.ts';
@@ -72,6 +73,8 @@ import { TextRenderer } from '../../textRenderer.ts';
72
73
  import { SdfTextRenderer } from '../../sdfTextRenderer.ts';
73
74
  import type { SdfFont } from '../../sdfFont.ts';
74
75
  import type { SdfTextStyle } from '../../sdfTextLayout.ts';
76
+ import type { DisplayRange } from '../../displayRange.ts';
77
+ import { type FilmicCurve } from '../../filmicCurve.ts';
75
78
  import type { RenderBackend } from '../api.ts';
76
79
  import type { TextStyle } from '../../textLayout.ts';
77
80
  import { WindStreakRenderer } from '../../windStreakRenderer.ts';
@@ -974,6 +977,10 @@ export declare class WebGL2Renderer implements RendererApi {
974
977
  private readonly reflectionQueue;
975
978
  /** Built on the first frame that submits a surface, and never for a scene with none. */
976
979
  private ssr;
980
+ /** The frame's materials' reflections, resolved; null where off. See `frameReflections.ts`. */
981
+ private readonly frameReflections;
982
+ /** Their two maps on the scene's framebuffer, null where they cannot be drawn. */
983
+ private readonly reflectionTargets;
977
984
  /** Said once where a reflection cannot be traced at all. */
978
985
  private reflectionsRefused;
979
986
  /** Where the frame's camera stands, which is what gives a reconstructed normal its sign. */
@@ -1033,6 +1040,8 @@ export declare class WebGL2Renderer implements RendererApi {
1033
1040
  * exactly as it asked to be and nothing about it changes.
1034
1041
  */
1035
1042
  private exposure;
1043
+ /** The `filmic` curve's eight constants, held until `setFilmicCurve` is called again. */
1044
+ private readonly filmConstants;
1036
1045
  /**
1037
1046
  * This frame's veil colour, and how much of it to composite. See `setFrameVeil`.
1038
1047
  *
@@ -1234,6 +1243,15 @@ export declare class WebGL2Renderer implements RendererApi {
1234
1243
  private warnedNoPointShadowArray;
1235
1244
  /** Said once, for the retired ambient dial. */
1236
1245
  private warnedEnvironmentAmbient;
1246
+ /** The overlay the draws wear, `uOverlay`: see `setSurfaceOverlay`. */
1247
+ private readonly overlayFloats;
1248
+ /** Its atlas, and the 2D copy of it bound where the refraction copy goes. */
1249
+ private overlayMaps;
1250
+ private overlayTexture;
1251
+ /** Whether this device's lit stage has room for the overlay's vectors: asked once. */
1252
+ private overlayRoom;
1253
+ private warnedOverlayRoom;
1254
+ private warnedOverlayBlocks;
1237
1255
  /** Whether a probe is being baked, so the passes that would recurse into one can tell. */
1238
1256
  private probePassActive;
1239
1257
  /**
@@ -1558,6 +1576,8 @@ export declare class WebGL2Renderer implements RendererApi {
1558
1576
  * call. `init` runs now rather than at the first frame, because building a pipeline in the
1559
1577
  * frame loop is the allocation the house rules are about.
1560
1578
  */
1579
+ /** Each registered pass's declared reads, as a mask, resolved once at registration. */
1580
+ private readonly passReads;
1561
1581
  registerPass(definition: PassDefinition): PassHandle;
1562
1582
  /**
1563
1583
  * How many registered passes declare `prepare`, so a frame with none walks nothing.
@@ -2015,6 +2035,27 @@ export declare class WebGL2Renderer implements RendererApi {
2015
2035
  * exactly as it did.
2016
2036
  */
2017
2037
  setSurfaceReflectivity(amount: number): void;
2038
+ /**
2039
+ * The overlay the following draws wear — a rim of light, a dissolve, wrinkles by region — or null
2040
+ * for none. Pass state, as the material is: `bindMeshPass` puts it back to none. See
2041
+ * `surfaceOverlay.ts` for what it lays and `shaders/flat/overlay.ts` for how.
2042
+ *
2043
+ * **Refused, said once, where the lit stage has no room for it.** Fifteen vectors more than the
2044
+ * program this device fits: counted from the source as `fitFlatProgram` counts, and on a device
2045
+ * whose limit is the 256 an Adreno 740 reports, the eight-light build at 254 has none. The draws
2046
+ * then wear nothing, which is the picture a game without the feature draws. Its atlas is bound as
2047
+ * a 2D copy of its first layer, in the refraction copy's unit (`SurfaceTexture.flatLayer`); a
2048
+ * compressed atlas has no copy and is read as none, also said once.
2049
+ */
2050
+ setSurfaceOverlay(overlay: SurfaceOverlay | null): void;
2051
+ /** The overlay's atlas into the refraction copy's unit, as a 2D copy, or the empty texture. */
2052
+ private bindOverlayMaps;
2053
+ /** The most fragment uniform vectors this GPU offers a stage. */
2054
+ private fragmentVectorLimit;
2055
+ /** What the lit shader at this renderer's budget leaves of the limit, with no overlay. */
2056
+ private overlayRoomLeft;
2057
+ /** Whether the lit shader with the overlay compiled in fits this GPU: counted once. */
2058
+ private overlayFits;
2018
2059
  /**
2019
2060
  * How bright the environment the surfaces drawn next reflect is. 1 is the default and identity.
2020
2061
  *
@@ -3015,8 +3056,17 @@ export declare class WebGL2Renderer implements RendererApi {
3015
3056
  * Set before `beginFrame` and held until changed, like `setSpeedRush`, which it sits
3016
3057
  * beside for the same reason: both describe how the finished image is presented rather
3017
3058
  * than anything in the world.
3059
+ *
3060
+ * `maxShare` is the longest smear, as a share of the frame (0.03 unless given, at most half): a
3061
+ * fast turn softens the frame rather than erasing it. **Each drawn object's own motion is
3062
+ * followed only under a reconstruction on WebGPU**, which draws the motion target it reads; here,
3063
+ * and on WebGPU without one, the camera's motion alone smears the frame, and a draw that names
3064
+ * where it was last frame is told so once.
3018
3065
  */
3019
- setCameraMotionBlur(scale: number): void;
3066
+ setCameraMotionBlur(scale: number, maxShare?: number): void;
3067
+ /** The longest smear, as a share of the frame. See `setCameraMotionBlur`. */
3068
+ private motionBlurMax;
3069
+ private warnedObjectBlur;
3020
3070
  /**
3021
3071
  * The camera has cut: this frame is a new shot, not the next moment of the last one.
3022
3072
  *
@@ -3178,6 +3228,30 @@ export declare class WebGL2Renderer implements RendererApi {
3178
3228
  * black frame with a sign error in it.
3179
3229
  */
3180
3230
  setOutputExposure(exposure: number): void;
3231
+ /**
3232
+ * The `filmic` curve this and every later frame is graded through, until it is set again: a
3233
+ * grading volume's five numbers, solved once here into the constants the resolve reads. Ignored
3234
+ * unless `outputTransform` is `filmic`. A number out of range is clamped into it and said once,
3235
+ * since a frame loop must not throw. See `filmicCurve.ts`.
3236
+ */
3237
+ setFilmicCurve(curve: FilmicCurve): void;
3238
+ private warnedFilmClamped;
3239
+ /**
3240
+ * The range the frame goes out in: `'high'` where the profile asked for `highDynamicRange`, the
3241
+ * frame holds light past white and the display shows it — on WebGPU — and `'standard'` otherwise.
3242
+ * **Always standard here**: this backend draws into a canvas of eight bits a channel, which has
3243
+ * nothing above white to show. Typed as the question rather than this backend's answer, because
3244
+ * the shared surface is derived from this class.
3245
+ */
3246
+ readonly displayRange: DisplayRange;
3247
+ /** Why it is that range, in words, for a game choosing its look and for a bug report. */
3248
+ get displayRangeReason(): string;
3249
+ /**
3250
+ * How bright paper white and the display's peak are, in one unit, held until changed: their
3251
+ * ratio is how far above white the highlights may run where `displayRange` is `'high'`, and
3252
+ * nothing changes where it is standard, which is always here.
3253
+ */
3254
+ setDisplayLuminance(paperWhite: number, peak: number): void;
3181
3255
  /**
3182
3256
  * Composite a flat colour over the finished frame — for a cut dipping to white or to black.
3183
3257
  *
@@ -3242,6 +3316,14 @@ export declare class WebGL2Renderer implements RendererApi {
3242
3316
  * see `filmLook.ts`. Needs `screenEffects`, and says so once rather than doing nothing.
3243
3317
  */
3244
3318
  setVignette(strength: number): void;
3319
+ /**
3320
+ * The lens's colour fringe, held until changed: `intensity` is a percentage, 0 is none, and red
3321
+ * and green are pulled toward the centre by their wavelength's distance from blue, growing from
3322
+ * `start` — a share of the half-frame, 0 the centre — to the whole of it at the edge. Applied to
3323
+ * scene light at the composite's first read. Needs `screenEffects`, and says so once rather than
3324
+ * doing nothing. See `shaders/fringe.ts`.
3325
+ */
3326
+ setChromaticAberration(intensity: number, start?: number): void;
3245
3327
  /**
3246
3328
  * Film grain, held until changed: an amplitude in display values (0 is none, 0.03 is a fine
3247
3329
  * grain) and this frame's seed. **The seed is the caller's**, because the engine takes time from
@@ -3517,12 +3599,18 @@ export declare class WebGL2Renderer implements RendererApi {
3517
3599
  * here, so there is no motion target for this to be written into, and a mover passed here is
3518
3600
  * neither read nor advanced.
3519
3601
  */
3520
- _previousModel?: ReadonlyMat4 | Mover | null): void;
3602
+ previousModel?: ReadonlyMat4 | Mover | null): void;
3521
3603
  /**
3522
3604
  * Whether a skin draw now draws its two halves: the profile asked, the frame has not spread its
3523
3605
  * skin yet, this is the frame's own picture — not a mirror's or a probe's, nor an overlay after
3524
3606
  * the frame — and the blur's target can be had.
3525
3607
  */
3608
+ /**
3609
+ * Whether a draw now is the frame's own, opaque and lit, and so writes the two maps beside its
3610
+ * colour: not a mirror's, a probe's or a capture's, whose framebuffers have no maps, and not after
3611
+ * the frame. See `reflectionSurfaceTargets.ts`.
3612
+ */
3613
+ private reflectionKeeps;
3526
3614
  private skinSplits;
3527
3615
  /**
3528
3616
  * Spread the frame's skin and add it back: once, at the first blended draw or at the frame's end.