@driftengine/core 4.10.0 → 4.11.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 (165) hide show
  1. package/CHANGELOG.json +107 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +6 -5
  4. package/dist/index.js +2 -2
  5. package/dist/render/ambientOcclusionPass.d.ts +3 -2
  6. package/dist/render/ambientOcclusionPass.js +4 -2
  7. package/dist/render/backend/api.d.ts +21 -1
  8. package/dist/render/backend/webgl2/renderer.d.ts +141 -3
  9. package/dist/render/backend/webgl2/renderer.js +454 -42
  10. package/dist/render/backend/webgpu/buffers.d.ts +5 -0
  11. package/dist/render/backend/webgpu/buffers.js +98 -48
  12. package/dist/render/backend/webgpu/clothKernels.d.ts +8 -0
  13. package/dist/render/backend/webgpu/clothKernels.js +76 -0
  14. package/dist/render/backend/webgpu/clothOps.d.ts +39 -0
  15. package/dist/render/backend/webgpu/clothOps.js +78 -0
  16. package/dist/render/backend/webgpu/clothSetLayout.d.ts +25 -0
  17. package/dist/render/backend/webgpu/clothSetLayout.js +84 -0
  18. package/dist/render/backend/webgpu/clothSetPack.d.ts +33 -0
  19. package/dist/render/backend/webgpu/clothSetPack.js +144 -0
  20. package/dist/render/backend/webgpu/drawCommand.d.ts +2 -0
  21. package/dist/render/backend/webgpu/drawCommand.js +1 -0
  22. package/dist/render/backend/webgpu/flatPass.d.ts +1 -1
  23. package/dist/render/backend/webgpu/flatPass.js +2 -1
  24. package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
  25. package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
  26. package/dist/render/backend/webgpu/latePass.js +1 -0
  27. package/dist/render/backend/webgpu/motionPass.js +15 -6
  28. package/dist/render/backend/webgpu/pipelineCache.d.ts +22 -1
  29. package/dist/render/backend/webgpu/pipelineCache.js +39 -1
  30. package/dist/render/backend/webgpu/postPass.d.ts +1 -1
  31. package/dist/render/backend/webgpu/renderer.d.ts +100 -1
  32. package/dist/render/backend/webgpu/renderer.js +423 -95
  33. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +9 -2
  34. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +121 -47
  35. package/dist/render/backend/webgpu/staticBundles.js +2 -1
  36. package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
  37. package/dist/render/backend/webgpu/vertexFormats.js +97 -0
  38. package/dist/render/globalMediumPass.d.ts +2 -0
  39. package/dist/render/globalMediumPass.js +1 -0
  40. package/dist/render/indexWidth.d.ts +17 -0
  41. package/dist/render/indexWidth.js +23 -0
  42. package/dist/render/instances.d.ts +13 -1
  43. package/dist/render/instances.js +10 -0
  44. package/dist/render/lightMatrix.d.ts +11 -0
  45. package/dist/render/lightMatrix.js +14 -0
  46. package/dist/render/mesh.d.ts +9 -0
  47. package/dist/render/mesh.js +38 -14
  48. package/dist/render/occlusionFade.d.ts +21 -0
  49. package/dist/render/occlusionFade.js +27 -0
  50. package/dist/render/renderQuality.d.ts +30 -0
  51. package/dist/render/renderQuality.js +5 -0
  52. package/dist/render/rendererHarness.d.ts +7 -0
  53. package/dist/render/rendererHarness.js +12 -0
  54. package/dist/render/sceneTarget.d.ts +2 -0
  55. package/dist/render/sceneTarget.js +1 -1
  56. package/dist/render/shader.d.ts +15 -0
  57. package/dist/render/shader.js +41 -0
  58. package/dist/render/shaders/ambientOcclusion.js +12 -1
  59. package/dist/render/shaders/flat/directionalShadow.js +84 -10
  60. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  61. package/dist/render/shaders/flat/glassTint.js +3 -0
  62. package/dist/render/shaders/flat/index.d.ts +15 -0
  63. package/dist/render/shaders/flat/index.js +24 -4
  64. package/dist/render/shaders/flat/layered.d.ts +27 -0
  65. package/dist/render/shaders/flat/layered.js +198 -0
  66. package/dist/render/shaders/flat/main.d.ts +1 -1
  67. package/dist/render/shaders/flat/main.js +50 -16
  68. package/dist/render/shaders/flat/mapReads.d.ts +10 -0
  69. package/dist/render/shaders/flat/mapReads.js +38 -0
  70. package/dist/render/shaders/flat/movingSun.d.ts +21 -0
  71. package/dist/render/shaders/flat/movingSun.js +46 -0
  72. package/dist/render/shaders/flat/normalMap.d.ts +6 -2
  73. package/dist/render/shaders/flat/normalMap.js +23 -3
  74. package/dist/render/shaders/flat/overlay.d.ts +3 -2
  75. package/dist/render/shaders/flat/overlay.js +41 -19
  76. package/dist/render/shaders/flat/preamble.js +23 -5
  77. package/dist/render/shaders/flat/worldUv.d.ts +22 -0
  78. package/dist/render/shaders/flat/worldUv.js +113 -0
  79. package/dist/render/shaders/generated/ambientOcclusion.wgsl.d.ts +6 -1
  80. package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +6 -1
  81. package/dist/render/shaders/generated/flat.wgsl.d.ts +768 -368
  82. package/dist/render/shaders/generated/flat.wgsl.js +791 -391
  83. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +8 -3
  84. package/dist/render/shaders/generated/globalMedium.wgsl.js +8 -3
  85. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +15 -10
  86. package/dist/render/shaders/generated/lightVolume.wgsl.js +16 -11
  87. package/dist/render/shaders/globalMedium.js +9 -1
  88. package/dist/render/shaders/lightVolume.js +9 -1
  89. package/dist/render/shaders/tangentFrame.d.ts +1 -1
  90. package/dist/render/shaders/tangentFrame.js +16 -3
  91. package/dist/render/skinnedClothRun.d.ts +28 -0
  92. package/dist/render/skinnedClothRun.js +103 -35
  93. package/dist/render/surfaceLayers.d.ts +107 -0
  94. package/dist/render/surfaceLayers.js +82 -0
  95. package/dist/render/surfaceOverlay.d.ts +16 -3
  96. package/dist/render/surfaceOverlay.js +11 -6
  97. package/dist/render/surfaceProjection.d.ts +34 -0
  98. package/dist/render/surfaceProjection.js +30 -0
  99. package/dist/render/surfaceTexture.d.ts +39 -0
  100. package/dist/render/transmission.d.ts +21 -0
  101. package/dist/render/transmission.js +21 -0
  102. package/dist/render/vertexPacking.d.ts +48 -0
  103. package/dist/render/vertexPacking.js +68 -0
  104. package/dist/ui/splash.d.ts +42 -2
  105. package/dist/ui/splash.js +78 -13
  106. package/package.json +6 -6
  107. package/src/index.ts +6 -2
  108. package/src/render/ambientOcclusionPass.ts +4 -1
  109. package/src/render/backend/api.ts +31 -0
  110. package/src/render/backend/webgl2/renderer.ts +519 -56
  111. package/src/render/backend/webgpu/buffers.ts +153 -54
  112. package/src/render/backend/webgpu/clothKernels.ts +86 -0
  113. package/src/render/backend/webgpu/clothOps.ts +86 -0
  114. package/src/render/backend/webgpu/clothSetLayout.ts +102 -0
  115. package/src/render/backend/webgpu/clothSetPack.ts +197 -0
  116. package/src/render/backend/webgpu/drawCommand.ts +3 -0
  117. package/src/render/backend/webgpu/flatPass.ts +26 -28
  118. package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
  119. package/src/render/backend/webgpu/latePass.ts +1 -0
  120. package/src/render/backend/webgpu/motionPass.ts +18 -7
  121. package/src/render/backend/webgpu/pipelineCache.ts +46 -7
  122. package/src/render/backend/webgpu/renderer.ts +622 -164
  123. package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
  124. package/src/render/backend/webgpu/staticBundles.ts +2 -1
  125. package/src/render/backend/webgpu/vertexFormats.ts +113 -0
  126. package/src/render/globalMediumPass.ts +7 -0
  127. package/src/render/indexWidth.ts +27 -0
  128. package/src/render/instances.ts +18 -1
  129. package/src/render/lightMatrix.ts +15 -0
  130. package/src/render/mesh.ts +66 -14
  131. package/src/render/occlusionFade.ts +29 -0
  132. package/src/render/renderQuality.ts +38 -0
  133. package/src/render/rendererHarness.ts +15 -0
  134. package/src/render/sceneTarget.ts +3 -0
  135. package/src/render/shader.ts +59 -0
  136. package/src/render/shaders/ambientOcclusion.ts +12 -1
  137. package/src/render/shaders/flat/directionalShadow.ts +84 -10
  138. package/src/render/shaders/flat/glassTint.ts +7 -1
  139. package/src/render/shaders/flat/index.ts +39 -4
  140. package/src/render/shaders/flat/layered.ts +198 -0
  141. package/src/render/shaders/flat/main.ts +50 -16
  142. package/src/render/shaders/flat/mapReads.ts +38 -0
  143. package/src/render/shaders/flat/movingSun.ts +46 -0
  144. package/src/render/shaders/flat/normalMap.ts +23 -3
  145. package/src/render/shaders/flat/overlay.ts +41 -19
  146. package/src/render/shaders/flat/preamble.ts +23 -5
  147. package/src/render/shaders/flat/worldUv.ts +113 -0
  148. package/src/render/shaders/generated/ambientOcclusion.wgsl.ts +6 -1
  149. package/src/render/shaders/generated/flat.wgsl.ts +791 -391
  150. package/src/render/shaders/generated/globalMedium.wgsl.ts +8 -3
  151. package/src/render/shaders/generated/lightVolume.wgsl.ts +16 -11
  152. package/src/render/shaders/globalMedium.ts +9 -1
  153. package/src/render/shaders/lightVolume.ts +9 -1
  154. package/src/render/shaders/tangentFrame.ts +16 -3
  155. package/src/render/skinnedClothRun.ts +138 -38
  156. package/src/render/surfaceLayers.ts +176 -0
  157. package/src/render/surfaceOverlay.ts +24 -7
  158. package/src/render/surfaceProjection.ts +57 -0
  159. package/src/render/surfaceTexture.ts +39 -0
  160. package/src/render/transmission.ts +27 -0
  161. package/src/render/vertexPacking.ts +109 -0
  162. package/src/ui/splash.ts +83 -14
  163. package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +0 -74
  164. package/dist/render/backend/webgpu/gpuSkinnedCloth.js +0 -452
  165. package/src/render/backend/webgpu/gpuSkinnedCloth.ts +0 -502
package/CHANGELOG.json CHANGED
@@ -1,4 +1,111 @@
1
1
  [
2
+ {
3
+ "version": "4.11.1",
4
+ "date": "2026-10-10",
5
+ "entries": [
6
+ {
7
+ "kind": "added",
8
+ "short": "`SurfaceMaterial.layers` takes its mask from the ORM array or the vertex colour, sums layers, places them by the world, lays one by facing up, and lays a normal under them.",
9
+ "text": "`layers` covers more of the ways a layered surface is blended. `mask: 'orm'` reads the mask from the ORM array's layer past the material's layers, which leaves the model's slot to a lightmap's page, so a lightmapped surface blends its layers; `mask: 'vertex'` takes the weights from the vertex colour, which then tints nothing. `blend: 'sum'` mixes the base toward each layer times its weight, by the weights' sum held to 1, besides laying each over the ones before it. Under a `projection` the layers are placed by the world, each at its own repeats a metre, and `addMask` adds a mask placed the same way to one layer's weight. `facing` weighs one layer by how much the surface faces up, and `meshNormal` lays a normal map read at the mesh's own coordinates under the layers' blended normal. A triplanar projection lays layers on the horizontal plane, and says so once. On `demo/dev/layerFamilies.html` a mask carried by the ORM array draws exactly as the same mask as a map, keeps its layers under a lightmap's page, and every mode draws the same on both backends."
10
+ },
11
+ {
12
+ "kind": "added",
13
+ "short": "`SurfaceMaterial.transmissionColor` names the colour light through a thin surface takes, where it was always the surface's own.",
14
+ "text": "`transmissionColor`, linear, is the colour the light from behind a thin surface (`diffuseTransmission`) takes through it, in place of the surface's own colour: a printed banner glowing a flat red behind its print rather than lit through in the print's colours. Absent, the surface's colour, as before. It rides the glass tint's colour lanes, which no opaque material reads, and a pane drawn between two draws of the material puts it back. On `demo/dev/thinLight.html?color=0,0.4,1` a red banner lit from behind went from 163, 24, 19 to 16, 74, 171 under the sun with no red let through, the same to the pixel on both backends."
15
+ },
16
+ {
17
+ "kind": "added",
18
+ "short": "`setAmbientOcclusionFade(distance, radius)` fades ambient occlusion out with distance, so a sky dome or a far range is not shaded in rings.",
19
+ "text": "Ambient occlusion is whole up to `distance` metres from the eye and gone `radius` metres past it, held until changed; a negative distance, the default, fades nothing. Far off, the depth buffer's steps are metres apart and the estimate shaded them in bands across a sky dome or a mountain range; past the fade those pixels are left open and skip the estimate's walk. Bound in DriftScript as `drift/render`'s `occlusionFade`. On `demo/dev/aoFade.html` a ridge 3 km off went from 66,267 banded pixels to none, while the feet of the near boxes darkened exactly as before, on both backends."
20
+ },
21
+ {
22
+ "kind": "added",
23
+ "short": "A lightmapped instance fades by `setDitherOpacity` too: its opacity rides its page region in sixty-fourths, where it had none.",
24
+ "text": "A lightmapped batch carries its page regions in the lane `MeshInstances.alphas` would take, so its instances could not fade. Their opacity now travels as whole steps added to the region's U offset, 64 of them, the screen door's own resolution, and the vertex stage takes them back off. An opaque instance's region is written to the bit as before; a fading one's offset keeps 16 bits of fraction, a sixteenth of a texel on a 4,096 page. On `demo/dev/lightmap.html?mode=tiles&fade=0.5` the faded tiles keep 50.0% of their pixels, each lit exactly as unfaded, and nothing else changes, on both backends."
25
+ },
26
+ {
27
+ "kind": "added",
28
+ "short": "`prepareMesh` and `prepareInstanced` compile a draw's pipelines in its material before its first frame, so a scene drawn under `pipelineCompile: 'skip'` shows whole.",
29
+ "text": "`prepareMesh(mesh, material)` and `prepareInstanced(batch, material)` compile, off the main thread, every pipeline a draw of that mesh or batch in that material takes that making it did not: a two-sided, cut-out or surface-model variant, a skin's halves under the screen-space blur, and the material's lit switches; `{ translucent: true }` adds the blended ones. `ready()` resolves once they have. Under `pipelineCompile: 'skip'` a draw whose pipeline is new is left out until it lands, so a figure brought on screen came without its skin and hair for a few frames; prepared, it is drawn whole in its first frame. On WebGPU a prepared key is the one the draw asks for, worked out by the same function; on WebGL2 a surface model's programs are what a draw can wait for, and are what it prepares."
30
+ },
31
+ {
32
+ "kind": "added",
33
+ "short": "`holdSplash(promise, { capMs })` keeps the engine's badge up until the game's own first screen has loaded, under a cap of its own.",
34
+ "text": "The badge left three seconds after the first frame, twenty at most, so a game whose first screen loads for longer was seen loading. `holdSplash` keeps it up until the promise settles, either way, as well as until the first frame; frames run behind it while the game loads, since a load may need them, and are held once it has settled for what is left of the minimum. `capMs` is the game's ceiling in place of the engine's twenty seconds, the longest of several holds, and the call answers whether there was a badge to hold. On `demo/dev/splashHold.html` a load settling at 6.2 s took the badge down at 6.7 s with 361 frames run under it, a rejected load the same, and an 8-second cap at 8.4 s."
35
+ }
36
+ ]
37
+ },
38
+ {
39
+ "version": "4.11.0",
40
+ "date": "2026-10-10",
41
+ "entries": [
42
+ {
43
+ "kind": "added",
44
+ "short": "`SurfaceRim.mode: 'blend'` pulls a surface's colour toward the rim colour at its edges before it is lit, where a rim used to add glow over it.",
45
+ "text": "A surface overlay's rim takes `mode: 'blend'`: the base colour is mixed toward the rim colour by the edge's weight, raised to its contrast and scaled by its alpha, before lighting, so the edge is lit, shadowed and fogged as the rest of the surface is rather than glowing over it, and a colour channel weighted above 1 is held at 1. `'add'`, the default, is the rim as it was. On `demo/dev/surfaceOverlay.html?rim=blend` an edge out of the light moved by −1.9 in luminance where adding moved it by +41, and the two backends draw the same picture."
46
+ },
47
+ {
48
+ "kind": "added",
49
+ "short": "`setDitherOpacity(true)` spends each opaque draw's own opacity, an instance's alpha included, on a screen door, so an instance fades without blending or a pop.",
50
+ "text": "With `setDitherOpacity(true)` the draws that follow keep a pixel only where the opacity it carries (a mesh's own, or `MeshInstances.alphas` for an instance of a batch) lies above an 8x8 ordered threshold, the pattern `setDitherFade` uses, so an opaque instance fades in or out over a few frames instead of popping, still writes depth and needs no sorting; two instances crossfading cover each pixel once. On `demo/dev/hlodFade.html?opacity=` the share of pixels kept matches the opacity to 1/64 on both backends. What it gives up: a screen door shows as a pattern up close, which the temporal resolve smooths where it runs."
51
+ },
52
+ {
53
+ "kind": "added",
54
+ "short": "`createSkinnedClothSet` solves every garment together, one dispatch per batch of constraints across all of them, and `maxSteps` caps the steps one frame runs.",
55
+ "text": "`createSkinnedClothSet(renderer, setups)` solves several garments together: on WebGPU one pass a step, each constraint batch of every garment merged by colour into one dispatch, where each garment had its own encoder, pass and submit. Garments are named by their index, in `setPose(g, …)`, `setWind(g, …)`, `reset(g)` and `particles[g]` for `setCloth`, and `step(dt)` runs them all; `createSkinnedCloth` is a set of one, and a backend without compute runs the set on the CPU. Garments in a set share `substeps`, `iterations` and `maxSteps`, and are refused by the first they disagree on. `SkinnedClothParameters.maxSteps` caps the whole steps one `advance` runs and drops the time past it, so a slow frame no longer hands the next one more steps; unset, it is unbounded as before. On `demo/dev/skinnedCloth.html?bench=1&garments=15`, fifteen garments step in 0.793 ms as one set against 8.903 ms as fifteen solvers, and a set agrees with separate solvers within the spread of a nudged control."
56
+ },
57
+ {
58
+ "kind": "added",
59
+ "short": "`RenderQuality.pipelineCompile: 'skip'` leaves a draw out while its new pipeline compiles off the main thread, rather than holding the frame for the compile.",
60
+ "text": "A lit pipeline first needed inside a frame, for a material's surface model or a lit switch met mid-scene, was compiled where it was asked for, which holds the GPU process for the compile, reported at 130 to 400 ms on a phone. With `pipelineCompile: 'skip'` the compile starts off the main thread and the draw is left out until it lands; `'wait'`, the default, is as before. `ready()` waits for every compile started either way. On WebGL2 a model program compiles through `KHR_parallel_shader_compile` where the context offers it and waits where it does not. On `demo/dev/pipelineCompile.html`, eight new variants at once held a WebGPU frame 700 to 3,700 ms waiting and 17 ms skipping, the draws landing 21 to 26 frames later; through ANGLE on OpenGL the longest WebGL2 frame went from 820–1,120 ms to 630–700 ms, the driver finishing its own compile at the program's first draw. What it gives up is the draw, for the frames its compile takes."
61
+ },
62
+ {
63
+ "kind": "added",
64
+ "short": "`SurfaceMaterial.projection` places a material's maps by the world, planar across the ground or triplanar on three planes, at so many repeats a metre.",
65
+ "text": "`projection: { kind: 'planar' | 'triplanar', scale, sharpness? }` reads a material's maps at a point's place in the world rather than at the mesh's texture coordinates: `'planar'` across the two horizontal axes, `'triplanar'` on three planes blended by how squarely the surface faces each, with the normal map turned into the world by the whiteout blend, so no tangent frame is needed. A lit switch, compiled in the first time a material asks. On `demo/dev/worldUv.html` a triplanar cube matches the same map read through each face's own coordinates and tangents, on both backends. What it gives up: a projection belongs to the world, so a mesh that moves slides under it; triplanar reads each map three times; a texture is mirrored on a plane's far side; and a cutout material's shadow is still cut by the mesh's coordinates."
66
+ },
67
+ {
68
+ "kind": "added",
69
+ "short": "The sun's moving shadow layer is read through the matrix its own pass was drawn with, so what moves can have a tight, sharp square inside the static layer's wide one.",
70
+ "text": "A frame's `beginShadowPass(matrix, 'dynamic')` drawn with a matrix other than the environment's `lightViewProj` is now read through that matrix: the static layer keeps one square over the whole of what stands still, and the moving layer a tight one around what moves, at its own texel size, filter and depth span. The lit stage, the global medium and light volumes all read it, behind a lit switch that turns on the first frame a dynamic pass's matrix differs. On `demo/dev/sunLayers.html?moving=tight` a moving caster's shadow core went from 144 to 202 pixels and its penumbra from 886 to 739, and everything outside the tight square was unchanged to the pixel, on both backends. What it gives up: a receiver outside the static layer's square takes no sun shadow at all, so the tight square belongs inside the wide one."
71
+ },
72
+ {
73
+ "kind": "added",
74
+ "short": "`SurfaceMaterial.diffuseTransmission` lets light from behind a thin surface through, coloured by it: a banner, a leaf or a lampshade lit from behind.",
75
+ "text": "`diffuseTransmission`, 0 to 1, brings the light falling on a surface's far side (the sun, lamps, area lights and DriftLight) to the eye through it, coloured by the surface's own colour; a metal lets none through, and 0, the default, is every material as it was. A lamp's shadow is read from the side the lamp is on, so a thin surface does not stand in its own shadow. On `demo/dev/thinLight.html` a sheet lit only from behind went from 16 to 163 in luminance under the sun and to 104 under a lamp, with no light behind it unchanged, on both backends. What it gives up: a thin surface rather than a volume, so a thick one lets as much through as a sheet; the light behind is not blurred; and glass lets light through by its own rule."
76
+ },
77
+ {
78
+ "kind": "added",
79
+ "short": "`SurfaceMaterial.layers` blends up to five layers by a mask, each at its own repeat, colour, normal and roughness alike, out of one texture array per map.",
80
+ "text": "`layers: { mask, repeats, emissiveLayer? }` lays up to four layers over a base by an RGBA mask read at the mesh's coordinates, red laying layer 1 through alpha laying layer 4, each over every one before it. Layer `i` is layer `i` of the material's albedo, normal and ORM arrays, read at the coordinates times its own repeat, and the tangent-space normals are blended before the frame turns them into the world; `emissiveLayer` makes the emissive map glow only where that layer shows. A lit switch, compiled in the first time a material asks, and the mask is read where `modelMap` goes, so it takes no texture binding of its own. On `demo/dev/layers.html` three layers at three repeats draw the same on both backends, and a material without layers draws as before. What it gives up: every layer is read at every pixel whatever its share; a material carries a mask or a surface model's map, not both."
81
+ },
82
+ {
83
+ "kind": "changed",
84
+ "short": "A mesh's indices are sixteen bits where its vertices fit, and its normals, tangents, colours and weights sixteen-bit fixed point: 29% less mesh memory in the published scenes.",
85
+ "text": "Every attribute of a mesh travelled as 32-bit floats and every index as 32 bits. Indices are 16 bits now wherever a mesh has 65,536 vertices or fewer; normals and tangents travel as 16-bit signed fixed point, colours and skin weights inside [0, 1] as unsigned; and a colour or an emissive every vertex shares is a constant rather than a stream. Both backends decide it the same way, from the data, when a mesh is made, and positions and texture coordinates stay floats. Measured on WebGPU across the eight published scenes, mesh vertex and index buffers went from 210 to 150 MB: the voxel sandbox's vertices from 38.4 to 28.5 MB and its indices from 4.8 to 2.4, the showroom's from 113.8 to 78.2 and from 34.9 to 25.2. Eight of the nine scenes are the same to the pixel on WebGPU, and one moves 12 pixels by 2 of 255 on average, which is normals at 1/32767. What it gives up: texture coordinates stay floats because sixteen bits move a pixel on an atlas tile's edge onto the next tile, measured as 29 pixels in the voxel sandbox; a dynamic mesh's normals stay floats; and an attribute outside its range stays a float rather than being clamped."
86
+ },
87
+ {
88
+ "kind": "fixed",
89
+ "short": "A normal map on a mesh with no tangents and a mirrored mapping is no longer turned half a circle by the frame derived for it, which lit it from the far side.",
90
+ "text": "Where a mesh carries no tangents, the frame its normal map is read in is derived from screen-space derivatives, and its handedness was taken from the texture coordinates' determinant alone, which cannot see a mapping mirrored about the normal: such a surface had its bumps lit from the side opposite the light. The handedness is the surface's orientation on screen against its normal now, which is the determinant's sign wherever a mapping is not mirrored. On `demo/dev/worldUv.html` a mirrored ground read 74.8 derived against 110.8 through its tangents, and 110.8 both ways after; every published scene is unchanged to the pixel."
91
+ },
92
+ {
93
+ "kind": "fixed",
94
+ "short": "On WebGPU a lit surface reads the sun's static layer through the environment's light matrix, not through whichever matrix the last shadow pass used.",
95
+ "text": "The WebGPU lit vertex stage projected a receiver into the sun's map through the matrix of the frame's last shadow pass. Where every pass shared one matrix nothing showed; where a dynamic pass was drawn with a tighter one, the static layer was read through it and its shadows landed in the wrong place. It projects through `env.lightViewProj` now, as WebGL2 always did."
96
+ },
97
+ {
98
+ "kind": "fixed",
99
+ "short": "`ready()` no longer waits for ever on a pipeline whose compile off the main thread failed, and it waits for a lit switch's rebuild as well.",
100
+ "text": "A pipeline whose `createRenderPipelineAsync` rejected stayed among the compiles `ready()` waits on, so a boot awaiting it never resolved; it is taken out, and built where it is next asked for so the device says why. And `ready()` now waits for a lit switch's rebuild, so the first frame after it has every pipeline with the feature that turned the switch on, where a capture taken then photographed the frames before."
101
+ },
102
+ {
103
+ "kind": "fixed",
104
+ "short": "On WebGL2 a surface model's program first made in the middle of a mesh pass keeps the pass's dither fade, where its draws used to ignore it.",
105
+ "text": "A program for a surface model first made between `bindMeshPass` and the draws after it was handed the pass's state with the dither fade cleared, so the draws through it ignored `setDitherFade` until the next pass. WebGL2 only: WebGPU carries the fade in each draw's block."
106
+ }
107
+ ]
108
+ },
2
109
  {
3
110
  "version": "4.10.0",
4
111
  "date": "2026-10-07",
package/README.md CHANGED
@@ -6,7 +6,7 @@ WebGL2 everywhere else, behind one API, and carries the fixed-step loop, the ren
6
6
  input, the scene graph, geometry, text and the whole of `@driftengine/physics`, which it re-exports.
7
7
  Everything else is a separate package a game adds when it reaches for it.
8
8
 
9
- **Cost: 873.3 KB gzipped, importing `createRenderer`.** Measured by `scripts/size-gate.test.mjs`,
9
+ **Cost: 904.6 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
@@ -155,7 +155,7 @@ export { BLOOM_LEVELS } from './render/bloomChain.ts';
155
155
  export type { BloomResponse } from './render/bloomChain.ts';
156
156
  export { probeDevice } from './render/backend/probe.ts';
157
157
  export type { ProbeVerdict } from './render/backend/probe.ts';
158
- export type { BoltHandle, CausticsHandle, ClothBindingHandle, ClothParticlesHandle, FlockHandle, IncrementalMeshHandle, LineHandle, MeshHandle, MeshOptions, RenderBackend, RendererApi, ParticleHandle, PlumeHandle, ScatterHandle, InstancedHandle, SdfTextHandle, SurfaceTextureHandle, TextHandle, WaterHandle, WindStreakHandle, } from './render/backend/api.ts';
158
+ export type { BoltHandle, CausticsHandle, ClothBindingHandle, ClothParticlesHandle, FlockHandle, IncrementalMeshHandle, LineHandle, MeshHandle, MeshOptions, PrepareOptions, RenderBackend, RendererApi, ParticleHandle, PlumeHandle, ScatterHandle, InstancedHandle, SdfTextHandle, SurfaceTextureHandle, TextHandle, WaterHandle, WindStreakHandle, } from './render/backend/api.ts';
159
159
  export type { ClothBindingData } from './render/clothBindingData.ts';
160
160
  export { anisotropicModel, eyeModel, hairModel, lightmapModel, skinModel, } from './render/surfaceModel.ts';
161
161
  export type { AnisotropicModel, EyeModel, HairModel, LightmapModel, SkinModel, SurfaceModel, SurfaceModelKind, } from './render/surfaceModel.ts';
@@ -164,8 +164,8 @@ export type { FrameReflections } from './render/frameReflections.ts';
164
164
  export type { OverlayRegion, SurfaceDissolve, SurfaceOverlay, SurfaceRim, SurfaceWrinkle, } from './render/surfaceOverlay.ts';
165
165
  export { WHOLE_PAGE } from './render/lightmap.ts';
166
166
  export type { LightmapPage, LightmapRegion } from './render/lightmap.ts';
167
- export { createSkinnedCloth } from './render/skinnedClothRun.ts';
168
- export type { SkinnedClothSolver } from './render/skinnedClothRun.ts';
167
+ export { createSkinnedCloth, createSkinnedClothSet } from './render/skinnedClothRun.ts';
168
+ export type { SkinnedClothSet, SkinnedClothSolver } from './render/skinnedClothRun.ts';
169
169
  /**
170
170
  * What a frame asked of a backend, against the ceilings that backend imposes.
171
171
  *
@@ -223,11 +223,12 @@ export type { PrefilterQuality } from './render/prefilterEnvMap.ts';
223
223
  * `ProbeBakeOptions`, which is the whole of the reasoning.
224
224
  */
225
225
  export type { ProbeBakeOptions } from './render/reflectionProbe.ts';
226
- export type { DirectionalShadowDepthLayers, GlassShadows, OutputTransform, RenderQuality, RenderQualityOptions, ShadowFilterTaps, SkinScattering, WaterReflectionFilterTaps, } from './render/renderQuality.ts';
226
+ export type { DirectionalShadowDepthLayers, GlassShadows, OutputTransform, PipelineCompile, RenderQuality, RenderQualityOptions, ShadowFilterTaps, SkinScattering, WaterReflectionFilterTaps, } from './render/renderQuality.ts';
227
227
  export { MAX_POINT_LIGHTS, SURFACE_TEXTURE_UNIT } from './render/lightBudget.ts';
228
228
  export { MAX_CLUSTERED_LIGHTS } from './render/clusteredLights.ts';
229
229
  export { SurfaceTexture } from './render/surfaceTexture.ts';
230
230
  export type { SurfaceMaterial, SurfaceTextureOptions } from './render/surfaceTexture.ts';
231
+ export type { SurfaceProjection } from './render/surfaceProjection.ts';
231
232
  export { compressedFormatName, isCompressedSource, levelBytes, uploadsCompressed, } from './render/compressedSource.ts';
232
233
  export type { BlockFormat, CompressedTextureFormat, CompressedTextureSource, SurfaceSource, } from './render/compressedSource.ts';
233
234
  export { DEFAULT_GOVERNOR_LIMITS, ResolutionGovernor } from './render/resolutionGovernor.ts';
@@ -343,7 +344,7 @@ export type { TouchControlsOptions } from './input/touchControls.ts';
343
344
  export { pixelCursor } from './ui/pixelCursor.ts';
344
345
  export { fullscreenWanted, requestFullscreenOnGesture } from './ui/fullscreen.ts';
345
346
  export type { FullscreenConditions } from './ui/fullscreen.ts';
346
- export { SPLASH_FADE_MS, SPLASH_HARD_CAP_MS, SPLASH_MIN_MS, forcedSplash, mountSplash, splashDecision, splashWanted, } from './ui/splash.ts';
347
+ export { SPLASH_FADE_MS, SPLASH_HARD_CAP_MS, SPLASH_MIN_MS, forcedSplash, holdSplash, mountSplash, splashDecision, splashWanted, } from './ui/splash.ts';
347
348
  export type { MountedSplash, SplashConditions, SplashDecision, SplashOptions, } from './ui/splash.ts';
348
349
  export { DEFAULT_CLIP_FPS, FrameRecorder, offerClip } from './ui/frameRecorder.ts';
349
350
  export { stillFrame } from './ui/stillFrame.ts';
package/dist/index.js CHANGED
@@ -237,7 +237,7 @@ export { WHOLE_PAGE } from './render/lightmap.js';
237
237
  * A skinned cloth solved where the renderer can: compute on WebGPU, the CPU under WebGL2. A function
238
238
  * rather than a renderer method so only a game that imports it carries the solver.
239
239
  */
240
- export { createSkinnedCloth } from './render/skinnedClothRun.js';
240
+ export { createSkinnedCloth, createSkinnedClothSet } from './render/skinnedClothRun.js';
241
241
  /*
242
242
  * Reading an image's texels back exactly, which no canvas and no `VideoFrame` will do.
243
243
  *
@@ -439,7 +439,7 @@ export { fullscreenWanted, requestFullscreenOnGesture } from './ui/fullscreen.js
439
439
  * they are here for the consumer that declines the automatic one and places its own, and because
440
440
  * the two decisions behind it are worth reading before overriding either.
441
441
  */
442
- export { SPLASH_FADE_MS, SPLASH_HARD_CAP_MS, SPLASH_MIN_MS, forcedSplash, mountSplash, splashDecision, splashWanted, } from './ui/splash.js';
442
+ export { SPLASH_FADE_MS, SPLASH_HARD_CAP_MS, SPLASH_MIN_MS, forcedSplash, holdSplash, mountSplash, splashDecision, splashWanted, } from './ui/splash.js';
443
443
  export { DEFAULT_CLIP_FPS, FrameRecorder, offerClip } from './ui/frameRecorder.js';
444
444
  // Not on `FrameRecorder`, so a screenshot does not cost a clip pipeline. See the module.
445
445
  export { stillFrame } from './ui/stillFrame.js';
@@ -46,8 +46,9 @@ export declare class AmbientOcclusionPass {
46
46
  * by, or null if this pass could not run, which the caller must read as "no occlusion"
47
47
  * rather than as black.
48
48
  *
49
- * `invProjection` is column-major, as `gl-matrix` produces it.
49
+ * `invProjection` is column-major, as `gl-matrix` produces it. `fade` is `uFade`, where the
50
+ * occlusion fades out with distance (`occlusionFade.ts`).
50
51
  */
51
- run(depth: WebGLTexture, width: number, height: number, radius: number, projScale: Float32Array, invProjection: Float32Array): WebGLTexture | null;
52
+ run(depth: WebGLTexture, width: number, height: number, radius: number, projScale: Float32Array, invProjection: Float32Array, fade: Float32Array): WebGLTexture | null;
52
53
  dispose(): void;
53
54
  }
@@ -102,9 +102,10 @@ export class AmbientOcclusionPass {
102
102
  * by, or null if this pass could not run, which the caller must read as "no occlusion"
103
103
  * rather than as black.
104
104
  *
105
- * `invProjection` is column-major, as `gl-matrix` produces it.
105
+ * `invProjection` is column-major, as `gl-matrix` produces it. `fade` is `uFade`, where the
106
+ * occlusion fades out with distance (`occlusionFade.ts`).
106
107
  */
107
- run(depth, width, height, radius, projScale, invProjection) {
108
+ run(depth, width, height, radius, projScale, invProjection, fade) {
108
109
  if (!this.ensureSize(width, height))
109
110
  return null;
110
111
  const { gl } = this;
@@ -123,6 +124,7 @@ export class AmbientOcclusionPass {
123
124
  gl.uniform1i(this.estimateUniforms['uDepth'] ?? null, 0);
124
125
  gl.uniform2fv(this.estimateUniforms['uProjScale'] ?? null, projScale);
125
126
  gl.uniform1f(this.estimateUniforms['uRadius'] ?? null, radius);
127
+ gl.uniform2fv(this.estimateUniforms['uFade'] ?? null, fade);
126
128
  gl.uniformMatrix4fv(this.estimateUniforms['uInvProjection'] ?? null, false, invProjection);
127
129
  gl.drawArrays(gl.TRIANGLES, 0, 3);
128
130
  /*
@@ -177,6 +177,14 @@ export interface SurfaceTextureHandle {
177
177
  * `WebGLBuffer` in a private field, so no WebGPU object can satisfy it however identical its
178
178
  * shape. It is not meant to be read; it is meant to be enforced.
179
179
  */
180
+ /** Which of a draw's pipelines `prepareMesh` and `prepareInstanced` compile beside the opaque ones. */
181
+ export interface PrepareOptions {
182
+ /**
183
+ * The blended ones too, for a draw made with `drawTranslucentMesh` or `drawTranslucentInstanced`.
184
+ * Off by default: an opaque surface would pay a compile it never draws with.
185
+ */
186
+ readonly translucent?: boolean;
187
+ }
180
188
  /** What a mesh needs to be told at creation that its geometry cannot say. */
181
189
  export interface MeshOptions {
182
190
  /**
@@ -190,7 +198,7 @@ export interface MeshOptions {
190
198
  }
191
199
  export type RendererApi = Omit<{
192
200
  [K in keyof Renderer]: Renderer[K];
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'> & {
201
+ }, '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' | 'prepareMesh' | 'prepareInstanced' | 'setSurfaceOverlay' | 'createClothBinding' | 'createClothParticles' | 'updateClothParticles' | 'setCloth' | 'disposeClothBinding' | 'disposeClothParticles'> & {
194
202
  /**
195
203
  * A mesh's cloth binding: which simulation triangle each of its vertices follows, where on it and
196
204
  * by how much (`ClothBindingData`). The mesh must be skinned — a binding blends each vertex
@@ -299,6 +307,18 @@ export type RendererApi = Omit<{
299
307
  * setter each — and `setSurfaceTexture` is now a wrapper over it.
300
308
  */
301
309
  setMaterial(material: SurfaceMaterial<SurfaceTextureHandle> | null): void;
310
+ /**
311
+ * Compile, off the main thread, every pipeline a draw of `mesh` in `material` takes that making
312
+ * the mesh did not — a two-sided, cut-out or shaded-by-a-model variant, a material's lit switches,
313
+ * a skin's halves — so the first frame that draws it waits on none; `ready()` resolves once they
314
+ * have. For `pipelineCompile: 'skip'` above all, where a draw whose pipeline is new is left out
315
+ * until it lands: a figure brought on screen whole rather than without its face for a few frames.
316
+ * A rigged mesh is prepared as drawn with a palette, and one with targets both with weights and
317
+ * without. Call it between frames: it sets `material` to read it and leaves none set after.
318
+ */
319
+ prepareMesh(mesh: MeshHandle, material: SurfaceMaterial<SurfaceTextureHandle> | null, options?: PrepareOptions): void;
320
+ /** `prepareMesh` for an instanced batch drawn in `material`, its own pipelines being its own. */
321
+ prepareInstanced(batch: InstancedHandle, material: SurfaceMaterial<SurfaceTextureHandle> | null, options?: PrepareOptions): void;
302
322
  /**
303
323
  * A 3D text object: one string, drawn as instanced glyph cubes over the scene.
304
324
  *
@@ -49,7 +49,7 @@ import type { RenderQuality, RenderQualityOptions } from '../../renderQuality.ts
49
49
  import { Mesh } from '../../mesh.ts';
50
50
  import type { IncrementalMesh } from '../../mesh.ts';
51
51
  import type { MeshData } from '../../mesh.ts';
52
- import type { MeshOptions } from '../api.ts';
52
+ import type { MeshOptions, PrepareOptions } from '../api.ts';
53
53
  import { SurfaceTexture } from '../../surfaceTexture.ts';
54
54
  import type { CompressedTextureFormat, CompressedTextureSource, SurfaceSource } from '../../compressedSource.ts';
55
55
  import type { SurfaceMaterial, SurfaceTextureOptions } from '../../surfaceTexture.ts';
@@ -931,6 +931,15 @@ export declare class WebGL2Renderer implements RendererApi {
931
931
  * compiled the first time a draw asks for one. See `modelProgram`.
932
932
  */
933
933
  private readonly modelFlat;
934
+ /**
935
+ * Model programs still compiling, where draws skip a compile (`pipelineCompile: 'skip'`) and the
936
+ * context offers `KHR_parallel_shader_compile`; `parallelCompile` is its completion query then,
937
+ * and null where draws wait for a program here instead. See `modelProgram`.
938
+ */
939
+ private readonly modelPending;
940
+ /** What `drawVariantOf` answers, rewritten each draw rather than made. */
941
+ private readonly drawVariant;
942
+ private readonly parallelCompile;
934
943
  /** The last palette chosen, which an eye's axis is turned by. See `setSkinPalette`. */
935
944
  private paletteData;
936
945
  private readonly emptyTexture2D;
@@ -993,7 +1002,7 @@ export declare class WebGL2Renderer implements RendererApi {
993
1002
  private oitReplaying;
994
1003
  /** `uSurfaceScene`, resolved once a pass. See `surfaceScene.ts`. */
995
1004
  private readonly surfaceScene;
996
- /** The two halves of `uWriteMode`, kept here because a `vec2` is written whole. */
1005
+ /** The three lanes of `uWriteMode`, kept here because a `vec3` is written whole. */
997
1006
  private oitWeight;
998
1007
  /**
999
1008
  * How the surfaces drawn next meet the medium: what `setSurfaceFog` set, and `FOG_RECEDE` from
@@ -1001,6 +1010,7 @@ export declare class WebGL2Renderer implements RendererApi {
1001
1010
  */
1002
1011
  private surfaceFog;
1003
1012
  private ditherFade;
1013
+ private ditherOpacity;
1004
1014
  /** Said once, not per frame, where the context cannot give the float target the sum needs. */
1005
1015
  private oitRefused;
1006
1016
  /** Said once for the whole renderer. See `refuseOitMultisampled`. */
@@ -1104,6 +1114,8 @@ export declare class WebGL2Renderer implements RendererApi {
1104
1114
  private readonly aoProjScale;
1105
1115
  /** Where this frame's jitter put the occlusion, in the composite's uv. See `uAoOffset`. */
1106
1116
  private readonly aoOffset;
1117
+ /** Where occlusion fades out with distance, `uFade`: none until a caller sets one. */
1118
+ private readonly aoFade;
1107
1119
  /**
1108
1120
  * Whether this frame has already been resolved to the canvas.
1109
1121
  *
@@ -1257,6 +1269,32 @@ export declare class WebGL2Renderer implements RendererApi {
1257
1269
  private overlayTexture;
1258
1270
  /** Whether this device's lit stage has room for the overlay's vectors: asked once. */
1259
1271
  private overlayRoom;
1272
+ /**
1273
+ * The matrix the sun's dynamic shadow pass was last drawn with, uncorrected, and whether one has
1274
+ * been: what the moving layer is read through. See `movingLightFor`.
1275
+ */
1276
+ private readonly movingLightViewProj;
1277
+ private movingLightKnown;
1278
+ /** Whether the lit stage has room for the moving layer's matrix, and whether its refusal was said. */
1279
+ private movingRoom;
1280
+ private warnedMovingRoom;
1281
+ /** The same answer for a material's projection, and whether its refusal was said. */
1282
+ private projectionRoom;
1283
+ private warnedProjectionRoom;
1284
+ /** `uWorldUv`, the current material's projection packed. See `surfaceProjection.ts`. */
1285
+ private readonly projection;
1286
+ /**
1287
+ * `uGlassTint` as the current material leaves it: its light from behind and that light's colour,
1288
+ * which a pane overwrites for its own draw and `unbindSeeThrough` puts back. See `transmission.ts`.
1289
+ */
1290
+ private readonly transmission;
1291
+ /** `uLayers`, the current material's layers packed. See `surfaceLayers.ts`. */
1292
+ private readonly layerFloats;
1293
+ /** Whether the lit stage has room for layers' two vectors, and which refusals were said. */
1294
+ private layersRoom;
1295
+ private warnedLayersRoom;
1296
+ private warnedLayersModelMap;
1297
+ private warnedLayersTriplanar;
1260
1298
  private warnedOverlayRoom;
1261
1299
  private warnedOverlayBlocks;
1262
1300
  /** Whether a probe is being baked, so the passes that would recurse into one can tell. */
@@ -1786,6 +1824,15 @@ export declare class WebGL2Renderer implements RendererApi {
1786
1824
  * this before its first frame gets the right behaviour on both without asking which backend
1787
1825
  * it has, which is the contract `RendererApi` exists to keep.
1788
1826
  */
1827
+ /**
1828
+ * Resolve once every program a draw has started compiling has finished, adopting each: what a
1829
+ * loading screen awaits after drawing what its scene will use, where draws skip a compile. See
1830
+ * `RenderQuality.pipelineCompile`; nothing is started anywhere else here, so otherwise it is now.
1831
+ *
1832
+ * **It waits without holding the thread**, as the other backend's does: it asks the driver
1833
+ * whether each is done, which does not wait, and yields between asks. A driver that never says
1834
+ * done is asked the way that waits after `READY_MAX_POLLS`, so this cannot hang a loading screen.
1835
+ */
1789
1836
  ready(): Promise<void>;
1790
1837
  dispose(options?: {
1791
1838
  releaseContext?: boolean;
@@ -1918,6 +1965,25 @@ export declare class WebGL2Renderer implements RendererApi {
1918
1965
  * `uScale`/`vScale` are repeats across the mesh's own UV range: geometry authored in
1919
1966
  * metres is textured at whatever density the material wants without rebuilding it.
1920
1967
  */
1968
+ /**
1969
+ * Compile, off the frame, every program a draw of `mesh` in `material` takes that making the mesh
1970
+ * did not, so the first frame that draws it waits on none: the program of the material's shading
1971
+ * model for each vertex variant the mesh can be drawn with (its rig, with a palette; its targets,
1972
+ * with weights and without), a skin's halves under the screen-space blur, and the lit switches
1973
+ * the material turns on. `ready()` resolves once they have.
1974
+ *
1975
+ * **For `pipelineCompile: 'skip'` above all**, where a draw whose program is new is left out until
1976
+ * it lands: a figure brought on screen whole rather than without its face and hair for the frames
1977
+ * those take. Where draws wait, the programs compile here, at the call, rather than in a frame.
1978
+ * Two-sided and cut-out surfaces are draw state on this backend and need nothing; on WebGPU they
1979
+ * are pipelines of their own, prepared the same way.
1980
+ *
1981
+ * **Call it between frames**: it sets `material` to read it and leaves no material set after, as
1982
+ * `setMaterial(null)` does. `options` is the other backend's; blended draws share a program here.
1983
+ */
1984
+ prepareMesh(mesh: Mesh, material: SurfaceMaterial | null, _options?: PrepareOptions): void;
1985
+ /** `prepareMesh` for an instanced batch: its own programs, and its model's for its variant. */
1986
+ prepareInstanced(batch: InstancedBatch, material: SurfaceMaterial | null, _options?: PrepareOptions): void;
1921
1987
  /**
1922
1988
  * Choose the joint palette the following `drawMesh` calls skin by, or null for none.
1923
1989
  *
@@ -1936,6 +2002,8 @@ export declare class WebGL2Renderer implements RendererApi {
1936
2002
  * material at once, as `ensureClothPrograms` feeds its own.
1937
2003
  */
1938
2004
  private modelProgram;
2005
+ /** A model program compiled: kept, written to by every material setter, and fed the pass. */
2006
+ private adoptModel;
1939
2007
  /**
1940
2008
  * An eye's axis in the world for this draw, into its program's `uModelParams`: the material's
1941
2009
  * own numbers with the axis turned by the draw's model matrix and the eye's joint. See
@@ -2073,6 +2141,34 @@ export declare class WebGL2Renderer implements RendererApi {
2073
2141
  private overlayRoomLeft;
2074
2142
  /** Whether the lit shader with the overlay compiled in fits this GPU: counted once. */
2075
2143
  private overlayFits;
2144
+ /**
2145
+ * Whether a material's projection can be drawn: none asked, or the lit stage has the room for its
2146
+ * one vector. **Refused, said once, where it has not**, as the overlay is, and the material's
2147
+ * maps are then laid by the mesh's coordinates, as they were before it existed.
2148
+ */
2149
+ private projectionFits;
2150
+ /**
2151
+ * What the sun's moving layer is read through: the matrix its shadow pass was last drawn with,
2152
+ * where the lit stage carries one (`MOVING_SUN`); the environment's otherwise, which every caller
2153
+ * drawing both layers with one matrix hands it anyway.
2154
+ */
2155
+ private movingLightFor;
2156
+ /**
2157
+ * Turn the moving layer's own matrix on the first time a frame draws that layer with one the
2158
+ * environment's does not share. **Refused, said once, where the lit stage has no room for its five
2159
+ * vectors**: the layer is then read through the environment's matrix, so its shadows land where
2160
+ * that matrix puts them, and a caller there draws both layers with one.
2161
+ */
2162
+ private askMovingSun;
2163
+ /**
2164
+ * A material's layers where they can be drawn: none asked, a model's map in the mask's place, or
2165
+ * no room for their two vectors each leave one layer, the last two said once each.
2166
+ */
2167
+ private layersOf;
2168
+ /** Says once that a layered material's triplanar projection is laid on the horizontal plane. */
2169
+ private warnLayersTriplanar;
2170
+ /** Whether the lit shader with `feature` compiled in fits this GPU's fragment uniform vectors. */
2171
+ private switchFits;
2076
2172
  /**
2077
2173
  * How bright the environment the surfaces drawn next reflect is. 1 is the default and identity.
2078
2174
  *
@@ -2155,7 +2251,26 @@ export declare class WebGL2Renderer implements RendererApi {
2155
2251
  * are a pass of their own, do not. See `uDither` in `flat/preamble.ts` for what it costs.
2156
2252
  */
2157
2253
  setDitherFade(amount: number): void;
2158
- /** `uWriteMode`: the order-independent weight and the dither, which share one uniform row. */
2254
+ /**
2255
+ * Whether the draws that follow spend their own opacity on a screen door rather than on blending:
2256
+ * an instance's `MeshInstances.alphas`, or a vertex's channel alpha, keeps that share of the
2257
+ * pixels `setDitherFade` would and draws the rest not at all. So an opaque or cut-out batch fades
2258
+ * one instance at a time, with no sorting and no blend, and an instance at 1 is drawn whole.
2259
+ *
2260
+ * Off is what `bindMeshPass` restores. It shares the fade's pattern, so a crossfade at `t` and
2261
+ * `-t` of an instance at opacity `a` covers that instance's share once, without holes. Material
2262
+ * state like `setDitherFade`; instanced draws read it, and shadow casters do not.
2263
+ *
2264
+ * What it gives up: a fade reads as grain while it lasts, which a temporal resolve smooths and a
2265
+ * plain frame does not; a blended draw that asks spends its opacity here and blends none of it;
2266
+ * and a lightmapped batch's opacities ride its page regions, so they fade in steps of a
2267
+ * sixty-fourth, which is the pattern's own resolution (`MeshInstances.lightmapRegions`).
2268
+ */
2269
+ setDitherOpacity(on: boolean): void;
2270
+ /**
2271
+ * `uWriteMode`: the order-independent weight, the fade and whether the opacity is dithered, which
2272
+ * share one uniform row.
2273
+ */
2159
2274
  private writeMode;
2160
2275
  /**
2161
2276
  * How strong the microscopic relief on the surfaces drawn next is, and how coarse.
@@ -2317,6 +2432,8 @@ export declare class WebGL2Renderer implements RendererApi {
2317
2432
  * taking it mid-bake is how a probe's next face came to be attached to the scene's framebuffer.
2318
2433
  */
2319
2434
  private bindSeeThrough;
2435
+ /** `uGlassTint` as the current material leaves it. See `transmission`. */
2436
+ private writeTransmission;
2320
2437
  /** Put back what `bindSeeThrough` bound: the binding outlives its frame (the 2026-08-27 rule). */
2321
2438
  private unbindSeeThrough;
2322
2439
  disposeMesh(mesh: Mesh): void;
@@ -3206,6 +3323,17 @@ export declare class WebGL2Renderer implements RendererApi {
3206
3323
  * once rather than doing nothing. See `shaders/localExposure.ts`.
3207
3324
  */
3208
3325
  setLocalExposure(strength: number): void;
3326
+ /**
3327
+ * Where ambient occlusion fades out with distance: whole up to `distance` metres from the eye and
3328
+ * gone `radius` metres past it, so the far depth buffer's steps — a sky dome kilometres off, a
3329
+ * mountain range — are not shaded in rings, and those pixels skip the occlusion's sampling.
3330
+ *
3331
+ * A distance that is not a finite number at or above zero is no fade, which is the default, and
3332
+ * `Infinity` reads as "never fades"; a radius below zero is a cut at `distance`. Set before
3333
+ * `endFrame` and held until changed, like `setDepthOfField`. Nothing at all when
3334
+ * `ambientOcclusion` is 0. See `occlusionFade.ts` for what it gives up.
3335
+ */
3336
+ setAmbientOcclusionFade(distance: number, radius: number): void;
3209
3337
  /**
3210
3338
  * Where this frame's lens is focused, how deep the sharp zone is, and how much of the ceiling
3211
3339
  * to take.
@@ -3709,6 +3837,16 @@ export declare class WebGL2Renderer implements RendererApi {
3709
3837
  */
3710
3838
  private scatterSkin;
3711
3839
  /** One draw of a mesh, as `drawMesh` describes, of the half of a skin `half` names. */
3840
+ /**
3841
+ * Whether every half a split skin draws has its program, starting any that has not: a skin drawn
3842
+ * without its kept halves would lose its diffuse light for the frames they take to compile.
3843
+ */
3844
+ private skinHalvesReady;
3845
+ /**
3846
+ * The vertex variant a draw of `mesh` takes now, and its program and uniforms where it is not the
3847
+ * plain one: what a model's program is keyed by, and what `drawMeshHalf` draws with.
3848
+ */
3849
+ private drawVariantOf;
3712
3850
  private drawMeshHalf;
3713
3851
  /**
3714
3852
  * Draw a mesh you can see through, in the same material as everything else by default.