@driftengine/core 4.8.6 → 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 (253) hide show
  1. package/CHANGELOG.json +132 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +7 -2
  4. package/dist/index.js +8 -1
  5. package/dist/render/anisotropicLobe.d.ts +10 -0
  6. package/dist/render/anisotropicLobe.js +19 -0
  7. package/dist/render/areaLights.d.ts +35 -0
  8. package/dist/render/areaLights.js +35 -0
  9. package/dist/render/atmosphere.d.ts +13 -0
  10. package/dist/render/atmosphere.js +9 -1
  11. package/dist/render/backend/api.d.ts +41 -3
  12. package/dist/render/backend/createRenderer.d.ts +6 -0
  13. package/dist/render/backend/createRenderer.js +4 -1
  14. package/dist/render/backend/webgl2/renderer.d.ts +140 -16
  15. package/dist/render/backend/webgl2/renderer.js +436 -44
  16. package/dist/render/backend/webgl2/sceneCaptureTarget.d.ts +29 -0
  17. package/dist/render/backend/webgl2/sceneCaptureTarget.js +96 -0
  18. package/dist/render/backend/webgpu/device.d.ts +7 -1
  19. package/dist/render/backend/webgpu/device.js +31 -3
  20. package/dist/render/backend/webgpu/flatPass.d.ts +16 -1
  21. package/dist/render/backend/webgpu/flatPass.js +58 -26
  22. package/dist/render/backend/webgpu/particlePass.js +2 -1
  23. package/dist/render/backend/webgpu/postPass.d.ts +51 -9
  24. package/dist/render/backend/webgpu/postPass.js +16 -2
  25. package/dist/render/backend/webgpu/reflectionSurfacePass.d.ts +30 -0
  26. package/dist/render/backend/webgpu/reflectionSurfacePass.js +100 -0
  27. package/dist/render/backend/webgpu/renderer.d.ts +166 -3
  28. package/dist/render/backend/webgpu/renderer.js +738 -83
  29. package/dist/render/backend/webgpu/sceneCaptureTarget.d.ts +24 -0
  30. package/dist/render/backend/webgpu/sceneCaptureTarget.js +59 -0
  31. package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
  32. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +4 -1
  33. package/dist/render/backend/webgpu/surfaceTexturePass.js +34 -8
  34. package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
  35. package/dist/render/clusteredLights.d.ts +23 -5
  36. package/dist/render/clusteredLights.js +35 -8
  37. package/dist/render/displayRange.d.ts +31 -0
  38. package/dist/render/displayRange.js +47 -0
  39. package/dist/render/driftLight/bake.d.ts +2 -0
  40. package/dist/render/driftLight/bake.js +1 -1
  41. package/dist/render/driftLight/falloff.d.ts +4 -2
  42. package/dist/render/driftLight/falloff.js +8 -2
  43. package/dist/render/filmicCurve.d.ts +74 -0
  44. package/dist/render/filmicCurve.js +203 -0
  45. package/dist/render/fog.d.ts +4 -1
  46. package/dist/render/fog.js +12 -2
  47. package/dist/render/frame/resources.d.ts +2 -2
  48. package/dist/render/frame/resources.js +6 -0
  49. package/dist/render/frameReflections.d.ts +40 -0
  50. package/dist/render/frameReflections.js +48 -0
  51. package/dist/render/gi/bounceLights.d.ts +3 -2
  52. package/dist/render/gi/bounceLights.js +8 -3
  53. package/dist/render/instanceCull.js +5 -0
  54. package/dist/render/instances.d.ts +19 -5
  55. package/dist/render/instances.js +9 -3
  56. package/dist/render/lightBudget.d.ts +12 -1
  57. package/dist/render/lightBudget.js +23 -3
  58. package/dist/render/lightmap.d.ts +23 -9
  59. package/dist/render/lightmap.js +40 -18
  60. package/dist/render/particlePool.d.ts +21 -0
  61. package/dist/render/particlePool.js +7 -0
  62. package/dist/render/pass.d.ts +49 -0
  63. package/dist/render/pointLightSelection.d.ts +13 -0
  64. package/dist/render/pointLightSelection.js +3 -1
  65. package/dist/render/reflectionSurfaceTargets.d.ts +28 -0
  66. package/dist/render/reflectionSurfaceTargets.js +99 -0
  67. package/dist/render/renderQuality.d.ts +47 -15
  68. package/dist/render/renderQuality.js +4 -0
  69. package/dist/render/rendererHarness.js +3 -0
  70. package/dist/render/rgb9e5.d.ts +22 -0
  71. package/dist/render/rgb9e5.js +47 -0
  72. package/dist/render/sceneCapture.d.ts +29 -0
  73. package/dist/render/sceneCapture.js +36 -0
  74. package/dist/render/sceneTarget.d.ts +5 -0
  75. package/dist/render/sceneTarget.js +24 -1
  76. package/dist/render/screenSpaceReflection.d.ts +32 -1
  77. package/dist/render/screenSpaceReflection.js +14 -0
  78. package/dist/render/shaders/arcane.d.ts +1 -1
  79. package/dist/render/shaders/bolt.d.ts +1 -1
  80. package/dist/render/shaders/bolt.js +7 -1
  81. package/dist/render/shaders/caustics.d.ts +1 -1
  82. package/dist/render/shaders/depth.js +11 -0
  83. package/dist/render/shaders/film.d.ts +1 -1
  84. package/dist/render/shaders/filmLook.js +2 -1
  85. package/dist/render/shaders/fire.d.ts +1 -1
  86. package/dist/render/shaders/flat/anisotropic.d.ts +1 -1
  87. package/dist/render/shaders/flat/anisotropic.js +37 -3
  88. package/dist/render/shaders/flat/areaLight.d.ts +1 -1
  89. package/dist/render/shaders/flat/areaLight.js +46 -0
  90. package/dist/render/shaders/flat/glassTint.d.ts +3 -2
  91. package/dist/render/shaders/flat/glassTint.js +5 -1
  92. package/dist/render/shaders/flat/index.d.ts +15 -0
  93. package/dist/render/shaders/flat/index.js +22 -2
  94. package/dist/render/shaders/flat/lobes.d.ts +6 -0
  95. package/dist/render/shaders/flat/lobes.js +49 -43
  96. package/dist/render/shaders/flat/main.d.ts +1 -1
  97. package/dist/render/shaders/flat/main.js +85 -22
  98. package/dist/render/shaders/flat/models.d.ts +5 -2
  99. package/dist/render/shaders/flat/models.js +100 -16
  100. package/dist/render/shaders/flat/overlay.d.ts +16 -0
  101. package/dist/render/shaders/flat/overlay.js +117 -0
  102. package/dist/render/shaders/flat/pointShadow.js +1 -1
  103. package/dist/render/shaders/flat/preamble.js +10 -3
  104. package/dist/render/shaders/flat/reflectionSurface.d.ts +21 -0
  105. package/dist/render/shaders/flat/reflectionSurface.js +61 -0
  106. package/dist/render/shaders/fog.d.ts +1 -1
  107. package/dist/render/shaders/fog.js +9 -2
  108. package/dist/render/shaders/fringe.d.ts +29 -0
  109. package/dist/render/shaders/fringe.js +55 -0
  110. package/dist/render/shaders/generated/arcane.wgsl.d.ts +1 -1
  111. package/dist/render/shaders/generated/arcane.wgsl.js +1 -1
  112. package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
  113. package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
  114. package/dist/render/shaders/generated/caustics.wgsl.d.ts +1 -1
  115. package/dist/render/shaders/generated/caustics.wgsl.js +1 -1
  116. package/dist/render/shaders/generated/depth.wgsl.d.ts +4 -4
  117. package/dist/render/shaders/generated/depth.wgsl.js +4 -4
  118. package/dist/render/shaders/generated/film.wgsl.d.ts +1 -1
  119. package/dist/render/shaders/generated/film.wgsl.js +1 -1
  120. package/dist/render/shaders/generated/fire.wgsl.d.ts +1 -1
  121. package/dist/render/shaders/generated/fire.wgsl.js +1 -1
  122. package/dist/render/shaders/generated/flat.wgsl.d.ts +192 -16
  123. package/dist/render/shaders/generated/flat.wgsl.js +230 -54
  124. package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
  125. package/dist/render/shaders/generated/line.wgsl.js +1 -1
  126. package/dist/render/shaders/generated/particle.wgsl.d.ts +4 -4
  127. package/dist/render/shaders/generated/particle.wgsl.js +4 -4
  128. package/dist/render/shaders/generated/rush.wgsl.d.ts +56 -26
  129. package/dist/render/shaders/generated/rush.wgsl.js +56 -26
  130. package/dist/render/shaders/generated/scatter.wgsl.d.ts +1 -1
  131. package/dist/render/shaders/generated/scatter.wgsl.js +1 -1
  132. package/dist/render/shaders/generated/smoke.wgsl.d.ts +1 -1
  133. package/dist/render/shaders/generated/smoke.wgsl.js +1 -1
  134. package/dist/render/shaders/generated/ssrResolve.wgsl.d.ts +29 -0
  135. package/dist/render/shaders/generated/ssrResolve.wgsl.js +29 -0
  136. package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +32 -7
  137. package/dist/render/shaders/generated/ssrTrace.wgsl.js +32 -7
  138. package/dist/render/shaders/generated/water.wgsl.d.ts +23 -16
  139. package/dist/render/shaders/generated/water.wgsl.js +23 -16
  140. package/dist/render/shaders/gi/probeBake.wgsl.js +9 -5
  141. package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +2 -2
  142. package/dist/render/shaders/gpudriven/fog.wgsl.js +11 -3
  143. package/dist/render/shaders/line.d.ts +1 -1
  144. package/dist/render/shaders/particle.d.ts +3 -3
  145. package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
  146. package/dist/render/shaders/recon/motion.wgsl.js +13 -2
  147. package/dist/render/shaders/rush.js +54 -11
  148. package/dist/render/shaders/scatter.d.ts +1 -1
  149. package/dist/render/shaders/smoke.d.ts +1 -1
  150. package/dist/render/shaders/ssrResolve.d.ts +17 -0
  151. package/dist/render/shaders/ssrResolve.js +51 -0
  152. package/dist/render/shaders/ssrTrace.js +46 -1
  153. package/dist/render/shaders/water.d.ts +1 -1
  154. package/dist/render/shaders/water.js +12 -3
  155. package/dist/render/skinScatter.d.ts +3 -1
  156. package/dist/render/skinScatter.js +3 -1
  157. package/dist/render/ssrPass.d.ts +16 -0
  158. package/dist/render/ssrPass.js +107 -1
  159. package/dist/render/surfaceModel.d.ts +29 -8
  160. package/dist/render/surfaceModel.js +33 -9
  161. package/dist/render/surfaceOverlay.d.ts +132 -0
  162. package/dist/render/surfaceOverlay.js +85 -0
  163. package/dist/render/surfaceTexture.d.ts +44 -1
  164. package/dist/render/surfaceTexture.js +96 -5
  165. package/dist/render/temporalAa.d.ts +14 -0
  166. package/dist/render/temporalAa.js +20 -0
  167. package/dist/render/vertexDefaults.d.ts +17 -0
  168. package/dist/render/vertexDefaults.js +24 -0
  169. package/package.json +6 -6
  170. package/src/index.ts +22 -2
  171. package/src/render/anisotropicLobe.ts +29 -0
  172. package/src/render/areaLights.ts +58 -0
  173. package/src/render/atmosphere.ts +22 -1
  174. package/src/render/backend/api.ts +58 -2
  175. package/src/render/backend/createRenderer.ts +18 -1
  176. package/src/render/backend/webgl2/renderer.ts +548 -50
  177. package/src/render/backend/webgl2/sceneCaptureTarget.ts +130 -0
  178. package/src/render/backend/webgpu/device.ts +42 -3
  179. package/src/render/backend/webgpu/flatPass.ts +63 -26
  180. package/src/render/backend/webgpu/particlePass.ts +2 -1
  181. package/src/render/backend/webgpu/postPass.ts +18 -0
  182. package/src/render/backend/webgpu/reflectionSurfacePass.ts +114 -0
  183. package/src/render/backend/webgpu/renderer.ts +927 -100
  184. package/src/render/backend/webgpu/sceneCaptureTarget.ts +63 -0
  185. package/src/render/backend/webgpu/surfaceTexturePass.ts +44 -14
  186. package/src/render/clusteredLights.ts +41 -8
  187. package/src/render/displayRange.ts +61 -0
  188. package/src/render/driftLight/bake.ts +3 -1
  189. package/src/render/driftLight/falloff.ts +13 -2
  190. package/src/render/filmicCurve.ts +229 -0
  191. package/src/render/fog.ts +19 -3
  192. package/src/render/frame/resources.ts +6 -0
  193. package/src/render/frameReflections.ts +72 -0
  194. package/src/render/gi/bounceLights.ts +12 -3
  195. package/src/render/instanceCull.ts +4 -0
  196. package/src/render/instances.ts +26 -6
  197. package/src/render/lightBudget.ts +40 -2
  198. package/src/render/lightmap.ts +64 -23
  199. package/src/render/particlePool.ts +23 -0
  200. package/src/render/pass.ts +53 -1
  201. package/src/render/pointLightSelection.ts +20 -1
  202. package/src/render/reflectionSurfaceTargets.ts +105 -0
  203. package/src/render/renderQuality.ts +52 -15
  204. package/src/render/rendererHarness.ts +3 -0
  205. package/src/render/rgb9e5.ts +52 -0
  206. package/src/render/sceneCapture.ts +56 -0
  207. package/src/render/sceneTarget.ts +30 -2
  208. package/src/render/screenSpaceReflection.ts +38 -1
  209. package/src/render/shaders/bolt.ts +7 -1
  210. package/src/render/shaders/depth.ts +11 -0
  211. package/src/render/shaders/filmLook.ts +2 -1
  212. package/src/render/shaders/flat/anisotropic.ts +37 -3
  213. package/src/render/shaders/flat/areaLight.ts +46 -0
  214. package/src/render/shaders/flat/glassTint.ts +13 -2
  215. package/src/render/shaders/flat/index.ts +37 -2
  216. package/src/render/shaders/flat/lobes.ts +50 -43
  217. package/src/render/shaders/flat/main.ts +85 -22
  218. package/src/render/shaders/flat/models.ts +100 -16
  219. package/src/render/shaders/flat/overlay.ts +117 -0
  220. package/src/render/shaders/flat/pointShadow.ts +1 -1
  221. package/src/render/shaders/flat/preamble.ts +10 -3
  222. package/src/render/shaders/flat/reflectionSurface.ts +61 -0
  223. package/src/render/shaders/fog.ts +9 -2
  224. package/src/render/shaders/fringe.ts +59 -0
  225. package/src/render/shaders/generated/arcane.wgsl.ts +1 -1
  226. package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
  227. package/src/render/shaders/generated/caustics.wgsl.ts +1 -1
  228. package/src/render/shaders/generated/depth.wgsl.ts +4 -4
  229. package/src/render/shaders/generated/film.wgsl.ts +1 -1
  230. package/src/render/shaders/generated/fire.wgsl.ts +1 -1
  231. package/src/render/shaders/generated/flat.wgsl.ts +230 -54
  232. package/src/render/shaders/generated/line.wgsl.ts +1 -1
  233. package/src/render/shaders/generated/particle.wgsl.ts +4 -4
  234. package/src/render/shaders/generated/rush.wgsl.ts +56 -26
  235. package/src/render/shaders/generated/scatter.wgsl.ts +1 -1
  236. package/src/render/shaders/generated/smoke.wgsl.ts +1 -1
  237. package/src/render/shaders/generated/ssrResolve.wgsl.ts +30 -0
  238. package/src/render/shaders/generated/ssrTrace.wgsl.ts +32 -7
  239. package/src/render/shaders/generated/water.wgsl.ts +23 -16
  240. package/src/render/shaders/gi/probeBake.wgsl.ts +9 -5
  241. package/src/render/shaders/gpudriven/fog.wgsl.ts +11 -3
  242. package/src/render/shaders/recon/motion.wgsl.ts +13 -2
  243. package/src/render/shaders/rush.ts +54 -11
  244. package/src/render/shaders/ssrResolve.ts +52 -0
  245. package/src/render/shaders/ssrTrace.ts +46 -1
  246. package/src/render/shaders/water.ts +12 -3
  247. package/src/render/skinScatter.ts +3 -1
  248. package/src/render/ssrPass.ts +139 -1
  249. package/src/render/surfaceModel.ts +47 -10
  250. package/src/render/surfaceOverlay.ts +195 -0
  251. package/src/render/surfaceTexture.ts +136 -9
  252. package/src/render/temporalAa.ts +20 -0
  253. package/src/render/vertexDefaults.ts +28 -0
@@ -0,0 +1,130 @@
1
+ import { glSceneDepthFormat } from '../../depthConvention.ts';
2
+ import type { SurfaceTexture } from '../../surfaceTexture.ts';
3
+
4
+ /**
5
+ * A scene capture's framebuffers on WebGL2: the capture's own texture as the colour a pass resolves
6
+ * into, and a depth of its own, at the capture's size. See `sceneCapture.ts` for what a capture is.
7
+ *
8
+ * **At the frame's sample count, as WebGPU's is**, so a capture's edges are antialiased exactly as
9
+ * the frame's are: drawn into multisampled renderbuffers and blitted into the texture at the end,
10
+ * since a texture layer cannot be multisampled and sampled both. A count the driver refuses falls
11
+ * back to one, as the scene target's does, rather than drawing nothing.
12
+ */
13
+ export class GlSceneCaptureTarget {
14
+ /** Where the capture resolves: its texture's layer, and a depth when it is drawn into directly. */
15
+ private readonly resolveFramebuffer: WebGLFramebuffer;
16
+ private readonly resolveDepth: WebGLRenderbuffer;
17
+ /** Where it is drawn above one sample, or null. */
18
+ private multisampleFramebuffer: WebGLFramebuffer | null = null;
19
+ private multisampleColor: WebGLRenderbuffer | null = null;
20
+ private multisampleDepth: WebGLRenderbuffer | null = null;
21
+ /** False where the driver refused the attachment, said once by the renderer. */
22
+ readonly usable: boolean;
23
+
24
+ constructor(
25
+ gl: WebGL2RenderingContext,
26
+ texture: SurfaceTexture,
27
+ readonly width: number,
28
+ readonly height: number,
29
+ samples: number,
30
+ float: boolean,
31
+ ) {
32
+ const depthFormat = glSceneDepthFormat(gl).internalFormat;
33
+ this.resolveFramebuffer = must(gl.createFramebuffer());
34
+ this.resolveDepth = must(gl.createRenderbuffer());
35
+ gl.bindRenderbuffer(gl.RENDERBUFFER, this.resolveDepth);
36
+ gl.renderbufferStorage(gl.RENDERBUFFER, depthFormat, width, height);
37
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.resolveFramebuffer);
38
+ texture.attachColor(gl);
39
+ gl.framebufferRenderbuffer(
40
+ gl.FRAMEBUFFER,
41
+ gl.DEPTH_ATTACHMENT,
42
+ gl.RENDERBUFFER,
43
+ this.resolveDepth,
44
+ );
45
+ this.usable = gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE;
46
+
47
+ const maximum = gl.getParameter(gl.MAX_SAMPLES) as number;
48
+ const count = Math.max(
49
+ 1,
50
+ Math.min(Math.round(samples), Number.isFinite(maximum) ? maximum : 1),
51
+ );
52
+ if (this.usable && count > 1) {
53
+ const color = must(gl.createRenderbuffer());
54
+ const depth = must(gl.createRenderbuffer());
55
+ gl.bindRenderbuffer(gl.RENDERBUFFER, color);
56
+ gl.renderbufferStorageMultisample(
57
+ gl.RENDERBUFFER,
58
+ count,
59
+ float ? gl.RGBA16F : gl.RGBA8,
60
+ width,
61
+ height,
62
+ );
63
+ gl.bindRenderbuffer(gl.RENDERBUFFER, depth);
64
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, count, depthFormat, width, height);
65
+ const framebuffer = must(gl.createFramebuffer());
66
+ gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
67
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, color);
68
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depth);
69
+ if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE) {
70
+ this.multisampleFramebuffer = framebuffer;
71
+ this.multisampleColor = color;
72
+ this.multisampleDepth = depth;
73
+ } else {
74
+ gl.deleteFramebuffer(framebuffer);
75
+ gl.deleteRenderbuffer(color);
76
+ gl.deleteRenderbuffer(depth);
77
+ }
78
+ }
79
+ gl.bindRenderbuffer(gl.RENDERBUFFER, null);
80
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
81
+ }
82
+
83
+ /** Bind where the pass draws, size the viewport and clear: colour to `clear`, depth to `depthClear`. */
84
+ begin(
85
+ gl: WebGL2RenderingContext,
86
+ clear: readonly [number, number, number],
87
+ depthClear: number,
88
+ ): void {
89
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.multisampleFramebuffer ?? this.resolveFramebuffer);
90
+ gl.viewport(0, 0, this.width, this.height);
91
+ gl.clearColor(clear[0], clear[1], clear[2], 1);
92
+ gl.clearDepth(depthClear);
93
+ gl.depthMask(true);
94
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
95
+ }
96
+
97
+ /** Resolve the multisampled colour into the texture, where there is any. */
98
+ end(gl: WebGL2RenderingContext): void {
99
+ if (this.multisampleFramebuffer === null) return;
100
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.multisampleFramebuffer);
101
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.resolveFramebuffer);
102
+ gl.blitFramebuffer(
103
+ 0,
104
+ 0,
105
+ this.width,
106
+ this.height,
107
+ 0,
108
+ 0,
109
+ this.width,
110
+ this.height,
111
+ gl.COLOR_BUFFER_BIT,
112
+ gl.NEAREST,
113
+ );
114
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
115
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
116
+ }
117
+
118
+ dispose(gl: WebGL2RenderingContext): void {
119
+ gl.deleteFramebuffer(this.resolveFramebuffer);
120
+ gl.deleteRenderbuffer(this.resolveDepth);
121
+ if (this.multisampleFramebuffer !== null) gl.deleteFramebuffer(this.multisampleFramebuffer);
122
+ if (this.multisampleColor !== null) gl.deleteRenderbuffer(this.multisampleColor);
123
+ if (this.multisampleDepth !== null) gl.deleteRenderbuffer(this.multisampleDepth);
124
+ }
125
+ }
126
+
127
+ function must<T>(made: T | null): T {
128
+ if (made === null) throw new Error('GlSceneCaptureTarget: a GL object could not be created');
129
+ return made;
130
+ }
@@ -7,6 +7,14 @@
7
7
  * reset the page did not cause, and a frame is wrong because of something the frame did.
8
8
  */
9
9
 
10
+ import type { DisplayRange, DisplayRangeChoice } from '../../displayRange.ts';
11
+
12
+ /** A canvas nobody asked to extend. */
13
+ const STANDARD_RANGE: DisplayRangeChoice = {
14
+ range: 'standard',
15
+ reason: 'highDynamicRange was not asked for',
16
+ };
17
+
10
18
  /** What the renderer needs from the canvas it draws into, with the API's shape absorbed. */
11
19
  export interface GpuSurface {
12
20
  readonly device: GPUDevice;
@@ -22,6 +30,9 @@ export interface GpuSurface {
22
30
  readonly canvas: HTMLCanvasElement;
23
31
  /** The swap chain's format, which every render pipeline has to be built against. */
24
32
  readonly format: GPUTextureFormat;
33
+ /** The range the canvas was configured in, and why. See `displayRange.ts`. */
34
+ readonly displayRange: DisplayRange;
35
+ readonly displayRangeReason: string;
25
36
  /** Whether the device has gone. Read per frame; never throws. */
26
37
  readonly lost: boolean;
27
38
  /** Called when the device is lost. Fires immediately if it already has been. */
@@ -51,13 +62,27 @@ export interface GpuSurface {
51
62
  * consumer its GPU had failed every time it unmounted a canvas on purpose, so a disposed
52
63
  * surface stays silent.
53
64
  */
54
- export function createGpuSurface(canvas: HTMLCanvasElement, device: GPUDevice): GpuSurface {
65
+ export function createGpuSurface(
66
+ canvas: HTMLCanvasElement,
67
+ device: GPUDevice,
68
+ /** The range the profile and the display allow (`chooseDisplayRange`); standard unless given. */
69
+ wanted: DisplayRangeChoice = STANDARD_RANGE,
70
+ ): GpuSurface {
55
71
  const context = canvas.getContext('webgpu') as GPUCanvasContext | null;
56
72
  if (context === null) {
57
73
  throw new Error('createGpuSurface: the canvas would not give a webgpu context');
58
74
  }
59
75
 
60
- const format = navigator.gpu.getPreferredCanvasFormat();
76
+ /*
77
+ * **A high range is half floats with extended tone mapping**: a value of 1 is the display's
78
+ * paper white, and what lies above it is shown up to the display's peak instead of clipped. The
79
+ * colour space stays sRGB, so the composite's encoding is the one it always wrote, carried past 1.
80
+ */
81
+ const extended = wanted.range === 'high';
82
+ const format: GPUTextureFormat = extended
83
+ ? 'rgba16float'
84
+ : navigator.gpu.getPreferredCanvasFormat();
85
+ let range: DisplayRangeChoice = wanted;
61
86
  const listeners: (() => void)[] = [];
62
87
  let lost = false;
63
88
  let disposed = false;
@@ -115,16 +140,30 @@ export function createGpuSurface(canvas: HTMLCanvasElement, device: GPUDevice):
115
140
  * has no use for a transparent canvas, and the opaque path is the one a compositor can
116
141
  * take without a blend.
117
142
  */
118
- context.configure({ device, format, alphaMode: 'opaque' });
143
+ context.configure(
144
+ extended
145
+ ? { device, format, alphaMode: 'opaque', toneMapping: { mode: 'extended' } }
146
+ : { device, format, alphaMode: 'opaque' },
147
+ );
119
148
  };
120
149
 
121
150
  configure(canvas.width, canvas.height);
151
+ /*
152
+ * **Read back, because a browser that does not know the option ignores it** and draws the
153
+ * standard range from an extended-range frame, clipped at paper white, which is the frame a game
154
+ * would grade for the standard range if it knew. So it is told.
155
+ */
156
+ if (extended && context.getConfiguration?.()?.toneMapping?.mode !== 'extended') {
157
+ range = { range: 'standard', reason: 'this browser has no extended-range canvas' };
158
+ }
122
159
 
123
160
  return {
124
161
  device,
125
162
  context,
126
163
  canvas,
127
164
  format,
165
+ displayRange: range.range,
166
+ displayRangeReason: range.reason,
128
167
  get lost() {
129
168
  return lost;
130
169
  },
@@ -12,6 +12,7 @@ import type { LitSwitch, PipelineCache } from './pipelineCache.ts';
12
12
  import type { ClothViews } from './clothTextures.ts';
13
13
  import { shaderModule } from './shaderModules.ts';
14
14
  import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.ts';
15
+ import { REFLECTION_SURFACE_FORMAT } from './reflectionSurfacePass.ts';
15
16
 
16
17
  /** The material maps the flat pass reads: `2d-array` colour, filtered, not shadow maps. */
17
18
  const SURFACE_MAPS: ReadonlySet<string> = new Set([
@@ -232,6 +233,26 @@ export const FLAT_VERT_SIZE = Math.max(
232
233
  );
233
234
  const FLAT_VERT_BINDING = PLAIN_VERTEX.uniforms;
234
235
 
236
+ /**
237
+ * The most sampled textures and distinct samplers any lit variant carrying the environment probe
238
+ * binds in its fragment stage, counted from the generated bindings: what a device must allow per
239
+ * stage before the probe's permutation can be built. A sampler several textures share counts once,
240
+ * which is what `// wgsl:share` buys.
241
+ */
242
+ export function probeStageCeilings(): { readonly textures: number; readonly samplers: number } {
243
+ let textures = 0;
244
+ let samplers = 0;
245
+ for (const [variant, bindings] of Object.entries(FLAT_BINDINGS.flatFrag)) {
246
+ if (!variant.includes('environmentProbe')) continue;
247
+ const listed = Object.values(
248
+ (bindings as { textures?: Record<string, { sampler: number }> }).textures ?? {},
249
+ );
250
+ textures = Math.max(textures, listed.length);
251
+ samplers = Math.max(samplers, new Set(listed.map((entry) => entry.sampler)).size);
252
+ }
253
+ return { textures, samplers };
254
+ }
255
+
235
256
  /** Everything the generator recorded about one permutation of the fragment stage. */
236
257
  export function flatFragmentBindings(variant: FlatVariant): StageBindings {
237
258
  const found = FRAGMENT_BINDINGS[variant];
@@ -253,6 +274,7 @@ function flatFragmentConstants(
253
274
  cache: PipelineCache,
254
275
  model: SurfaceModelKind | null,
255
276
  skinPass: SkinHalf = 'whole',
277
+ reflectionSurface = false,
256
278
  ): Record<string, number> {
257
279
  const overrides = flatFragmentBindings(variant).overrides;
258
280
  if (overrides?.['GLASS_SHADOWS'] === undefined) {
@@ -271,6 +293,11 @@ function flatFragmentConstants(
271
293
  constants[String(id)] = skinPass !== 'whole' && name === SKIN_HALF_SWITCH[skinPass] ? 1 : 0;
272
294
  continue;
273
295
  }
296
+ /* The reflection pass's surface half, set from the key too. See `reflectionSurface.ts`. */
297
+ if (name === 'REFLECTION_SURFACE') {
298
+ constants[String(id)] = reflectionSurface ? 1 : 0;
299
+ continue;
300
+ }
274
301
  if (!(name in cache.litSwitches)) {
275
302
  throw new Error(`flatPass: variant "${variant}" declares a switch "${name}" no cache sets`);
276
303
  }
@@ -609,6 +636,11 @@ export function flatPipeline(
609
636
  model: SurfaceModelKind | null = null,
610
637
  /** Which half of a skin it draws. The key must carry it: `|ss`, `|sd` or `|sa`. See `SkinHalf`. */
611
638
  skinPass: SkinHalf = 'whole',
639
+ /**
640
+ * The reflection pass's surface half: the draw again, against its own depth, into the two
641
+ * targets `reflectionSurfacePass.ts` holds. The key must carry it: `|rs`.
642
+ */
643
+ reflectionSurface = false,
612
644
  ): GPURenderPipeline {
613
645
  return cache.get(
614
646
  key,
@@ -633,6 +665,7 @@ export function flatPipeline(
633
665
  cloth,
634
666
  model,
635
667
  skinPass,
668
+ reflectionSurface,
636
669
  ),
637
670
  true,
638
671
  );
@@ -765,10 +798,12 @@ function flatDescriptor(
765
798
  cloth = false,
766
799
  model: SurfaceModelKind | null = null,
767
800
  skinPass: SkinHalf = 'whole',
801
+ reflectionSurface = false,
768
802
  ): GPURenderPipelineDescriptor {
769
803
  const offset = depthOffsetForLayer(depthLayer);
770
- /* The diffuse and colour halves are the same surface again, into the blur's targets. */
771
- const diffuseAlone = skinPass === 'diffuse' || skinPass === 'albedo';
804
+ /* The diffuse and colour halves are the same surface again, into the blur's targets; and so is
805
+ the reflection pass's surface half, into its two. */
806
+ const diffuseAlone = skinPass === 'diffuse' || skinPass === 'albedo' || reflectionSurface;
772
807
  return {
773
808
  label: key,
774
809
  layout: device.createPipelineLayout({ bindGroupLayouts: [layout] }),
@@ -798,31 +833,33 @@ function flatDescriptor(
798
833
  }),
799
834
  entryPoint: 'main',
800
835
  /* Every variant declares it, so every lit pipeline may set it: see `PipelineCache`. */
801
- constants: flatFragmentConstants(variant, cache, model, skinPass),
802
- targets: [
803
- oitTarget(oit) ??
804
- (translucent === 'additive'
805
- ? {
806
- format: cache.format,
807
- blend: {
808
- color: { srcFactor: 'src-alpha', dstFactor: 'one', operation: 'add' },
809
- alpha: { srcFactor: 'zero', dstFactor: 'one', operation: 'add' },
810
- },
811
- }
812
- : translucent
813
- ? {
814
- format: cache.format,
815
- blend: {
816
- color: {
817
- srcFactor: 'src-alpha',
818
- dstFactor: 'one-minus-src-alpha',
819
- operation: 'add',
836
+ constants: flatFragmentConstants(variant, cache, model, skinPass, reflectionSurface),
837
+ targets: reflectionSurface
838
+ ? [null, { format: REFLECTION_SURFACE_FORMAT }, { format: REFLECTION_SURFACE_FORMAT }]
839
+ : [
840
+ oitTarget(oit) ??
841
+ (translucent === 'additive'
842
+ ? {
843
+ format: cache.format,
844
+ blend: {
845
+ color: { srcFactor: 'src-alpha', dstFactor: 'one', operation: 'add' },
846
+ alpha: { srcFactor: 'zero', dstFactor: 'one', operation: 'add' },
820
847
  },
821
- alpha: SCENE_ALPHA_COVERS,
822
- },
823
- }
824
- : { format: cache.format }),
825
- ],
848
+ }
849
+ : translucent
850
+ ? {
851
+ format: cache.format,
852
+ blend: {
853
+ color: {
854
+ srcFactor: 'src-alpha',
855
+ dstFactor: 'one-minus-src-alpha',
856
+ operation: 'add',
857
+ },
858
+ alpha: SCENE_ALPHA_COVERS,
859
+ },
860
+ }
861
+ : { format: cache.format }),
862
+ ],
826
863
  },
827
864
  primitive: { topology: 'triangle-list', cullMode: doubleSided ? 'none' : 'back' },
828
865
  /* Coverage only where a sample count can carry it: WebGPU refuses it at one sample. */
@@ -3,6 +3,7 @@ import { particleShaders, type ParticleMaterial } from '../../particleMaterial.t
3
3
  import { PARTICLE_BINDINGS } from '../../shaders/generated/particle.wgsl.ts';
4
4
  import { PARTICLE_BLADES, PARTICLE_INDICES, PARTICLE_VERTS } from '../../shaders/particle.ts';
5
5
  import type { ParticleInstances } from '../../particlePool.ts';
6
+ import { DEVICE_PARTICLE_FLOATS } from '../../particlePool.ts';
6
7
  import { packSpriteStream, type ParticleBlend, type ParticleFacing } from '../../particleBatch.ts';
7
8
  import { createParticleSort, type ParticleSort } from '../../particleSort.ts';
8
9
  import { DEPTH_FORMAT } from './flatPass.ts';
@@ -58,7 +59,7 @@ const USAGE_UNIFORM_DST = 0x0040 | 0x0008;
58
59
  * Floats per instance, in the order they are packed: position 3, size 1, spin 1, colour 3,
59
60
  * alpha 1, age 1, seed 1, velocity 3, and a sprite's frame and half-height 2.
60
61
  */
61
- const INSTANCE_FLOATS = 16;
62
+ const INSTANCE_FLOATS = DEVICE_PARTICLE_FLOATS;
62
63
 
63
64
  const INSTANCE_ATTRIBUTES: GPUVertexAttribute[] = [
64
65
  { shaderLocation: 2, offset: 0, format: 'float32x3' },
@@ -29,6 +29,7 @@ import {
29
29
  } from '../../shaders/generated/ssrTrace.wgsl.ts';
30
30
  import {
31
31
  SSRRESOLVE_BINDINGS as SSR_RESOLVE_BINDINGS,
32
+ SSR_MATERIAL_RESOLVE_FRAG_WGSL,
32
33
  SSR_RESOLVE_FRAG_WGSL,
33
34
  } from '../../shaders/generated/ssrResolve.wgsl.ts';
34
35
  import {
@@ -331,6 +332,16 @@ export const REFLECTION_OVER_BLEND: GPUBlendState = {
331
332
  alpha: SCENE_ALPHA_KEEPS,
332
333
  };
333
334
 
335
+ /**
336
+ * The frame's materials' reflection, added: the found colour times the tint, less the probe's share,
337
+ * which is negative where the probe was brighter than what replaced it. A half-float scene takes
338
+ * the negative addend as the subtraction it is. Its alpha kept.
339
+ */
340
+ export const FRAME_REFLECTION_BLEND: GPUBlendState = {
341
+ color: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
342
+ alpha: SCENE_ALPHA_KEEPS,
343
+ };
344
+
334
345
  /**
335
346
  * The bloom pyramid's way back up: an octave added onto a level that the blend multiplies by that
336
347
  * level's tint first — the blend constant, which white makes `one, one` exactly. Into the pyramid's
@@ -450,6 +461,12 @@ export const SSR_FRAG_SIZE = SSR_BINDINGS.SSR_TRACE_FRAG.uniformSize;
450
461
  export const SSR_UNIFORMS = SSR_BINDINGS.SSR_TRACE_FRAG.uniforms;
451
462
  export const SSR_TEXTURES = SSR_BINDINGS.SSR_TRACE_FRAG.textures;
452
463
  export const SSR_RESOLVE_TEXTURES = SSR_RESOLVE_BINDINGS.SSR_RESOLVE_FRAG.textures;
464
+ /** The material resolve's block — the blur — and its three inputs. See `frameReflections.ts`. */
465
+ export const SSR_MATERIAL_FIELDS: UniformFields =
466
+ SSR_RESOLVE_BINDINGS.SSR_MATERIAL_RESOLVE_FRAG.fields;
467
+ export const SSR_MATERIAL_SIZE = SSR_RESOLVE_BINDINGS.SSR_MATERIAL_RESOLVE_FRAG.uniformSize;
468
+ export const SSR_MATERIAL_UNIFORMS = SSR_RESOLVE_BINDINGS.SSR_MATERIAL_RESOLVE_FRAG.uniforms;
469
+ export const SSR_MATERIAL_TEXTURES = SSR_RESOLVE_BINDINGS.SSR_MATERIAL_RESOLVE_FRAG.textures;
453
470
 
454
471
  /**
455
472
  * The resolve's layout: one texture and no uniform block.
@@ -513,6 +530,7 @@ export {
513
530
  DECAL_PROJECT_FRAG_WGSL,
514
531
  SSR_TRACE_FRAG_WGSL,
515
532
  SSR_RESOLVE_FRAG_WGSL,
533
+ SSR_MATERIAL_RESOLVE_FRAG_WGSL,
516
534
  };
517
535
  export { BLOOM_PREFILTER_FRAG_WGSL, BLOOM_DOWNSAMPLE_FRAG_WGSL, BLOOM_UPSAMPLE_FRAG_WGSL };
518
536
  export { RUSH_UNIFORMS, RUSH_TEXTURES };
@@ -0,0 +1,114 @@
1
+ /**
2
+ * The surface half of a reflection traced through the frame's materials, on this backend: the
3
+ * frame's opaque lit draws again, against the depth they wrote, into two half-float targets — the
4
+ * environment as the frame shows it with the surface's roughness, and what a reflection found in the
5
+ * frame is multiplied by to land in its place. `shaders/flat/reflectionSurface.ts` is what the two
6
+ * hold and why the swap is exact; this is where they are drawn.
7
+ *
8
+ * **Why a second pass and not a second output of the frame's.** Every pipeline drawn into the
9
+ * frame's pass would have to declare a target it never writes — the sky, water, particles, text and
10
+ * every pass a consumer registers — so the frame keeps its one attachment, and the lit stage's two
11
+ * extra outputs land nowhere there. Here they land: the same pipelines with `REFLECTION_SURFACE` on,
12
+ * which leaves before the lamps, the same bind groups and ring slots, tested `equal` against the
13
+ * frame's depth so only what the frame kept writes.
14
+ *
15
+ * **The draws are commands, kept the way the skin's halves are** (`skinScatterPass.ts`): the frame
16
+ * graph's pool is spent at every flush, and these are replayed after the flush that ends the frame's
17
+ * pass. Each holds the ring slots its frame draw used, which stay valid until the frame's last
18
+ * submit, after this has run.
19
+ *
20
+ * **What it costs**: every opaque lit draw twice, the second without its lamps; two half-float
21
+ * targets the size of the frame. Single-sampled only, as the trace that reads them is.
22
+ */
23
+ import { createCommandPool, resetPool, takeCommand } from './drawCommand.ts';
24
+ import type { CommandPool, DrawCommand } from './drawCommand.ts';
25
+
26
+ /** Both targets' format: linear light and a tint, which an eight-bit target would clip and band. */
27
+ export const REFLECTION_SURFACE_FORMAT: GPUTextureFormat = 'rgba16float';
28
+
29
+ const USAGE_TARGET = 0x10 | 0x4; // RENDER_ATTACHMENT | TEXTURE_BINDING
30
+
31
+ export class ReflectionSurfacePass {
32
+ private readonly device: GPUDevice;
33
+ private readonly commands: CommandPool = createCommandPool(256);
34
+ private probe: GPUTexture | null = null;
35
+ private tint: GPUTexture | null = null;
36
+ /** The environment as the frame shows it, and the roughness in alpha. */
37
+ probeView: GPUTextureView | null = null;
38
+ /** What a found reflection is multiplied by. */
39
+ tintView: GPUTextureView | null = null;
40
+
41
+ constructor(device: GPUDevice) {
42
+ this.device = device;
43
+ }
44
+
45
+ /** A command for one opaque lit draw's surface half, filled by the caller. */
46
+ take(): DrawCommand {
47
+ return this.commands.commands[takeCommand(this.commands)] as DrawCommand;
48
+ }
49
+
50
+ /** How many draws the frame kept. */
51
+ get kept(): number {
52
+ return this.commands.taken;
53
+ }
54
+
55
+ /** A new frame keeps nothing yet. */
56
+ reset(): void {
57
+ resetPool(this.commands);
58
+ }
59
+
60
+ /** The two targets at the frame's size, made again only when it changes. */
61
+ size(width: number, height: number): void {
62
+ if (this.probe !== null && this.probe.width === width && this.probe.height === height) return;
63
+ this.release();
64
+ const make = (label: string): GPUTexture =>
65
+ this.device.createTexture({
66
+ label,
67
+ size: [width, height],
68
+ format: REFLECTION_SURFACE_FORMAT,
69
+ usage: USAGE_TARGET,
70
+ });
71
+ this.probe = make('reflection.probe');
72
+ this.tint = make('reflection.tint');
73
+ this.probeView = this.probe.createView();
74
+ this.tintView = this.tint.createView();
75
+ }
76
+
77
+ /**
78
+ * Every kept draw into the two targets against the frame's depth, which it reads and does not
79
+ * write; both cleared first, so a pixel no lit surface reached reflects nothing. Called with no
80
+ * pass open on `encoder`, and drawn even with nothing kept, since the clear is what the trace
81
+ * reads there.
82
+ */
83
+ draw(
84
+ encoder: GPUCommandEncoder,
85
+ depth: GPUTextureView,
86
+ issue: (pass: GPURenderPassEncoder, command: DrawCommand) => void,
87
+ ): void {
88
+ const probe = this.probeView;
89
+ const tint = this.tintView;
90
+ if (probe === null || tint === null) return;
91
+ const pass = encoder.beginRenderPass({
92
+ label: 'reflection.surface',
93
+ colorAttachments: [
94
+ null,
95
+ { view: probe, clearValue: [0, 0, 0, 1], loadOp: 'clear', storeOp: 'store' },
96
+ { view: tint, clearValue: [0, 0, 0, 0], loadOp: 'clear', storeOp: 'store' },
97
+ ],
98
+ depthStencilAttachment: { view: depth, depthReadOnly: true },
99
+ });
100
+ for (let i = 0; i < this.commands.taken; i += 1) {
101
+ issue(pass, this.commands.commands[i] as DrawCommand);
102
+ }
103
+ pass.end();
104
+ }
105
+
106
+ release(): void {
107
+ this.probe?.destroy();
108
+ this.tint?.destroy();
109
+ this.probe = null;
110
+ this.tint = null;
111
+ this.probeView = null;
112
+ this.tintView = null;
113
+ }
114
+ }