@downdraft/engine 0.0.0 → 0.1.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 (908) hide show
  1. package/README.md +33 -0
  2. package/app/src/index.ts +10 -0
  3. package/app/src/renderer/bootstrap.ts +271 -0
  4. package/app/src/renderer/downdraft-base.css +93 -0
  5. package/app/src/renderer/feature-log.ts +200 -0
  6. package/app/src/renderer/game-module.ts +923 -0
  7. package/app/src/renderer/hooks.ts +88 -0
  8. package/app/src/renderer/hot-reload.ts +178 -0
  9. package/app/src/renderer/import-cache.ts +88 -0
  10. package/app/src/renderer/index.ts +206 -0
  11. package/app/src/renderer/mcp-harness.ts +144 -0
  12. package/app/src/renderer/save-store-factory.ts +239 -0
  13. package/app/src/renderer/sim-bridge.ts +183 -0
  14. package/app/src/renderer/standard-automation-tools.ts +771 -0
  15. package/app/src/shared/feature-log.ts +71 -0
  16. package/app/src/shared/gpu-info.ts +82 -0
  17. package/app/src/shared/import-cache-store.ts +142 -0
  18. package/app/src/shared/index.ts +6 -0
  19. package/app/src/shared/mcp-discovery.ts +47 -0
  20. package/app/src/shared/mcp-proxy.ts +82 -0
  21. package/app/src/shared/types.ts +324 -0
  22. package/asset-bake/src/bake-audio.ts +119 -0
  23. package/asset-bake/src/bake-gltf.ts +172 -0
  24. package/asset-bake/src/bake.ts +78 -0
  25. package/asset-bake/src/cache.ts +188 -0
  26. package/asset-bake/src/config.ts +186 -0
  27. package/asset-bake/src/index.ts +36 -0
  28. package/core/src/animation/animation-event.ts +41 -0
  29. package/core/src/animation/bone-mask.ts +122 -0
  30. package/core/src/animation/clip.ts +252 -0
  31. package/core/src/animation/index.ts +22 -0
  32. package/core/src/animation/mixamo.ts +113 -0
  33. package/core/src/animation/morph-target.ts +69 -0
  34. package/core/src/animation/player.ts +697 -0
  35. package/core/src/animation/retarget.ts +158 -0
  36. package/core/src/animation/skeleton-animator.ts +1399 -0
  37. package/core/src/animation/skeleton.ts +214 -0
  38. package/core/src/animation/state-machine.ts +280 -0
  39. package/core/src/assets/blob-store.ts +66 -0
  40. package/core/src/assets/cache.ts +186 -0
  41. package/core/src/assets/cubemap-converter.ts +155 -0
  42. package/core/src/assets/environment-manager.ts +121 -0
  43. package/core/src/assets/import-cache.ts +65 -0
  44. package/core/src/assets/import-settings.ts +72 -0
  45. package/core/src/assets/importer.ts +108 -0
  46. package/core/src/assets/index.ts +74 -0
  47. package/core/src/assets/irradiance-generator.ts +198 -0
  48. package/core/src/assets/loader-audio.ts +86 -0
  49. package/core/src/assets/loader-cubemap.ts +211 -0
  50. package/core/src/assets/loader-dds.ts +236 -0
  51. package/core/src/assets/loader-hdr.ts +474 -0
  52. package/core/src/assets/loader-mesh.ts +143 -0
  53. package/core/src/assets/loader-shader.ts +73 -0
  54. package/core/src/assets/loader-texture.ts +349 -0
  55. package/core/src/assets/lod.ts +198 -0
  56. package/core/src/assets/manager.ts +480 -0
  57. package/core/src/assets/manifest.ts +81 -0
  58. package/core/src/assets/material-bridge.ts +186 -0
  59. package/core/src/assets/model-importer.ts +72 -0
  60. package/core/src/assets/model-normalizer.ts +248 -0
  61. package/core/src/assets/model-to-gpu.ts +70 -0
  62. package/core/src/assets/model-to-mesh.ts +237 -0
  63. package/core/src/assets/prefilter-generator.ts +250 -0
  64. package/core/src/audio/engine.ts +177 -0
  65. package/core/src/audio/interface.ts +130 -0
  66. package/core/src/audio/listener.ts +32 -0
  67. package/core/src/audio/mixer.ts +141 -0
  68. package/core/src/audio/registry.ts +52 -0
  69. package/core/src/audio/sab.ts +87 -0
  70. package/core/src/audio/source.ts +89 -0
  71. package/core/src/change-detection/atomics.ts +1 -0
  72. package/core/src/change-detection/tracker.ts +47 -0
  73. package/core/src/debug-draw/lines.ts +64 -0
  74. package/core/src/debug-draw/points.ts +17 -0
  75. package/core/src/debug-draw/queue.ts +124 -0
  76. package/core/src/debug-draw/text.ts +25 -0
  77. package/core/src/ecs/archetype.ts +323 -0
  78. package/core/src/ecs/component.ts +188 -0
  79. package/core/src/ecs/entity.ts +25 -0
  80. package/core/src/ecs/events.ts +93 -0
  81. package/core/src/ecs/hierarchy.ts +124 -0
  82. package/core/src/ecs/hmr-swap-registry.ts +22 -0
  83. package/core/src/ecs/index.ts +25 -0
  84. package/core/src/ecs/query.ts +141 -0
  85. package/core/src/ecs/resource.ts +8 -0
  86. package/core/src/ecs/schedule.ts +181 -0
  87. package/core/src/ecs/system-params.ts +99 -0
  88. package/core/src/ecs/system.ts +42 -0
  89. package/core/src/ecs/world.ts +348 -0
  90. package/core/src/index.ts +352 -0
  91. package/core/src/input/context.ts +73 -0
  92. package/core/src/input/dom-handler.ts +380 -0
  93. package/core/src/input/local-player-manager.ts +218 -0
  94. package/core/src/input/mapping.ts +40 -0
  95. package/core/src/input/multi-sab-bridge.ts +276 -0
  96. package/core/src/input/multi-state.ts +54 -0
  97. package/core/src/input/sab-bridge.ts +71 -0
  98. package/core/src/input/state.ts +132 -0
  99. package/core/src/input/ui-router.ts +87 -0
  100. package/core/src/library/host.ts +317 -0
  101. package/core/src/library/index.ts +16 -0
  102. package/core/src/library/library.ts +292 -0
  103. package/core/src/material/compiler.ts +80 -0
  104. package/core/src/material/graph-bridge.ts +172 -0
  105. package/core/src/material/index.ts +9 -0
  106. package/core/src/material/library.ts +442 -0
  107. package/core/src/material/material.ts +156 -0
  108. package/core/src/material/variants.ts +61 -0
  109. package/core/src/math/mat4.ts +202 -0
  110. package/core/src/math/perlin-noise-2d.ts +77 -0
  111. package/core/src/math/perlin-noise-3d.ts +110 -0
  112. package/core/src/math/quat.ts +33 -0
  113. package/core/src/math/rng.ts +29 -0
  114. package/core/src/math/trig.ts +47 -0
  115. package/core/src/math/value-noise.ts +67 -0
  116. package/core/src/mesh/builder.ts +177 -0
  117. package/core/src/mesh/csg.ts +312 -0
  118. package/core/src/mesh/greased-line.ts +145 -0
  119. package/core/src/mesh/mesh.ts +17 -0
  120. package/core/src/mesh/parametric.ts +501 -0
  121. package/core/src/mesh/skinning.ts +67 -0
  122. package/core/src/mesh/vertex-layout.ts +91 -0
  123. package/core/src/module/cross-thread.ts +163 -0
  124. package/core/src/module/diagnostics.ts +123 -0
  125. package/core/src/module/host.ts +451 -0
  126. package/core/src/module/index.ts +22 -0
  127. package/core/src/module/module.ts +109 -0
  128. package/core/src/module/registry.ts +94 -0
  129. package/core/src/module/renderer-module.ts +247 -0
  130. package/core/src/module/ts-loader.ts +26 -0
  131. package/core/src/particles/compute-pass.ts +378 -0
  132. package/core/src/particles/emitter.ts +156 -0
  133. package/core/src/particles/index.ts +11 -0
  134. package/core/src/particles/particle-data.ts +47 -0
  135. package/core/src/particles/render-pass.ts +365 -0
  136. package/core/src/particles/simulator.ts +264 -0
  137. package/core/src/particles/system.ts +193 -0
  138. package/core/src/physics/body.ts +59 -0
  139. package/core/src/physics/ccd-heuristic.ts +55 -0
  140. package/core/src/physics/character.ts +282 -0
  141. package/core/src/physics/collider.ts +55 -0
  142. package/core/src/physics/collision-system.ts +91 -0
  143. package/core/src/physics/events.ts +133 -0
  144. package/core/src/physics/index.ts +34 -0
  145. package/core/src/physics/interface.ts +473 -0
  146. package/core/src/physics/interpolation-buffer.ts +192 -0
  147. package/core/src/physics/lifecycle.ts +87 -0
  148. package/core/src/physics/load-shedder.ts +164 -0
  149. package/core/src/physics/physics-accumulator.ts +101 -0
  150. package/core/src/physics/physics-system.ts +240 -0
  151. package/core/src/physics/ragdoll-presets.ts +272 -0
  152. package/core/src/physics/ragdoll-system.ts +220 -0
  153. package/core/src/physics/ragdoll.ts +182 -0
  154. package/core/src/physics/raycast.ts +143 -0
  155. package/core/src/physics/realm-manager.ts +499 -0
  156. package/core/src/physics/realm-worker-pool.ts +191 -0
  157. package/core/src/physics/realm-worker.ts +102 -0
  158. package/core/src/physics/realm.ts +189 -0
  159. package/core/src/physics/registry.ts +52 -0
  160. package/core/src/physics/safety.ts +247 -0
  161. package/core/src/physics/snapshot-manager.ts +141 -0
  162. package/core/src/physics/worker-protocol.ts +30 -0
  163. package/core/src/platform/bun-preload.ts +169 -0
  164. package/core/src/platform/css-stub.ts +4 -0
  165. package/core/src/platform/glob-polyfill.ts +124 -0
  166. package/core/src/platform/hidpi.ts +57 -0
  167. package/core/src/platform/render-surface.ts +77 -0
  168. package/core/src/platform/rpc.ts +98 -0
  169. package/core/src/platform/runtime.ts +132 -0
  170. package/core/src/plugin/context.ts +364 -0
  171. package/core/src/plugin/diagnostics.ts +61 -0
  172. package/core/src/plugin/discovery.ts +186 -0
  173. package/core/src/plugin/extension-loaders.ts +172 -0
  174. package/core/src/plugin/host.ts +659 -0
  175. package/core/src/plugin/index.ts +106 -0
  176. package/core/src/plugin/loader-asset.ts +124 -0
  177. package/core/src/plugin/loader-quickjs.ts +84 -0
  178. package/core/src/plugin/loader-wasm.ts +411 -0
  179. package/core/src/plugin/loader-worker.ts +261 -0
  180. package/core/src/plugin/manifest.ts +653 -0
  181. package/core/src/plugin/material-registry.ts +65 -0
  182. package/core/src/plugin/mcp-tools.ts +165 -0
  183. package/core/src/plugin/permissions.ts +175 -0
  184. package/core/src/plugin/quickjs-bridge.ts +294 -0
  185. package/core/src/plugin/registry.ts +107 -0
  186. package/core/src/plugin/sandbox-shim.ts +86 -0
  187. package/core/src/plugin/sandbox-worker.ts +215 -0
  188. package/core/src/plugin/wasm-abi.ts +152 -0
  189. package/core/src/plugin/wasm-worker.ts +229 -0
  190. package/core/src/plugin/workshop.ts +169 -0
  191. package/core/src/profiling/event-loop.ts +180 -0
  192. package/core/src/profiling/index.ts +69 -0
  193. package/core/src/profiling/iops/idb-patch.ts +242 -0
  194. package/core/src/profiling/iops/opfs-patch.ts +276 -0
  195. package/core/src/profiling/profiling-sab.ts +839 -0
  196. package/core/src/profiling/task-latency.ts +134 -0
  197. package/core/src/profiling/thread-metrics.ts +126 -0
  198. package/core/src/profiling/trace-event-writer.ts +205 -0
  199. package/core/src/profiling/warnings.ts +423 -0
  200. package/core/src/profiling/worker-prelude.ts +376 -0
  201. package/core/src/render/bind-group.ts +38 -0
  202. package/core/src/render/bindless/bindless-struct.ts +42 -0
  203. package/core/src/render/bindless/bindless.wgsl.ts +167 -0
  204. package/core/src/render/bindless/frame-bindings.ts +176 -0
  205. package/core/src/render/bindless/index.ts +10 -0
  206. package/core/src/render/bindless/material-manager.ts +257 -0
  207. package/core/src/render/bindless/texture-registry.ts +609 -0
  208. package/core/src/render/buffer.ts +82 -0
  209. package/core/src/render/camera-2d.ts +217 -0
  210. package/core/src/render/camera-controller.ts +440 -0
  211. package/core/src/render/camera.ts +409 -0
  212. package/core/src/render/canvas-resize-watcher.ts +46 -0
  213. package/core/src/render/compute-kernel.ts +172 -0
  214. package/core/src/render/constants.ts +2 -0
  215. package/core/src/render/device.ts +111 -0
  216. package/core/src/render/frame-graph.ts +768 -0
  217. package/core/src/render/frustum.ts +185 -0
  218. package/core/src/render/fullscreen-vs.ts +25 -0
  219. package/core/src/render/g-buffer.ts +186 -0
  220. package/core/src/render/game-renderer.ts +1452 -0
  221. package/core/src/render/gpu-driven/cull-pass.ts +394 -0
  222. package/core/src/render/gpu-driven/hzb.ts +215 -0
  223. package/core/src/render/gpu-driven/index.ts +7 -0
  224. package/core/src/render/gpu-driven/indirect-draw-pass.ts +324 -0
  225. package/core/src/render/gpu-driven/instance-buffer.ts +178 -0
  226. package/core/src/render/gpu-driven/mesh-table.ts +175 -0
  227. package/core/src/render/gpu-driven/passes/hzb-build-pass.ts +47 -0
  228. package/core/src/render/gpu-utils.ts +96 -0
  229. package/core/src/render/hdr.ts +88 -0
  230. package/core/src/render/hot-reload.ts +295 -0
  231. package/core/src/render/ibl-bind-group.ts +266 -0
  232. package/core/src/render/ibl.ts +180 -0
  233. package/core/src/render/index.ts +163 -0
  234. package/core/src/render/input-manager.ts +259 -0
  235. package/core/src/render/lighting/area-light.ts +196 -0
  236. package/core/src/render/lighting/cluster-grid.ts +288 -0
  237. package/core/src/render/lighting/cluster-types.ts +193 -0
  238. package/core/src/render/lighting/ies-parser.ts +162 -0
  239. package/core/src/render/lighting.ts +454 -0
  240. package/core/src/render/material-types/depth.wgsl +44 -0
  241. package/core/src/render/material-types/line.wgsl +50 -0
  242. package/core/src/render/material-types/matcap.wgsl +51 -0
  243. package/core/src/render/material-types/normal.wgsl +41 -0
  244. package/core/src/render/material-types/physical.wgsl +152 -0
  245. package/core/src/render/material-types/sprite.wgsl +46 -0
  246. package/core/src/render/material-types/sss.wgsl +79 -0
  247. package/core/src/render/material-types/toon.wgsl +79 -0
  248. package/core/src/render/passes/README.md +121 -0
  249. package/core/src/render/passes/atmosphere.ts +152 -0
  250. package/core/src/render/passes/cluster-lighting-pass.ts +476 -0
  251. package/core/src/render/passes/csm.ts +408 -0
  252. package/core/src/render/passes/cubemap-capture.ts +114 -0
  253. package/core/src/render/passes/debug-viz.ts +278 -0
  254. package/core/src/render/passes/debug.ts +538 -0
  255. package/core/src/render/passes/decal-mesh.ts +111 -0
  256. package/core/src/render/passes/decal-pass.ts +377 -0
  257. package/core/src/render/passes/deferred-lighting.ts +434 -0
  258. package/core/src/render/passes/depth-prepass.ts +163 -0
  259. package/core/src/render/passes/dome-360.ts +83 -0
  260. package/core/src/render/passes/fluid-render.ts +137 -0
  261. package/core/src/render/passes/gi-types.ts +118 -0
  262. package/core/src/render/passes/graph-compute.ts +300 -0
  263. package/core/src/render/passes/greased-line-pass.ts +232 -0
  264. package/core/src/render/passes/morph-target.ts +40 -0
  265. package/core/src/render/passes/opaque.ts +1028 -0
  266. package/core/src/render/passes/point-light-shadow.ts +266 -0
  267. package/core/src/render/passes/procedural-texture.ts +185 -0
  268. package/core/src/render/passes/reflection-probe.ts +140 -0
  269. package/core/src/render/passes/rsm-pass.ts +248 -0
  270. package/core/src/render/passes/sdf-text.ts +155 -0
  271. package/core/src/render/passes/shadow-map.ts +163 -0
  272. package/core/src/render/passes/shadow.ts +220 -0
  273. package/core/src/render/passes/skinning-vs.ts +121 -0
  274. package/core/src/render/passes/skinning.ts +326 -0
  275. package/core/src/render/passes/sky-dome.ts +404 -0
  276. package/core/src/render/passes/skybox.ts +162 -0
  277. package/core/src/render/passes/spot-light-shadow.ts +273 -0
  278. package/core/src/render/passes/terrain.ts +326 -0
  279. package/core/src/render/passes/transparent.ts +353 -0
  280. package/core/src/render/passes/underwater-fog.ts +185 -0
  281. package/core/src/render/passes/video-texture.ts +79 -0
  282. package/core/src/render/passes/volumetric-pass.ts +312 -0
  283. package/core/src/render/passes/volumetric-types.ts +171 -0
  284. package/core/src/render/passes/water.ts +753 -0
  285. package/core/src/render/pbr.ts +296 -0
  286. package/core/src/render/pipeline.ts +52 -0
  287. package/core/src/render/render-pass.ts +30 -0
  288. package/core/src/render/renderer-input-bus.ts +220 -0
  289. package/core/src/render/renderer-module-host.ts +623 -0
  290. package/core/src/render/resource-tracker.ts +67 -0
  291. package/core/src/render/sab-canvas-overlay.ts +122 -0
  292. package/core/src/render/shader-validator.ts +200 -0
  293. package/core/src/render/shaders/debug/debug-line.wgsl +27 -0
  294. package/core/src/render/shaders/debug/debug-point.wgsl +30 -0
  295. package/core/src/render/shaders/debug/debug-text.wgsl +42 -0
  296. package/core/src/render/shaders/debug-viz/aabb.wgsl +20 -0
  297. package/core/src/render/shaders/debug-viz/depth.wgsl +29 -0
  298. package/core/src/render/shaders/debug-viz/lod.wgsl +38 -0
  299. package/core/src/render/shaders/debug-viz/normals.wgsl +30 -0
  300. package/core/src/render/shaders/debug-viz/overdraw.wgsl +21 -0
  301. package/core/src/render/shaders/debug-viz/tangents.wgsl +29 -0
  302. package/core/src/render/shaders/debug-viz/wireframe.wgsl +33 -0
  303. package/core/src/render/shaders/shadow-chunks.ts +135 -0
  304. package/core/src/render/skybox.ts +146 -0
  305. package/core/src/render/splitscreen.ts +85 -0
  306. package/core/src/render/surface.ts +78 -0
  307. package/core/src/render/tonemap.ts +180 -0
  308. package/core/src/render/tracked-render-pass.ts +158 -0
  309. package/core/src/render/viewport.ts +109 -0
  310. package/core/src/render/wgsl-hmr.ts +70 -0
  311. package/core/src/sab/core-input-channel.ts +23 -0
  312. package/core/src/sab/define.ts +239 -0
  313. package/core/src/sab/errors.ts +76 -0
  314. package/core/src/sab/game-input.ts +343 -0
  315. package/core/src/sab/gamepad-devices.ts +259 -0
  316. package/core/src/sab/grid-buffer.ts +117 -0
  317. package/core/src/sab/input.ts +55 -0
  318. package/core/src/sab/layout.ts +219 -0
  319. package/core/src/sab/multi-input-channel.ts +50 -0
  320. package/core/src/sab/record-buffer.ts +155 -0
  321. package/core/src/sab/sab-polyfill.ts +56 -0
  322. package/core/src/sab/sim-channel.ts +453 -0
  323. package/core/src/sab/slot-buffer.ts +190 -0
  324. package/core/src/sab/types.ts +158 -0
  325. package/core/src/safety/bounds.ts +76 -0
  326. package/core/src/safety/json.ts +80 -0
  327. package/core/src/safety/path.ts +167 -0
  328. package/core/src/save/binary-format.ts +117 -0
  329. package/core/src/save/game-save-system.ts +161 -0
  330. package/core/src/save/grid-save-system.ts +158 -0
  331. package/core/src/save/migrate.ts +44 -0
  332. package/core/src/save/migration-registry.ts +94 -0
  333. package/core/src/save/persist-types.ts +208 -0
  334. package/core/src/save/schema.ts +46 -0
  335. package/core/src/save/serializer.ts +180 -0
  336. package/core/src/scene/builder.ts +65 -0
  337. package/core/src/scene/camera.ts +142 -0
  338. package/core/src/scene/checkpoint.ts +109 -0
  339. package/core/src/scene/game-loop.ts +198 -0
  340. package/core/src/scene/index.ts +24 -0
  341. package/core/src/scene/layer.ts +6 -0
  342. package/core/src/scene/prefab.ts +145 -0
  343. package/core/src/scene/scene-manager.ts +157 -0
  344. package/core/src/scene/scene.ts +192 -0
  345. package/core/src/scene/sky.ts +256 -0
  346. package/core/src/scene/spatial-grid.ts +176 -0
  347. package/core/src/scene/streaming.ts +304 -0
  348. package/core/src/scene/vegetation.ts +163 -0
  349. package/core/src/scene/world.ts +89 -0
  350. package/core/src/scripting/binding.ts +129 -0
  351. package/core/src/scripting/hot-reload.ts +144 -0
  352. package/core/src/scripting/script.ts +99 -0
  353. package/core/src/sim/hot-reload-pipeline.ts +49 -0
  354. package/core/src/sim/sim-state-helper.ts +47 -0
  355. package/core/src/sim/transient-state-registry.ts +77 -0
  356. package/core/src/sim/types.ts +113 -0
  357. package/core/src/sim/worker-loop.ts +222 -0
  358. package/core/src/stores/game-store.ts +158 -0
  359. package/core/src/stores/hot-reload-store.ts +44 -0
  360. package/core/src/telemetry/collector.ts +410 -0
  361. package/core/src/telemetry/gc-controller.ts +453 -0
  362. package/core/src/telemetry/gc-profiler.ts +126 -0
  363. package/core/src/telemetry/gc-tracker.ts +69 -0
  364. package/core/src/telemetry/gpu-profiler.ts +703 -0
  365. package/core/src/telemetry/gpu-resource-tracker.ts +285 -0
  366. package/core/src/telemetry/gpu-timer-pool.ts +234 -0
  367. package/core/src/telemetry/gpu-timer.ts +123 -0
  368. package/core/src/telemetry/gpu-timestamp-gate.ts +21 -0
  369. package/core/src/telemetry/reporter.ts +62 -0
  370. package/core/src/types/engine-types.ts +129 -0
  371. package/core/src/ui-font-scale.ts +112 -0
  372. package/core/src/util/broad-phase-grid.ts +82 -0
  373. package/core/src/util/feature-log.ts +220 -0
  374. package/core/src/util/index.ts +17 -0
  375. package/core/src/util/logger.ts +604 -0
  376. package/core/src/webgpu-destroy-augmentation.d.ts +25 -0
  377. package/core/src/worker/base-worker-host.ts +183 -0
  378. package/core/src/worker/buffer-sync.ts +550 -0
  379. package/core/src/worker/crash-recovery.ts +201 -0
  380. package/core/src/worker/entity-sim-worker-host.ts +190 -0
  381. package/core/src/worker/instrumented-worker-host.ts +171 -0
  382. package/core/src/worker/rpc.ts +253 -0
  383. package/core/src/worker/sim-worker-base.ts +913 -0
  384. package/core/src/worker/sim-worker-host.ts +358 -0
  385. package/core/src/worker/task-pool.ts +256 -0
  386. package/core/src/worker/task-worker.ts +83 -0
  387. package/libraries/asset-browser/src/index.ts +11 -0
  388. package/libraries/asset-browser/src/software-thumbnails.ts +949 -0
  389. package/libraries/audio-kira/README.md +17 -0
  390. package/libraries/audio-kira/native/Cargo.toml +14 -0
  391. package/libraries/audio-kira/native/src/engine.rs +27 -0
  392. package/libraries/audio-kira/native/src/lib.rs +185 -0
  393. package/libraries/audio-kira/native/src/mixer.rs +11 -0
  394. package/libraries/audio-kira/native/src/source.rs +80 -0
  395. package/libraries/audio-kira/src/backend.ts +469 -0
  396. package/libraries/audio-kira/src/index.ts +8 -0
  397. package/libraries/audio-kira/src/library.ts +82 -0
  398. package/libraries/blitz-ui/native/Cargo.toml +33 -0
  399. package/libraries/blitz-ui/native/assets/DejaVuSans.woff2 +0 -0
  400. package/libraries/blitz-ui/native/src/bridge.rs +100 -0
  401. package/libraries/blitz-ui/native/src/fonts.rs +20 -0
  402. package/libraries/blitz-ui/native/src/input.rs +80 -0
  403. package/libraries/blitz-ui/native/src/lib.rs +96 -0
  404. package/libraries/blitz-ui/native/src/shell.rs +301 -0
  405. package/libraries/blitz-ui/native-osr/Cargo.toml +24 -0
  406. package/libraries/blitz-ui/native-osr/src/events.rs +237 -0
  407. package/libraries/blitz-ui/native-osr/src/lib.rs +846 -0
  408. package/libraries/blitz-ui/native-osr/src/mutate.rs +385 -0
  409. package/libraries/blitz-ui/native-osr/src/resources.rs +45 -0
  410. package/libraries/blitz-ui/native-osr-spike.ts +70 -0
  411. package/libraries/blitz-ui/src/index.ts +15 -0
  412. package/libraries/blitz-ui/src/native-module.ts +221 -0
  413. package/libraries/blitz-ui/src/native-osr-ffi.ts +423 -0
  414. package/libraries/blitz-ui/src/router.ts +101 -0
  415. package/libraries/blitz-ui/src/shaders/ui-blit.wgsl.ts +35 -0
  416. package/libraries/blitz-ui/src/ui-blit-pass.ts +116 -0
  417. package/libraries/character/README.md +25 -0
  418. package/libraries/character/src/character-animator.ts +374 -0
  419. package/libraries/character/src/index.ts +24 -0
  420. package/libraries/character/src/loader.ts +375 -0
  421. package/libraries/character/src/locomotion.ts +124 -0
  422. package/libraries/character/src/preview.ts +287 -0
  423. package/libraries/devtools/README.md +51 -0
  424. package/libraries/devtools/native/Cargo.toml +12 -0
  425. package/libraries/devtools/native/src/lib.rs +1646 -0
  426. package/libraries/devtools/native/src/panels/console.rs +338 -0
  427. package/libraries/devtools/native/src/panels/dom_tree.rs +47 -0
  428. package/libraries/devtools/native/src/panels/generic.rs +317 -0
  429. package/libraries/devtools/native/src/panels/gpu.rs +107 -0
  430. package/libraries/devtools/native/src/panels/input.rs +88 -0
  431. package/libraries/devtools/native/src/panels/mod.rs +154 -0
  432. package/libraries/devtools/native/src/panels/perf_metrics.rs +91 -0
  433. package/libraries/devtools/native/src/panels/perf_recorder.rs +158 -0
  434. package/libraries/devtools/native/src/panels/scene.rs +72 -0
  435. package/libraries/devtools/native/src/state.rs +394 -0
  436. package/libraries/devtools/src/cdp-bridge.ts +349 -0
  437. package/libraries/devtools/src/collectors.ts +346 -0
  438. package/libraries/devtools/src/egui-ffi.ts +804 -0
  439. package/libraries/devtools/src/egui-renderer.ts +407 -0
  440. package/libraries/devtools/src/host.ts +363 -0
  441. package/libraries/devtools/src/index.ts +40 -0
  442. package/libraries/devtools/src/mirror.ts +794 -0
  443. package/libraries/devtools/src/native-providers.ts +744 -0
  444. package/libraries/devtools/src/verify.ts +167 -0
  445. package/libraries/devtools/src/web/browser.ts +102 -0
  446. package/libraries/devtools/src/web/host.ts +212 -0
  447. package/libraries/devtools/src/web/mirror.ts +430 -0
  448. package/libraries/devtools/src/web/server.ts +198 -0
  449. package/libraries/entities/README.md +17 -0
  450. package/libraries/entities/src/index.ts +4 -0
  451. package/libraries/entities/src/library.ts +64 -0
  452. package/libraries/entities/src/model-renderer.ts +1345 -0
  453. package/libraries/entities/src/shaders/model.wgsl +375 -0
  454. package/libraries/gamepad/src/feed-input.ts +83 -0
  455. package/libraries/gamepad/src/filters.ts +49 -0
  456. package/libraries/gamepad/src/glyphs.ts +93 -0
  457. package/libraries/gamepad/src/hub.ts +206 -0
  458. package/libraries/gamepad/src/index.ts +22 -0
  459. package/libraries/gamepad/src/library.ts +68 -0
  460. package/libraries/gamepad/src/source-sab.ts +70 -0
  461. package/libraries/gamepad/src/sysfs-enrich.ts +138 -0
  462. package/libraries/gaussian-splats/README.md +25 -0
  463. package/libraries/gaussian-splats/src/gpu-sort.ts +595 -0
  464. package/libraries/gaussian-splats/src/index.ts +31 -0
  465. package/libraries/gaussian-splats/src/library.ts +60 -0
  466. package/libraries/gaussian-splats/src/parser.ts +382 -0
  467. package/libraries/gaussian-splats/src/renderer.ts +427 -0
  468. package/libraries/gaussian-splats/src/sh-eval.ts +211 -0
  469. package/libraries/gaussian-splats/src/sorter.ts +110 -0
  470. package/libraries/gaussian-splats/src/tile-raster.ts +473 -0
  471. package/libraries/gpu-kernels/README.md +154 -0
  472. package/libraries/gpu-kernels/src/index.ts +11 -0
  473. package/libraries/gpu-kernels/src/kernel.ts +696 -0
  474. package/libraries/gpu-kernels/src/transpile.ts +958 -0
  475. package/libraries/html-ui-kit/src/behaviors.ts +250 -0
  476. package/libraries/html-ui-kit/src/components.ts +309 -0
  477. package/libraries/html-ui-kit/src/index.ts +30 -0
  478. package/libraries/html-ui-kit/src/nav/nav-controller.ts +238 -0
  479. package/libraries/html-ui-kit/src/nav/nav-input.ts +145 -0
  480. package/libraries/html-ui-kit/src/nav/osk.ts +153 -0
  481. package/libraries/html-ui-kit/src/nav/router.ts +167 -0
  482. package/libraries/html-ui-kit/src/nav/vcursor.ts +203 -0
  483. package/libraries/html-ui-kit/src/ten-foot.ts +182 -0
  484. package/libraries/html-ui-kit/src/theme.ts +238 -0
  485. package/libraries/lighting/README.md +20 -0
  486. package/libraries/lighting/src/index.ts +5 -0
  487. package/libraries/lighting/src/library.ts +66 -0
  488. package/libraries/lighting/src/light-system.ts +452 -0
  489. package/libraries/lighting/src/lighting-system.ts +68 -0
  490. package/libraries/lighting/src/shaders/light-debug.wgsl +30 -0
  491. package/libraries/marching-cubes/README.md +25 -0
  492. package/libraries/marching-cubes/src/chunked-field.ts +177 -0
  493. package/libraries/marching-cubes/src/deformation.ts +63 -0
  494. package/libraries/marching-cubes/src/generator.ts +407 -0
  495. package/libraries/marching-cubes/src/index.ts +32 -0
  496. package/libraries/marching-cubes/src/library.ts +80 -0
  497. package/libraries/marching-cubes/src/lod.ts +122 -0
  498. package/libraries/marching-cubes/src/marching-tetrahedra.ts +252 -0
  499. package/libraries/marching-cubes/src/sab.ts +45 -0
  500. package/libraries/marching-cubes/src/streaming-config.ts +48 -0
  501. package/libraries/marching-cubes/src/streaming-manager.ts +686 -0
  502. package/libraries/marching-cubes/src/tables.ts +323 -0
  503. package/libraries/marching-cubes/src/types.ts +18 -0
  504. package/libraries/models/README.md +25 -0
  505. package/libraries/models/src/bake-node-transforms.ts +171 -0
  506. package/libraries/models/src/codecs/basisu-codec.ts +180 -0
  507. package/libraries/models/src/codecs/draco-codec.ts +372 -0
  508. package/libraries/models/src/codecs/draco3dgltf.d.ts +40 -0
  509. package/libraries/models/src/codecs/index.ts +22 -0
  510. package/libraries/models/src/codecs/meshopt-codec.ts +91 -0
  511. package/libraries/models/src/codecs/registry.ts +221 -0
  512. package/libraries/models/src/dae.ts +753 -0
  513. package/libraries/models/src/default-part-selection.ts +56 -0
  514. package/libraries/models/src/dxf.ts +200 -0
  515. package/libraries/models/src/fbx/index.ts +457 -0
  516. package/libraries/models/src/fbx/interpreter/animation.ts +431 -0
  517. package/libraries/models/src/fbx/interpreter/blend-shapes.ts +323 -0
  518. package/libraries/models/src/fbx/interpreter/connections.ts +166 -0
  519. package/libraries/models/src/fbx/interpreter/diagnostics.ts +65 -0
  520. package/libraries/models/src/fbx/interpreter/geometry.ts +535 -0
  521. package/libraries/models/src/fbx/interpreter/global-settings.ts +92 -0
  522. package/libraries/models/src/fbx/interpreter/materials.ts +265 -0
  523. package/libraries/models/src/fbx/interpreter/nodes.ts +197 -0
  524. package/libraries/models/src/fbx/interpreter/skeleton.ts +476 -0
  525. package/libraries/models/src/fbx/parsers/binary-parser.ts +244 -0
  526. package/libraries/models/src/fbx/types.ts +119 -0
  527. package/libraries/models/src/gltf-extensions.ts +196 -0
  528. package/libraries/models/src/gltf.ts +935 -0
  529. package/libraries/models/src/index.ts +87 -0
  530. package/libraries/models/src/library.ts +75 -0
  531. package/libraries/models/src/loader.ts +265 -0
  532. package/libraries/models/src/material-adapter.ts +108 -0
  533. package/libraries/models/src/mini-xml.ts +174 -0
  534. package/libraries/models/src/normalize.ts +216 -0
  535. package/libraries/models/src/obj.ts +264 -0
  536. package/libraries/models/src/off.ts +95 -0
  537. package/libraries/models/src/ply.ts +348 -0
  538. package/libraries/models/src/pose-sampler.ts +219 -0
  539. package/libraries/models/src/sidecar/blender-extras.ts +37 -0
  540. package/libraries/models/src/sidecar/ddmeta.ts +114 -0
  541. package/libraries/models/src/sidecar/godot-import.ts +86 -0
  542. package/libraries/models/src/sidecar/resolver.ts +145 -0
  543. package/libraries/models/src/sidecar/types.ts +11 -0
  544. package/libraries/models/src/sidecar/unity-meta.ts +61 -0
  545. package/libraries/models/src/skeleton-synthesis.ts +132 -0
  546. package/libraries/models/src/stl.ts +149 -0
  547. package/libraries/models/src/threeds.ts +363 -0
  548. package/libraries/models/src/threemf.ts +201 -0
  549. package/libraries/models/src/types.ts +188 -0
  550. package/libraries/models/src/vtk.ts +218 -0
  551. package/libraries/models/test-fixtures/fbx/LICENSE-notice.txt +24 -0
  552. package/libraries/models/test-fixtures/fbx/README.md +46 -0
  553. package/libraries/models/test-fixtures/fbx/m01_cube_phong.fbx +0 -0
  554. package/libraries/models/test-fixtures/fbx/m02_geo_ngons.fbx +0 -0
  555. package/libraries/models/test-fixtures/fbx/m03_normals.fbx +0 -0
  556. package/libraries/models/test-fixtures/fbx/m04_material_properties.fbx +0 -0
  557. package/libraries/models/test-fixtures/fbx/m05_external_diffuse.png +0 -0
  558. package/libraries/models/test-fixtures/fbx/m05_textures.fbx +0 -0
  559. package/libraries/models/test-fixtures/fbx/m06_uv_transform.fbx +0 -0
  560. package/libraries/models/test-fixtures/fbx/m07_multimaterial.fbx +0 -0
  561. package/libraries/models/test-fixtures/fbx/m08_transforms.fbx +0 -0
  562. package/libraries/models/test-fixtures/fbx/m09_skinning.fbx +0 -0
  563. package/libraries/models/test-fixtures/fbx/m10_morph.fbx +0 -0
  564. package/libraries/models/test-fixtures/fbx/m11_node_anim.fbx +0 -0
  565. package/libraries/models/test-fixtures/fbx/m12_skeletal_anim.fbx +0 -0
  566. package/libraries/models/test-fixtures/fbx/m13_morph_anim.fbx +0 -0
  567. package/libraries/models/test-fixtures/fbx/m14_multiclip.fbx +0 -0
  568. package/libraries/models/test-fixtures/fbx/m15_camera_lights.fbx +0 -0
  569. package/libraries/models/test-fixtures/fbx/m16_axis_yup.fbx +0 -0
  570. package/libraries/models/test-fixtures/fbx/m16_axis_zup.fbx +0 -0
  571. package/libraries/models/test-fixtures/fbx/m16_units_254.fbx +0 -0
  572. package/libraries/navmesh/README.md +23 -0
  573. package/libraries/navmesh/src/crowd-system.ts +240 -0
  574. package/libraries/navmesh/src/debug-viz.ts +74 -0
  575. package/libraries/navmesh/src/index.ts +15 -0
  576. package/libraries/navmesh/src/library.ts +64 -0
  577. package/libraries/navmesh/src/navmesh-generator.ts +407 -0
  578. package/libraries/navmesh/src/navmesh.ts +159 -0
  579. package/libraries/navmesh/src/pathfinder.ts +252 -0
  580. package/libraries/navmesh/src/types.ts +63 -0
  581. package/libraries/networking/README.md +25 -0
  582. package/libraries/networking/src/authority.ts +121 -0
  583. package/libraries/networking/src/connection.ts +139 -0
  584. package/libraries/networking/src/discovery.ts +373 -0
  585. package/libraries/networking/src/enhanced-replication.ts +355 -0
  586. package/libraries/networking/src/epic-adapter.ts +129 -0
  587. package/libraries/networking/src/index.ts +45 -0
  588. package/libraries/networking/src/library.ts +69 -0
  589. package/libraries/networking/src/lobby.ts +108 -0
  590. package/libraries/networking/src/platform-adapter.ts +163 -0
  591. package/libraries/networking/src/rcs-adapter.ts +127 -0
  592. package/libraries/networking/src/remote-input.ts +188 -0
  593. package/libraries/networking/src/replication.ts +327 -0
  594. package/libraries/networking/src/rpc.ts +188 -0
  595. package/libraries/networking/src/session.ts +173 -0
  596. package/libraries/networking/src/transport.ts +201 -0
  597. package/libraries/networking/src/webrtc.ts +323 -0
  598. package/libraries/pathfinding-2d/README.md +15 -0
  599. package/libraries/pathfinding-2d/src/astar-grid.ts +312 -0
  600. package/libraries/pathfinding-2d/src/index.ts +11 -0
  601. package/libraries/persistence/README.md +25 -0
  602. package/libraries/persistence/src/autosave-manager.ts +112 -0
  603. package/libraries/persistence/src/binary-record-store.ts +244 -0
  604. package/libraries/persistence/src/browser.ts +51 -0
  605. package/libraries/persistence/src/file-save-store.ts +553 -0
  606. package/libraries/persistence/src/hash-utils.ts +59 -0
  607. package/libraries/persistence/src/index.ts +57 -0
  608. package/libraries/persistence/src/library.ts +74 -0
  609. package/libraries/persistence/src/mock-opfs.ts +141 -0
  610. package/libraries/persistence/src/node-fs-directory-handle.ts +161 -0
  611. package/libraries/persistence/src/opfs-save-store.ts +745 -0
  612. package/libraries/persistence/src/save-worker-proxy.ts +127 -0
  613. package/libraries/persistence/src/save-worker.ts +99 -0
  614. package/libraries/persistence/src/xxh3-ts.d.ts +10 -0
  615. package/libraries/physics-native/README.md +21 -0
  616. package/libraries/physics-native/native/Cargo.toml +11 -0
  617. package/libraries/physics-native/native/src/body.rs +403 -0
  618. package/libraries/physics-native/native/src/character.rs +226 -0
  619. package/libraries/physics-native/native/src/collider.rs +299 -0
  620. package/libraries/physics-native/native/src/joint.rs +81 -0
  621. package/libraries/physics-native/native/src/lib.rs +830 -0
  622. package/libraries/physics-native/native/src/query.rs +261 -0
  623. package/libraries/physics-native/native/src/realm.rs +33 -0
  624. package/libraries/physics-native/native/src/step.rs +54 -0
  625. package/libraries/physics-native/native/src/world.rs +148 -0
  626. package/libraries/physics-native/src/backend.ts +1046 -0
  627. package/libraries/physics-native/src/broadphase.ts +101 -0
  628. package/libraries/physics-native/src/ffi-spike-worker.ts +71 -0
  629. package/libraries/physics-native/src/index.ts +11 -0
  630. package/libraries/physics-native/src/library.ts +79 -0
  631. package/libraries/physics-native/src/narrowphase-shapes.ts +420 -0
  632. package/libraries/physics-native/src/narrowphase.ts +123 -0
  633. package/libraries/physics-native/src/solver.ts +141 -0
  634. package/libraries/physics-native/src/types.ts +76 -0
  635. package/libraries/physics-rapier/README.md +21 -0
  636. package/libraries/physics-rapier/bench/ffi-bench.ts +79 -0
  637. package/libraries/physics-rapier/bench/wasm-baseline.ts +90 -0
  638. package/libraries/physics-rapier/src/api.ts +452 -0
  639. package/libraries/physics-rapier/src/auto-backend.ts +59 -0
  640. package/libraries/physics-rapier/src/backend.ts +1207 -0
  641. package/libraries/physics-rapier/src/bulk-ops.ts +68 -0
  642. package/libraries/physics-rapier/src/ffi-backend.ts +21 -0
  643. package/libraries/physics-rapier/src/ffi-lib.ts +597 -0
  644. package/libraries/physics-rapier/src/index.ts +12 -0
  645. package/libraries/physics-rapier/src/library.ts +107 -0
  646. package/libraries/physics-rapier/src/rapier-backend.ts +1052 -0
  647. package/libraries/physics-rapier/src/realm-worker-script.ts +237 -0
  648. package/libraries/postfx/README.md +17 -0
  649. package/libraries/postfx/src/index.ts +19 -0
  650. package/libraries/postfx/src/library.ts +337 -0
  651. package/libraries/postfx/src/post-process-stack.ts +1752 -0
  652. package/libraries/postfx/src/shaders/post-process/afterimage.wgsl +14 -0
  653. package/libraries/postfx/src/shaders/post-process/ascii.wgsl +24 -0
  654. package/libraries/postfx/src/shaders/post-process/blit.wgsl +12 -0
  655. package/libraries/postfx/src/shaders/post-process/bloom-blur.wgsl +21 -0
  656. package/libraries/postfx/src/shaders/post-process/bloom-bright.wgsl +15 -0
  657. package/libraries/postfx/src/shaders/post-process/bloom-composite.wgsl +14 -0
  658. package/libraries/postfx/src/shaders/post-process/bloom-downsample.wgsl +60 -0
  659. package/libraries/postfx/src/shaders/post-process/bloom-soft.wgsl +46 -0
  660. package/libraries/postfx/src/shaders/post-process/bloom-upsample.wgsl +51 -0
  661. package/libraries/postfx/src/shaders/post-process/channel-mixer.wgsl +36 -0
  662. package/libraries/postfx/src/shaders/post-process/chromatic-aberration.wgsl +37 -0
  663. package/libraries/postfx/src/shaders/post-process/dithering.wgsl +64 -0
  664. package/libraries/postfx/src/shaders/post-process/dof.wgsl +131 -0
  665. package/libraries/postfx/src/shaders/post-process/edges.wgsl +45 -0
  666. package/libraries/postfx/src/shaders/post-process/fullscreen-vs.wgsl +15 -0
  667. package/libraries/postfx/src/shaders/post-process/fxaa.wgsl +102 -0
  668. package/libraries/postfx/src/shaders/post-process/gaussian-blur.wgsl +37 -0
  669. package/libraries/postfx/src/shaders/post-process/glow-blur.wgsl +33 -0
  670. package/libraries/postfx/src/shaders/post-process/glow-composite.wgsl +25 -0
  671. package/libraries/postfx/src/shaders/post-process/grain.wgsl +37 -0
  672. package/libraries/postfx/src/shaders/post-process/halftone.wgsl +80 -0
  673. package/libraries/postfx/src/shaders/post-process/highlight-blur.wgsl +33 -0
  674. package/libraries/postfx/src/shaders/post-process/highlight-composite.wgsl +32 -0
  675. package/libraries/postfx/src/shaders/post-process/lens-distortion.wgsl +43 -0
  676. package/libraries/postfx/src/shaders/post-process/lens-flare.wgsl +68 -0
  677. package/libraries/postfx/src/shaders/post-process/lut.wgsl +30 -0
  678. package/libraries/postfx/src/shaders/post-process/motion-blur.wgsl +55 -0
  679. package/libraries/postfx/src/shaders/post-process/occluder-chunk.wgsl +28 -0
  680. package/libraries/postfx/src/shaders/post-process/outline.wgsl +51 -0
  681. package/libraries/postfx/src/shaders/post-process/pixelation.wgsl +47 -0
  682. package/libraries/postfx/src/shaders/post-process/sharpen.wgsl +32 -0
  683. package/libraries/postfx/src/shaders/post-process/sobel.wgsl +26 -0
  684. package/libraries/postfx/src/shaders/post-process/split-tone.wgsl +36 -0
  685. package/libraries/postfx/src/shaders/post-process/ssao-blur.wgsl +38 -0
  686. package/libraries/postfx/src/shaders/post-process/ssao-composite.wgsl +25 -0
  687. package/libraries/postfx/src/shaders/post-process/ssao.wgsl +147 -0
  688. package/libraries/postfx/src/shaders/post-process/ssr.wgsl +155 -0
  689. package/libraries/postfx/src/shaders/post-process/taa.wgsl +98 -0
  690. package/libraries/postfx/src/shaders/post-process/tonemap.wgsl +40 -0
  691. package/libraries/postfx/src/shaders/post-process/watercolor.wgsl +82 -0
  692. package/libraries/postfx/src/shaders/post-process/white-balance.wgsl +41 -0
  693. package/libraries/recast/README.md +21 -0
  694. package/libraries/recast/src/backend.ts +150 -0
  695. package/libraries/recast/src/crowd-system.ts +352 -0
  696. package/libraries/recast/src/index.ts +12 -0
  697. package/libraries/recast/src/library.ts +91 -0
  698. package/libraries/recast/src/types.ts +86 -0
  699. package/libraries/sand/README.md +25 -0
  700. package/libraries/sand/src/cell.ts +43 -0
  701. package/libraries/sand/src/fields.ts +29 -0
  702. package/libraries/sand/src/fluid-grid.ts +261 -0
  703. package/libraries/sand/src/grid-dims.ts +74 -0
  704. package/libraries/sand/src/grid-sim-buffer.ts +271 -0
  705. package/libraries/sand/src/index.ts +81 -0
  706. package/libraries/sand/src/library.ts +92 -0
  707. package/libraries/sand/src/materials.ts +492 -0
  708. package/libraries/sand/src/palette.ts +93 -0
  709. package/libraries/sand/src/particles.ts +185 -0
  710. package/libraries/sand/src/render/sand-grid-pass.ts +430 -0
  711. package/libraries/sand/src/rng.ts +51 -0
  712. package/libraries/sand/src/rules/builtin-rules.ts +132 -0
  713. package/libraries/sand/src/rules/rule-compiler.ts +162 -0
  714. package/libraries/sand/src/rules/rule-engine.ts +231 -0
  715. package/libraries/sand/src/rules/rule-types.ts +87 -0
  716. package/libraries/sand/src/sand-step-pool.ts +484 -0
  717. package/libraries/sand/src/sand-step-worker.ts +97 -0
  718. package/libraries/sand/src/sand-world.ts +3227 -0
  719. package/libraries/stickman/README.md +25 -0
  720. package/libraries/stickman/src/index.ts +25 -0
  721. package/libraries/stickman/src/library.ts +61 -0
  722. package/libraries/stickman/src/proportions.ts +45 -0
  723. package/libraries/stickman/src/shader.ts +117 -0
  724. package/libraries/stickman/src/skeleton.ts +197 -0
  725. package/libraries/stickman/src/stickman-pass.ts +255 -0
  726. package/libraries/stickman/src/thick-line.ts +123 -0
  727. package/libraries/surface-nets/README.md +25 -0
  728. package/libraries/surface-nets/src/chunked-field.ts +14 -0
  729. package/libraries/surface-nets/src/deformation.ts +45 -0
  730. package/libraries/surface-nets/src/index.ts +36 -0
  731. package/libraries/surface-nets/src/library.ts +59 -0
  732. package/libraries/surface-nets/src/lod.ts +7 -0
  733. package/libraries/surface-nets/src/sab.ts +4 -0
  734. package/libraries/surface-nets/src/streaming-config.ts +5 -0
  735. package/libraries/surface-nets/src/streaming-manager.ts +15 -0
  736. package/libraries/surface-nets/src/surface-nets.ts +334 -0
  737. package/libraries/surface-nets/src/types.ts +23 -0
  738. package/libraries/water/README.md +25 -0
  739. package/libraries/water/src/buoyancy.ts +103 -0
  740. package/libraries/water/src/index.ts +43 -0
  741. package/libraries/water/src/library.ts +85 -0
  742. package/libraries/water/src/shore-damping.ts +93 -0
  743. package/libraries/water/src/water-buffer.ts +89 -0
  744. package/libraries/water/src/water-chunks.ts +13 -0
  745. package/libraries/water/src/water-physics.ts +376 -0
  746. package/libraries/water/src/water-renderer.ts +809 -0
  747. package/libraries/water/src/water-sab.ts +161 -0
  748. package/libraries/water/src/wave-sources.ts +84 -0
  749. package/libraries/weather/README.md +20 -0
  750. package/libraries/weather/src/index.ts +9 -0
  751. package/libraries/weather/src/library.ts +72 -0
  752. package/libraries/weather/src/types.ts +64 -0
  753. package/libraries/weather/src/weather-blend.ts +112 -0
  754. package/libraries/weather/src/weather-system.ts +271 -0
  755. package/libraries/weatherfx/README.md +24 -0
  756. package/libraries/weatherfx/src/cloud-provider.ts +31 -0
  757. package/libraries/weatherfx/src/cloud-system.ts +372 -0
  758. package/libraries/weatherfx/src/index.ts +9 -0
  759. package/libraries/weatherfx/src/library.ts +58 -0
  760. package/libraries/weatherfx/src/particle-system.ts +434 -0
  761. package/libraries/weatherfx/src/shaders/cloud.wgsl +111 -0
  762. package/libraries/weatherfx/src/shaders/particle-compute.wgsl +187 -0
  763. package/libraries/weatherfx/src/shaders/particle-render.wgsl +95 -0
  764. package/lint-plugin.mjs +41 -0
  765. package/mcp/src/client.ts +703 -0
  766. package/mcp/src/engine-context.ts +186 -0
  767. package/mcp/src/http-transport.ts +471 -0
  768. package/mcp/src/index.ts +13 -0
  769. package/mcp/src/prompts/add-entity.ts +42 -0
  770. package/mcp/src/prompts/create-scene.ts +44 -0
  771. package/mcp/src/prompts/debug-frame.ts +39 -0
  772. package/mcp/src/prompts/index.ts +12 -0
  773. package/mcp/src/resources/asset-list.ts +32 -0
  774. package/mcp/src/resources/checkpoint-list.ts +33 -0
  775. package/mcp/src/resources/entity-state.ts +55 -0
  776. package/mcp/src/resources/gpu-info.ts +103 -0
  777. package/mcp/src/resources/index.ts +57 -0
  778. package/mcp/src/resources/performance.ts +37 -0
  779. package/mcp/src/resources/scene-tree.ts +43 -0
  780. package/mcp/src/server.ts +375 -0
  781. package/mcp/src/tools/animation.ts +118 -0
  782. package/mcp/src/tools/asset.ts +161 -0
  783. package/mcp/src/tools/audio.ts +96 -0
  784. package/mcp/src/tools/build.ts +143 -0
  785. package/mcp/src/tools/camera.ts +167 -0
  786. package/mcp/src/tools/checkpoint.ts +100 -0
  787. package/mcp/src/tools/component.ts +199 -0
  788. package/mcp/src/tools/debug.ts +300 -0
  789. package/mcp/src/tools/entity.ts +361 -0
  790. package/mcp/src/tools/inspect.ts +210 -0
  791. package/mcp/src/tools/lighting.ts +263 -0
  792. package/mcp/src/tools/material.ts +243 -0
  793. package/mcp/src/tools/mesh.ts +239 -0
  794. package/mcp/src/tools/physics.ts +266 -0
  795. package/mcp/src/tools/scene.ts +153 -0
  796. package/mcp/src/tools/script.ts +122 -0
  797. package/mcp/src/types.ts +92 -0
  798. package/mcp/src/undo-redo.ts +77 -0
  799. package/modules/camera-controls/README.md +20 -0
  800. package/modules/camera-controls/src/index.ts +161 -0
  801. package/modules/controller-ui/src/index.ts +164 -0
  802. package/modules/devtools/README.md +25 -0
  803. package/modules/devtools/src/api.ts +461 -0
  804. package/modules/devtools/src/asset-utils.ts +105 -0
  805. package/modules/devtools/src/bind-debug-store.ts +48 -0
  806. package/modules/devtools/src/data-bridge.ts +286 -0
  807. package/modules/devtools/src/debug-overlay.ts +238 -0
  808. package/modules/devtools/src/debug-raycast.ts +328 -0
  809. package/modules/devtools/src/debug-store.ts +92 -0
  810. package/modules/devtools/src/debug-view-descriptors.ts +59 -0
  811. package/modules/devtools/src/doctor-panel.ts +182 -0
  812. package/modules/devtools/src/gizmo-module.ts +136 -0
  813. package/modules/devtools/src/graph-model.ts +509 -0
  814. package/modules/devtools/src/grid-renderer.ts +157 -0
  815. package/modules/devtools/src/height-ruler-renderer.ts +216 -0
  816. package/modules/devtools/src/index.ts +81 -0
  817. package/modules/devtools/src/init.ts +261 -0
  818. package/modules/devtools/src/label-overlay.ts +154 -0
  819. package/modules/devtools/src/material-stats-panel.ts +188 -0
  820. package/modules/devtools/src/profiling-bridge.ts +377 -0
  821. package/modules/devtools/src/profiling-hooks.ts +98 -0
  822. package/modules/devtools/src/renderer-adapter.ts +76 -0
  823. package/modules/devtools/src/scene-inspector.ts +637 -0
  824. package/modules/devtools/src/scene-store.ts +320 -0
  825. package/modules/devtools/src/scene-sync.ts +116 -0
  826. package/modules/devtools/src/shaders/transform-gizmo.wgsl +38 -0
  827. package/modules/devtools/src/sim-stats-panel.ts +272 -0
  828. package/modules/devtools/src/sim-stats-provider.ts +105 -0
  829. package/modules/devtools/src/skeleton-renderer.ts +351 -0
  830. package/modules/devtools/src/transform-gizmo.ts +928 -0
  831. package/modules/devtools/src/types.ts +290 -0
  832. package/modules/devtools/src/worker-expose.ts +67 -0
  833. package/modules/devtools/src/worker-sync.ts +137 -0
  834. package/modules/editor/src/commands/component.ts +156 -0
  835. package/modules/editor/src/commands/entity.ts +287 -0
  836. package/modules/editor/src/commands/hierarchy.ts +83 -0
  837. package/modules/editor/src/commands/index.ts +61 -0
  838. package/modules/editor/src/commands/prefab.ts +163 -0
  839. package/modules/editor/src/commands/registry.ts +331 -0
  840. package/modules/editor/src/commands/scene-io.ts +211 -0
  841. package/modules/editor/src/commands/selection.ts +73 -0
  842. package/modules/editor/src/commands/transform.ts +91 -0
  843. package/modules/editor/src/ddscene.ts +267 -0
  844. package/modules/editor/src/document.ts +123 -0
  845. package/modules/editor/src/editor-context.ts +172 -0
  846. package/modules/editor/src/index.ts +41 -0
  847. package/modules/editor/src/mcp.ts +103 -0
  848. package/modules/editor/src/module.ts +148 -0
  849. package/modules/editor/src/selection.ts +70 -0
  850. package/modules/editor/src/shell.ts +456 -0
  851. package/modules/editor/src/viewport.ts +199 -0
  852. package/modules/html-ui/src/composite.ts +135 -0
  853. package/modules/html-ui/src/create-html-ui.ts +188 -0
  854. package/modules/html-ui/src/doc-backend.ts +343 -0
  855. package/modules/html-ui/src/host.ts +646 -0
  856. package/modules/html-ui/src/index.ts +13 -0
  857. package/modules/html-ui/src/jsx-dev-runtime.ts +12 -0
  858. package/modules/html-ui/src/jsx-runtime.ts +126 -0
  859. package/modules/html-ui/src/protocol.ts +88 -0
  860. package/modules/html-ui/src/ui-worker.ts +16 -0
  861. package/modules/mcp/README.md +18 -0
  862. package/modules/mcp/src/index.ts +27 -0
  863. package/modules/movement-2d/README.md +19 -0
  864. package/modules/movement-2d/src/grid-character.ts +455 -0
  865. package/modules/movement-2d/src/index.ts +45 -0
  866. package/modules/movement-3d/README.md +21 -0
  867. package/modules/movement-3d/src/character-motor.ts +230 -0
  868. package/modules/movement-3d/src/index.ts +46 -0
  869. package/modules/native-osr/src/index.ts +22 -0
  870. package/modules/native-osr/src/input-router.ts +401 -0
  871. package/modules/native-osr/src/native-osr-manager.ts +247 -0
  872. package/modules/native-osr/src/native-texture-source.ts +86 -0
  873. package/modules/native-osr/src/types.ts +112 -0
  874. package/modules/native-osr/src/world-space-ui-pass.ts +351 -0
  875. package/modules/sailing/README.md +18 -0
  876. package/modules/sailing/src/index.ts +51 -0
  877. package/modules/terrain/README.md +18 -0
  878. package/modules/terrain/src/index.ts +78 -0
  879. package/modules/vitals/README.md +17 -0
  880. package/modules/vitals/src/index.ts +43 -0
  881. package/modules/vitals/src/vitals.ts +265 -0
  882. package/modules/xr/README.md +25 -0
  883. package/modules/xr/src/camera-rig.ts +146 -0
  884. package/modules/xr/src/debug.ts +59 -0
  885. package/modules/xr/src/frame-loop.ts +197 -0
  886. package/modules/xr/src/index.ts +16 -0
  887. package/modules/xr/src/input.ts +208 -0
  888. package/modules/xr/src/layer.ts +140 -0
  889. package/modules/xr/src/session.ts +156 -0
  890. package/modules/xr/src/sim-module.ts +23 -0
  891. package/modules/xr/src/types.ts +32 -0
  892. package/modules/xr/src/webxr.d.ts +182 -0
  893. package/modules/xr/src/xr-module.ts +179 -0
  894. package/package.json +232 -3
  895. package/shader-graph/src/chunks.ts +349 -0
  896. package/shader-graph/src/compiler.ts +732 -0
  897. package/shader-graph/src/compute-compiler.ts +453 -0
  898. package/shader-graph/src/compute-graph.ts +112 -0
  899. package/shader-graph/src/compute-profiles.ts +54 -0
  900. package/shader-graph/src/graph.ts +52 -0
  901. package/shader-graph/src/index.ts +53 -0
  902. package/shader-graph/src/profiles.ts +407 -0
  903. package/shader-graph/src/validator.ts +48 -0
  904. package/shader-graph/src/wgsl-binding-validator.ts +143 -0
  905. package/shader-graph/src/wgsl-struct-validator.ts +245 -0
  906. package/shader-graph/src/wgsl-struct.ts +397 -0
  907. package/test/src/index.ts +7 -0
  908. package/test/src/vision-test.ts +369 -0
@@ -0,0 +1,1752 @@
1
+ // ============================================================================
2
+ // PostProcess Stack — unified chainable WebGPU postprocessing
3
+ //
4
+ // All 21 effects: taa, ssao, ssr, dof, motion-blur, bloom, bloom-soft,
5
+ // tonemap, fxaa, sharpen, grain, sobel, edges, lens-flare, pixelation,
6
+ // gaussian-blur, afterimage, outline, highlight, glow, ascii.
7
+ //
8
+ // HDR section (taa → bloom-soft) uses rgba16float intermediates.
9
+ // tonemap is the HDR→LDR bridge.
10
+ // LDR section (fxaa → ascii) also uses rgba16float intermediates.
11
+ // A final blit copies the result to the canvas-format swapchain view.
12
+ // ============================================================================
13
+
14
+ import { createValidatedShaderModule } from "@downdraft/engine";
15
+
16
+ export interface ViewportRect { x: number; y: number; w: number; h: number; }
17
+ export interface PostProcessStackOptions {
18
+ depthFormat?: GPUTextureFormat;
19
+ /**
20
+ * Format of the scene color target the game renders into (`getSceneColorView`).
21
+ * Must match the game's scene pipelines' color target format. Defaults to
22
+ * rgba16float (HDR input for bloom/tonemap); games whose scene pipelines
23
+ * target the surface format should pass that format here (LDR input).
24
+ */
25
+ sceneFormat?: GPUTextureFormat;
26
+ }
27
+
28
+ import { createLogger } from "@downdraft/engine/util/logger";
29
+ import AFTERIMAGE_FS from "./shaders/post-process/afterimage.wgsl?raw" with { type: "text" };
30
+ import ASCII_FS from "./shaders/post-process/ascii.wgsl?raw" with { type: "text" };
31
+ import BLIT_FS from "./shaders/post-process/blit.wgsl?raw" with { type: "text" };
32
+ import BLOOM_BLUR_FS from "./shaders/post-process/bloom-blur.wgsl?raw" with { type: "text" };
33
+ import BLOOM_BRIGHT_FS from "./shaders/post-process/bloom-bright.wgsl?raw" with { type: "text" };
34
+ import BLOOM_COMPOSITE_FS from "./shaders/post-process/bloom-composite.wgsl?raw" with { type: "text" };
35
+ import BLOOM_DOWNSAMPLE_FS from "./shaders/post-process/bloom-downsample.wgsl?raw" with { type: "text" };
36
+ import BLOOM_SOFT_FS from "./shaders/post-process/bloom-soft.wgsl?raw" with { type: "text" };
37
+ import BLOOM_UPSAMPLE_FS from "./shaders/post-process/bloom-upsample.wgsl?raw" with { type: "text" };
38
+ import CHANNEL_MIXER_FS from "./shaders/post-process/channel-mixer.wgsl?raw" with { type: "text" };
39
+ import CHROMATIC_ABERRATION_FS from "./shaders/post-process/chromatic-aberration.wgsl?raw" with { type: "text" };
40
+ import DITHERING_FS from "./shaders/post-process/dithering.wgsl?raw" with { type: "text" };
41
+ import DOF_FS from "./shaders/post-process/dof.wgsl?raw" with { type: "text" };
42
+ import EDGES_FS from "./shaders/post-process/edges.wgsl?raw" with { type: "text" };
43
+ import VS from "./shaders/post-process/fullscreen-vs.wgsl?raw" with { type: "text" };
44
+ import FXAA_FS from "./shaders/post-process/fxaa.wgsl?raw" with { type: "text" };
45
+ import GAUSSIAN_BLUR_FS from "./shaders/post-process/gaussian-blur.wgsl?raw" with { type: "text" };
46
+ import GLOW_BLUR_FS from "./shaders/post-process/glow-blur.wgsl?raw" with { type: "text" };
47
+ import GLOW_COMPOSITE_FS from "./shaders/post-process/glow-composite.wgsl?raw" with { type: "text" };
48
+ import GRAIN_FS from "./shaders/post-process/grain.wgsl?raw" with { type: "text" };
49
+ import HALFTONE_FS from "./shaders/post-process/halftone.wgsl?raw" with { type: "text" };
50
+ import HIGHLIGHT_BLUR_FS from "./shaders/post-process/highlight-blur.wgsl?raw" with { type: "text" };
51
+ import HIGHLIGHT_COMPOSITE_FS from "./shaders/post-process/highlight-composite.wgsl?raw" with { type: "text" };
52
+ import LENS_DISTORTION_FS from "./shaders/post-process/lens-distortion.wgsl?raw" with { type: "text" };
53
+ import LENS_FLARE_FS from "./shaders/post-process/lens-flare.wgsl?raw" with { type: "text" };
54
+ import LUT_FS from "./shaders/post-process/lut.wgsl?raw" with { type: "text" };
55
+ import MOTION_BLUR_FS from "./shaders/post-process/motion-blur.wgsl?raw" with { type: "text" };
56
+ import OCCLUDER_CHUNK from "./shaders/post-process/occluder-chunk.wgsl?raw" with { type: "text" };
57
+ import OUTLINE_FS from "./shaders/post-process/outline.wgsl?raw" with { type: "text" };
58
+ import PIXELATION_FS from "./shaders/post-process/pixelation.wgsl?raw" with { type: "text" };
59
+ import SHARPEN_FS from "./shaders/post-process/sharpen.wgsl?raw" with { type: "text" };
60
+ import SOBEL_FS from "./shaders/post-process/sobel.wgsl?raw" with { type: "text" };
61
+ import SPLIT_TONE_FS from "./shaders/post-process/split-tone.wgsl?raw" with { type: "text" };
62
+ import SSAO_BLUR_FS from "./shaders/post-process/ssao-blur.wgsl?raw" with { type: "text" };
63
+ import SSAO_COMPOSITE_FS from "./shaders/post-process/ssao-composite.wgsl?raw" with { type: "text" };
64
+ import SSAO_FS from "./shaders/post-process/ssao.wgsl?raw" with { type: "text" };
65
+ import SSR_FS from "./shaders/post-process/ssr.wgsl?raw" with { type: "text" };
66
+ import TAA_FS from "./shaders/post-process/taa.wgsl?raw" with { type: "text" };
67
+ import TONEMAP_FS from "./shaders/post-process/tonemap.wgsl?raw" with { type: "text" };
68
+ import WATERCOLOR_FS from "./shaders/post-process/watercolor.wgsl?raw" with { type: "text" };
69
+ import WHITE_BALANCE_FS from "./shaders/post-process/white-balance.wgsl?raw" with { type: "text" };
70
+
71
+ const log = createLogger("info");
72
+
73
+ // ── Effect IDs ──────────────────────────────────────────────────────────────
74
+
75
+ export type EffectId =
76
+ | "taa" | "ssao" | "ssr" | "dof" | "motion-blur"
77
+ | "bloom" | "bloom-soft" | "tonemap"
78
+ // Color grading (LDR, post-tonemap)
79
+ | "lut" | "white-balance" | "channel-mixer" | "split-tone"
80
+ // Camera/lens (LDR)
81
+ | "fxaa" | "chromatic-aberration" | "lens-distortion" | "sharpen" | "grain"
82
+ // Stylized (LDR, late chain)
83
+ | "sobel" | "edges" | "lens-flare"
84
+ | "pixelation" | "gaussian-blur" | "afterimage"
85
+ | "halftone" | "dithering" | "watercolor"
86
+ | "outline" | "highlight" | "glow" | "ascii";
87
+
88
+ // ── Custom (mod-defined) effects ──
89
+ //
90
+ // Mods can register custom post-processing effects via `registerCustomEffect`.
91
+ // A custom effect is a WGSL fragment shader that runs as a full-screen pass
92
+ // with the `ccLayout` bind group (color input + dummy + sampler + uniform).
93
+ // Custom effects are inserted into the chain at the position specified by
94
+ // their `order` field, after the built-in effects in the same order group.
95
+
96
+ /** Where in the postfx chain a custom effect runs. */
97
+ export type CustomEffectOrder = "hdr" | "color-grading" | "camera" | "stylized";
98
+
99
+ /** A mod-defined post-processing effect. */
100
+ export interface CustomEffect {
101
+ /** Unique effect id (namespaced, e.g. "my-mod:acid"). */
102
+ id: string;
103
+ /** Display name. */
104
+ name: string;
105
+ /** WGSL fragment shader source (the `fs_main` entry point). */
106
+ wgsl: string;
107
+ /** Bind group layout the shader expects. Currently only "cc" is supported. */
108
+ layout: "cc";
109
+ /** Where in the chain this effect runs. */
110
+ order: CustomEffectOrder;
111
+ /** Uniform buffer size in bytes (default 64). The uniform is at binding 3. */
112
+ uniforms?: number;
113
+ /** User-configurable settings (from the mod manifest). */
114
+ settings?: Array<{ key: string; label: string; type: "slider" | "toggle" | "select"; default: number | boolean | string; min?: number; max?: number; step?: number; options?: Array<{ label: string; value: string }> }>;
115
+ }
116
+
117
+ const ALL_EFFECTS: EffectId[] = [
118
+ "taa", "ssao", "ssr", "dof", "motion-blur",
119
+ "bloom", "bloom-soft", "tonemap",
120
+ "lut", "white-balance", "channel-mixer", "split-tone",
121
+ "fxaa", "chromatic-aberration", "lens-distortion", "sharpen", "grain",
122
+ "sobel", "edges", "lens-flare",
123
+ "pixelation", "gaussian-blur", "afterimage",
124
+ "halftone", "dithering", "watercolor",
125
+ "outline", "highlight", "glow", "ascii",
126
+ ];
127
+
128
+ const CHAIN_ORDER: EffectId[] = [
129
+ // HDR
130
+ "taa", "ssao", "ssr", "dof", "motion-blur",
131
+ "bloom", "bloom-soft", "tonemap",
132
+ // Color grading (LDR, post-tonemap)
133
+ "lut", "white-balance", "channel-mixer", "split-tone",
134
+ // Camera/lens (LDR)
135
+ "fxaa", "chromatic-aberration", "lens-distortion", "sharpen", "grain",
136
+ // Stylized (LDR, late chain)
137
+ "sobel", "edges", "lens-flare",
138
+ "pixelation", "gaussian-blur", "afterimage",
139
+ "halftone", "dithering", "watercolor",
140
+ "outline", "highlight", "glow", "ascii",
141
+ ];
142
+
143
+ const EFFECT_NAMES: Record<EffectId, string> = {
144
+ taa: "TAA", ssao: "SSAO", ssr: "SSR", dof: "DOF", "motion-blur": "Motion Blur",
145
+ bloom: "Bloom", "bloom-soft": "Bloom (Soft)", tonemap: "Tonemap",
146
+ lut: "LUT (3D)", "white-balance": "White Balance", "channel-mixer": "Channel Mixer",
147
+ "split-tone": "Split Tone",
148
+ fxaa: "FXAA", "chromatic-aberration": "Chromatic Aberration",
149
+ "lens-distortion": "Lens Distortion", sharpen: "Sharpen", grain: "Grain",
150
+ sobel: "Sobel", edges: "Edges", "lens-flare": "Lens Flare",
151
+ pixelation: "Pixelation", "gaussian-blur": "Gaussian Blur", afterimage: "Afterimage",
152
+ halftone: "Halftone", dithering: "Dithering", watercolor: "Watercolor",
153
+ outline: "Outline", highlight: "Highlight", glow: "Glow", ascii: "ASCII",
154
+ };
155
+
156
+ const HDR_FORMAT: GPUTextureFormat = "rgba16float";
157
+
158
+ // ── PostProcessStack ────────────────────────────────────────────────────────
159
+
160
+ export class PostProcessStack {
161
+ private device: GPUDevice;
162
+ private format: GPUTextureFormat;
163
+ private depthFormat: GPUTextureFormat;
164
+ private sceneFormat: GPUTextureFormat;
165
+
166
+ // Bind group layouts (5 shared layouts)
167
+ private ccLayout!: GPUBindGroupLayout; // float + float + filtering + uniform
168
+ private cdLayout!: GPUBindGroupLayout; // float + depth + non-filtering + uniform
169
+ private cvvhLayout!: GPUBindGroupLayout; // float + float + float + filtering + uniform
170
+ private cvdLayout!: GPUBindGroupLayout; // float + float + depth + non-filtering + uniform
171
+ private dnfnLayout!: GPUBindGroupLayout; // depth + float + float + non-filtering + uniform
172
+
173
+ private linearSampler!: GPUSampler;
174
+ private nearestSampler!: GPUSampler;
175
+
176
+ // Pipelines + uniform buffers (keyed by pipeline name)
177
+ private pipelines: Record<string, GPURenderPipeline> = {};
178
+ private uniforms: Record<string, GPUBuffer> = {};
179
+
180
+ // Occlusion culling — shared bind group (group 1) bound on every pass.
181
+ // Contains up to 8 opaque UI panel rects in UV space; shaders discard
182
+ // fragments inside these rects to skip work where the output is invisible.
183
+ private occluderLayout!: GPUBindGroupLayout;
184
+ private occluderUniform!: GPUBuffer;
185
+ private occluderBindGroup!: GPUBindGroup;
186
+ private occluderRects: { x: number; y: number; w: number; h: number }[] = [];
187
+ private occluderKey = "";
188
+ private taaHistoryReset = false;
189
+
190
+ // Render targets
191
+ private sceneColor: GPUTexture | null = null;
192
+ private sceneDepth: GPUTexture | null = null;
193
+ private sceneNormals: GPUTexture | null = null;
194
+ private sceneVelocity: GPUTexture | null = null;
195
+ private sceneMask: GPUTexture | null = null;
196
+ private pingPong: [GPUTexture | null, GPUTexture | null] = [null, null];
197
+
198
+ // Effect-specific targets
199
+ private afterimageTex: GPUTexture | null = null;
200
+ private taaHistory: GPUTexture | null = null;
201
+ private taaHistory2: GPUTexture | null = null;
202
+ private bloomBright: GPUTexture | null = null;
203
+ private bloomBlurH: GPUTexture | null = null;
204
+ private bloomBlurV: GPUTexture | null = null;
205
+ private bloomMip: GPUTexture[] = []; // multi-MIP pyramid (downsample cascade)
206
+ private bloomTemp: GPUTexture[] = []; // temp targets for upsample (avoid read+write same texture)
207
+ private bloomBase: GPUTexture | null = null; // full-res scene copy for the final composite (avoid read+write same texture)
208
+ private halfResA: GPUTexture | null = null; // shared by bloom-soft
209
+ private halfResB: GPUTexture | null = null;
210
+ private ssaoA: GPUTexture | null = null;
211
+ private ssaoB: GPUTexture | null = null;
212
+ private blurA: GPUTexture | null = null; // shared by gaussian-blur, highlight, glow
213
+ private blurB: GPUTexture | null = null;
214
+
215
+ // Static textures
216
+ private dummyTex!: GPUTexture;
217
+ private glyphTex: GPUTexture | null = null;
218
+ private noiseTex: GPUTexture | null = null;
219
+
220
+ // Bloom-soft result (stored for tonemap to read)
221
+ private bloomSoftResult: GPUTextureView | null = null;
222
+
223
+ // Dimensions
224
+ private texW = 0;
225
+ private texH = 0;
226
+
227
+ // Enabled state
228
+ private enabled: Record<EffectId, boolean> = Object.fromEntries(
229
+ ALL_EFFECTS.map(e => [e, false])
230
+ ) as Record<EffectId, boolean>;
231
+ private prevEnabled: Record<EffectId, boolean> = { ...this.enabled };
232
+
233
+ // Custom (mod-defined) effects: id → { effect, pipeline, uniform, enabled }
234
+ private customEffects = new Map<string, {
235
+ effect: CustomEffect;
236
+ pipeline: GPURenderPipeline;
237
+ uniform: GPUBuffer;
238
+ uniformValues: Float32Array | null;
239
+ enabled: boolean;
240
+ }>();
241
+
242
+ // Settings
243
+ private grainTime = 0;
244
+ private lightScreenPos: [number, number] = [0.5, 0.5];
245
+ private camProj: Float32Array | null = null;
246
+ private camInvProj: Float32Array | null = null;
247
+ private camView: Float32Array | null = null;
248
+
249
+ // DOF
250
+ private dofFocusDist = 0.5;
251
+ private dofFocusRange = 0.3;
252
+ private dofMaxBlur = 8.0;
253
+ private dofBokehShape = 0; // 0=circle, 1=hexagon, 2=octagon
254
+ private dofSampleCount = 32;
255
+ private dofNearOnly = false;
256
+ private dofFarOnly = false;
257
+ private dofBladeRotation = 0.0;
258
+ // Afterimage
259
+ private afterimageDamp = 0.96;
260
+ // Bloom
261
+ private bloomThreshold = 0.8;
262
+ private bloomStrength = 1.0;
263
+ private bloomMipCount = 5; // number of downsample levels (full→½→¼→⅛→1/16)
264
+ private bloomTint: [number, number, number] = [1.0, 1.0, 1.0];
265
+ private bloomSoftKnee = 0.7; // soft threshold knee for bright pass
266
+ // Per-MIP upsample weights (index 0 = largest MIP, last = smallest)
267
+ private bloomMipWeights: number[] = [0.0, 0.0, 0.4, 0.6, 0.8, 1.0];
268
+ // Bloom-soft
269
+ private bloomSoftThreshold = 1.0;
270
+ private bloomSoftSoftThreshold = 0.5;
271
+ private bloomSoftIntensity = 0.3;
272
+ // Tonemap
273
+ private exposure = 1.0;
274
+ private gamma = 2.2;
275
+ private contrast = 1.0;
276
+ private saturation = 1.0;
277
+ private vignette = 0.3;
278
+ // TAA
279
+ private taaBlendFactor = 0.1;
280
+ private taaVarianceClamp = false;
281
+ private taaJitterX = 0;
282
+ private taaJitterY = 0;
283
+ // SSAO (GTAO)
284
+ private ssaoRadius = 0.5;
285
+ private ssaoBias = 0.025;
286
+ private ssaoKernelSize = 32; // compat alias → maps to ssaoDirections
287
+ private ssaoDirections = 4; // GTAO slice directions
288
+ private ssaoSlices = 8; // GTAO samples per slice
289
+ private ssaoPower = 1.5; // AO contrast power
290
+ private ssaoThickness = 0.1; // horizon depth thickness
291
+ // SSR (DDA + binary refinement)
292
+ private ssrMaxSteps = 64;
293
+ private ssrThickness = 0.05;
294
+ private ssrMaxDistance = 100.0;
295
+ private ssrFadeStart = 10.0;
296
+ private ssrFadeEnd = 50.0;
297
+ private ssrBinarySteps = 10;
298
+ private ssrStride = 1.0;
299
+ // Motion blur
300
+ private motionBlurIntensity = 1.0;
301
+ private motionBlurMaxSamples = 16;
302
+ // Sharpen
303
+ private sharpenSharpness = 0.5;
304
+ // Grain
305
+ private grainIntensity = 0.15;
306
+ private grainSize = 2.0;
307
+ private grainLuminanceAware = false;
308
+ // Edges
309
+ private edgesThreshold = 0.4;
310
+ private edgesOpacity = 1.0;
311
+ private edgesEdgeColor: [number, number, number] = [1.0, 1.0, 1.0];
312
+ // Lens flare
313
+ private lensFlareIntensity = 0.8;
314
+ private lensFlareThreshold = 0.9;
315
+ private lensFlareGhostCount = 8;
316
+ private lensFlareGhostSpacing = 0.15;
317
+ private lensFlareHaloWidth = 0.2;
318
+ private lensFlareTint: [number, number, number] = [1.0, 0.9, 0.8];
319
+ // Pixelation
320
+ private pixelationPixelSize = 6;
321
+ private pixelationDepthEdgeStrength = 0.4;
322
+ // Gaussian blur
323
+ private gaussianBlurRadius = 2.0;
324
+ // Outline
325
+ private outlineColor: [number, number, number] = [1.0, 0.8, 0.2];
326
+ private outlineWidth = 2.0;
327
+ private outlineOpacity = 1.0;
328
+ // Highlight
329
+ private highlightIntensity = 0.8;
330
+ private highlightInnerOpacity = 0.3;
331
+ private highlightBlurRadius = 3.0;
332
+ // Glow
333
+ private glowIntensity = 1.0;
334
+ private glowBlurRadius = 4.0;
335
+ // ASCII
336
+ private asciiCellSize = 8;
337
+ private asciiUseColor = 1.0;
338
+ // LUT (3D color grading)
339
+ private lutTexture: GPUTexture | null = null;
340
+ private lutView: GPUTextureView | null = null;
341
+ private lutSize = 32;
342
+ private lutEnabled = true;
343
+ private lutLayout!: GPUBindGroupLayout;
344
+ // White balance
345
+ private wbTemperature = 0.0;
346
+ private wbTint = 0.0;
347
+ // Channel mixer
348
+ private cmWeights: [number, number, number, number, number, number, number, number, number] = [1, 0, 0, 0, 1, 0, 0, 0, 1];
349
+ private cmMonochrome = false;
350
+ // Split-tone
351
+ private stShadow: [number, number, number] = [0.0, 0.0, 0.0];
352
+ private stHighlight: [number, number, number] = [0.0, 0.0, 0.0];
353
+ private stBalance = 0.0;
354
+ // Chromatic aberration
355
+ private caIntensity = 0.5;
356
+ private caStart = 0.0;
357
+ private caEnd = 1.0;
358
+ private caCenterX = 0.5;
359
+ private caCenterY = 0.5;
360
+ // Lens distortion
361
+ private ldIntensity = 0.0;
362
+ private ldScale = 1.0;
363
+ private ldChromaSplit = 0.0;
364
+ // Halftone
365
+ private htCellSize = 8.0;
366
+ private htDotScale = 1.0;
367
+ private htAngle = 0.0;
368
+ private htMonochrome = true;
369
+ // Dithering
370
+ private dMode = 1; // 0=off, 1=bayer, 2=bluenoise
371
+ private dStrength = 0.5;
372
+ private dLevels = 0; // 0 = no quantization
373
+ // Watercolor
374
+ private wcEdgeStrength = 1.0;
375
+ private wcPaperScale = 2.0;
376
+ private wcBlend = 0.7;
377
+
378
+ constructor(device: GPUDevice, format: GPUTextureFormat, options?: PostProcessStackOptions) {
379
+ this.device = device;
380
+ this.format = format;
381
+ this.depthFormat = options?.depthFormat ?? "depth32float";
382
+ this.sceneFormat = options?.sceneFormat ?? HDR_FORMAT;
383
+ }
384
+
385
+ // ── Init ──────────────────────────────────────────────────────────────────
386
+
387
+ init(): void {
388
+ this.linearSampler = this.device.createSampler({
389
+ magFilter: "linear", minFilter: "linear",
390
+ addressModeU: "clamp-to-edge", addressModeV: "clamp-to-edge",
391
+ });
392
+ this.nearestSampler = this.device.createSampler({
393
+ magFilter: "nearest", minFilter: "nearest",
394
+ addressModeU: "clamp-to-edge", addressModeV: "clamp-to-edge",
395
+ });
396
+
397
+ // 5 shared bind group layouts
398
+ this.ccLayout = this.device.createBindGroupLayout({ entries: [
399
+ { binding: 0, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
400
+ { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
401
+ { binding: 2, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
402
+ { binding: 3, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
403
+ ]});
404
+ this.cdLayout = this.device.createBindGroupLayout({ entries: [
405
+ { binding: 0, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
406
+ { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "depth" } },
407
+ { binding: 2, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "non-filtering" } },
408
+ { binding: 3, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
409
+ ]});
410
+ this.cvvhLayout = this.device.createBindGroupLayout({ entries: [
411
+ { binding: 0, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
412
+ { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
413
+ { binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
414
+ { binding: 3, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
415
+ { binding: 4, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
416
+ ]});
417
+ this.cvdLayout = this.device.createBindGroupLayout({ entries: [
418
+ { binding: 0, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
419
+ { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
420
+ { binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "depth" } },
421
+ { binding: 3, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "non-filtering" } },
422
+ { binding: 4, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
423
+ ]});
424
+ this.dnfnLayout = this.device.createBindGroupLayout({ entries: [
425
+ { binding: 0, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "depth" } },
426
+ { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
427
+ { binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
428
+ { binding: 3, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "non-filtering" } },
429
+ { binding: 4, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
430
+ ]});
431
+ // LUT layout: color2D + lut3D + sampler + uniform
432
+ this.lutLayout = this.device.createBindGroupLayout({ entries: [
433
+ { binding: 0, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
434
+ { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float", viewDimension: "3d" } },
435
+ { binding: 2, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
436
+ { binding: 3, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
437
+ ]});
438
+
439
+ // Dummy texture (1×1 black)
440
+ this.dummyTex = this.device.createTexture({
441
+ size: [1, 1], format: "rgba8unorm",
442
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
443
+ });
444
+ this.device.queue.writeTexture(
445
+ { texture: this.dummyTex },
446
+ new Uint8Array([0, 0, 0, 255]),
447
+ { bytesPerRow: 4, rowsPerImage: 1 }, [1, 1],
448
+ );
449
+
450
+ // Occlusion culling bind group (group 1, shared by all postfx pipelines).
451
+ // Layout: single uniform buffer at binding 0, FRAGMENT visibility.
452
+ // Buffer: 16-byte header (count + padding) + 8 × vec4 rects = 144 bytes.
453
+ this.occluderLayout = this.device.createBindGroupLayout({ entries: [
454
+ { binding: 0, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
455
+ ]});
456
+ this.occluderUniform = this.device.createBuffer({
457
+ size: 144,
458
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
459
+ });
460
+ this.occluderBindGroup = this.device.createBindGroup({
461
+ layout: this.occluderLayout,
462
+ entries: [{ binding: 0, resource: { buffer: this.occluderUniform } }],
463
+ });
464
+ // Initialize to zero occluders (no occlusion).
465
+ this.device.queue.writeBuffer(this.occluderUniform, 0, new Float32Array(36));
466
+
467
+ // Uniform buffers — 48 bytes for simple effects, 256 for matrix effects
468
+ const simpleKeys = [
469
+ "fxaa", "dof", "sobel", "afterimage", "bloom-bright", "bloom-blur", "bloom-composite",
470
+ "bloom-downsample", "bloom-upsample",
471
+ "ascii", "blit", "taa", "motion-blur", "bloom-soft", "tonemap", "sharpen", "grain",
472
+ "edges", "lens-flare", "pixelation", "gaussian-blur", "outline",
473
+ "ssao-blur", "ssao-composite", "highlight-blur", "highlight-composite",
474
+ "glow-blur", "glow-composite",
475
+ // New effects
476
+ "lut", "white-balance", "channel-mixer", "split-tone",
477
+ "chromatic-aberration", "lens-distortion",
478
+ "halftone", "dithering", "watercolor",
479
+ ];
480
+ simpleKeys.forEach((k) => { this.uniforms[k] = this.device.createBuffer({ size: 64, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });; });
481
+ this.uniforms["ssao"] = this.device.createBuffer({ size: 256, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
482
+ this.uniforms["ssr"] = this.device.createBuffer({ size: 256, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
483
+
484
+ // Pipelines
485
+ const mk = (fs: string, layout: GPUBindGroupLayout, fmt: GPUTextureFormat = HDR_FORMAT) =>
486
+ this.makePipeline(fs, layout, fmt);
487
+
488
+ this.pipelines["fxaa"] = mk(FXAA_FS, this.ccLayout);
489
+ this.pipelines["dof"] = mk(DOF_FS, this.cdLayout);
490
+ this.pipelines["sobel"] = mk(SOBEL_FS, this.ccLayout);
491
+ this.pipelines["afterimage"] = mk(AFTERIMAGE_FS, this.ccLayout);
492
+ this.pipelines["bloom-bright"] = mk(BLOOM_BRIGHT_FS, this.ccLayout);
493
+ this.pipelines["bloom-blur"] = mk(BLOOM_BLUR_FS, this.ccLayout);
494
+ this.pipelines["bloom-composite"] = mk(BLOOM_COMPOSITE_FS, this.ccLayout);
495
+ this.pipelines["bloom-downsample"] = mk(BLOOM_DOWNSAMPLE_FS, this.ccLayout);
496
+ this.pipelines["bloom-upsample"] = mk(BLOOM_UPSAMPLE_FS, this.ccLayout);
497
+ this.pipelines["ascii"] = mk(ASCII_FS, this.ccLayout);
498
+ this.pipelines["blit"] = mk(BLIT_FS, this.ccLayout, this.format);
499
+ // HDR blit variant for intermediate passes inside the HDR chain section
500
+ this.pipelines["blit-hdr"] = mk(BLIT_FS, this.ccLayout, HDR_FORMAT);
501
+ this.pipelines["taa"] = mk(TAA_FS, this.cvvhLayout);
502
+ this.pipelines["ssao"] = mk(SSAO_FS, this.dnfnLayout);
503
+ this.pipelines["ssao-blur"] = mk(SSAO_BLUR_FS, this.cdLayout);
504
+ this.pipelines["ssao-composite"] = mk(SSAO_COMPOSITE_FS, this.ccLayout);
505
+ this.pipelines["ssr"] = mk(SSR_FS, this.cvdLayout);
506
+ this.pipelines["motion-blur"] = mk(MOTION_BLUR_FS, this.cvdLayout);
507
+ this.pipelines["bloom-soft"] = mk(BLOOM_SOFT_FS, this.ccLayout);
508
+ this.pipelines["tonemap"] = mk(TONEMAP_FS, this.ccLayout);
509
+ this.pipelines["sharpen"] = mk(SHARPEN_FS, this.ccLayout);
510
+ this.pipelines["grain"] = mk(GRAIN_FS, this.ccLayout);
511
+ this.pipelines["edges"] = mk(EDGES_FS, this.cvdLayout);
512
+ this.pipelines["lens-flare"] = mk(LENS_FLARE_FS, this.cdLayout);
513
+ this.pipelines["pixelation"] = mk(PIXELATION_FS, this.cdLayout);
514
+ this.pipelines["gaussian-blur"] = mk(GAUSSIAN_BLUR_FS, this.ccLayout);
515
+ this.pipelines["outline"] = mk(OUTLINE_FS, this.ccLayout);
516
+ this.pipelines["highlight-blur"] = mk(HIGHLIGHT_BLUR_FS, this.ccLayout);
517
+ this.pipelines["highlight-composite"] = mk(HIGHLIGHT_COMPOSITE_FS, this.cvvhLayout);
518
+ this.pipelines["glow-blur"] = mk(GLOW_BLUR_FS, this.ccLayout);
519
+ this.pipelines["glow-composite"] = mk(GLOW_COMPOSITE_FS, this.ccLayout);
520
+ // New effects
521
+ this.pipelines["lut"] = mk(LUT_FS, this.lutLayout);
522
+ this.pipelines["white-balance"] = mk(WHITE_BALANCE_FS, this.ccLayout);
523
+ this.pipelines["channel-mixer"] = mk(CHANNEL_MIXER_FS, this.ccLayout);
524
+ this.pipelines["split-tone"] = mk(SPLIT_TONE_FS, this.ccLayout);
525
+ this.pipelines["chromatic-aberration"] = mk(CHROMATIC_ABERRATION_FS, this.ccLayout);
526
+ this.pipelines["lens-distortion"] = mk(LENS_DISTORTION_FS, this.ccLayout);
527
+ this.pipelines["halftone"] = mk(HALFTONE_FS, this.ccLayout);
528
+ this.pipelines["dithering"] = mk(DITHERING_FS, this.ccLayout); // color + noise + sampler + uniform
529
+ this.pipelines["watercolor"] = mk(WATERCOLOR_FS, this.ccLayout); // color + noise + sampler + uniform
530
+
531
+ this.createGlyphAtlas();
532
+ this.createNoiseTexture();
533
+ }
534
+
535
+ private makePipeline(fsCode: string, layout: GPUBindGroupLayout, targetFormat: GPUTextureFormat): GPURenderPipeline {
536
+ const shader = createValidatedShaderModule(this.device, { code: VS + "\n" + OCCLUDER_CHUNK + "\n" + fsCode, label: "PostProcessStack" });
537
+ const pl = this.device.createPipelineLayout({ bindGroupLayouts: [layout, this.occluderLayout] });
538
+ return this.device.createRenderPipeline({
539
+ layout: pl,
540
+ vertex: { module: shader, entryPoint: "vs_main" },
541
+ fragment: { module: shader, entryPoint: "fs_main", targets: [{ format: targetFormat }] },
542
+ primitive: { topology: "triangle-list" },
543
+ });
544
+ }
545
+
546
+ private createGlyphAtlas(): void {
547
+ const chars = " .:-=+*#%@";
548
+ const gw = 16, gh = 16, cols = chars.length;
549
+ const canvas = document.createElement("canvas");
550
+ canvas.width = cols * gw; canvas.height = gh;
551
+ const ctx = canvas.getContext("2d")!;
552
+ ctx.fillStyle = "black"; ctx.fillRect(0, 0, canvas.width, canvas.height);
553
+ ctx.fillStyle = "white"; ctx.font = `bold ${gh - 2}px monospace`;
554
+ ctx.textAlign = "center"; ctx.textBaseline = "middle";
555
+ for (let i = 0; i < chars.length; i++) ctx.fillText(chars[i], i * gw + gw / 2, gh / 2);
556
+ this.glyphTex = this.device.createTexture({
557
+ size: [canvas.width, canvas.height], format: "rgba8unorm",
558
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
559
+ });
560
+ this.device.queue.copyExternalImageToTexture({ source: canvas }, { texture: this.glyphTex }, [canvas.width, canvas.height]);
561
+ }
562
+
563
+ private createNoiseTexture(): void {
564
+ const size = 4;
565
+ const data = new Uint8Array(size * size * 4);
566
+ for (let i = 0; i < data.length; i += 4) {
567
+ data[i] = Math.floor(Math.random() * 255);
568
+ data[i + 1] = Math.floor(Math.random() * 255);
569
+ data[i + 2] = 0; data[i + 3] = 255;
570
+ }
571
+ this.noiseTex = this.device.createTexture({
572
+ size: [size, size], format: "rgba8unorm",
573
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
574
+ });
575
+ this.device.queue.writeTexture(
576
+ { texture: this.noiseTex }, data,
577
+ { bytesPerRow: size * 4 }, [size, size],
578
+ );
579
+ }
580
+
581
+ // ── Public API: queries ───────────────────────────────────────────────────
582
+
583
+ hasEnabledEffects(): boolean {
584
+ if ((Object.values(this.enabled) as boolean[]).some(v => v)) return true;
585
+ for (const [, e] of this.customEffects.entries()) if (e.enabled) return true;
586
+ return false;
587
+ }
588
+
589
+ getEnabledEffects(): string[] {
590
+ const builtin = CHAIN_ORDER.filter(id => this.enabled[id]).map(id => EFFECT_NAMES[id]);
591
+ const custom = [...this.customEffects.values()].filter(e => e.enabled).map(e => e.effect.name);
592
+ return [...builtin, ...custom];
593
+ }
594
+
595
+ isEnabled(id: EffectId): boolean { return this.enabled[id]; }
596
+
597
+ // ── Public API: occlusion culling ──────────────────────────────────────────
598
+
599
+ /**
600
+ * Set opaque UI panel rects (in UV space, 0-1, top-left origin) that the
601
+ * postfx shaders should skip. Up to 8 rects; extras are clamped. When the
602
+ * rects change, TAA history is reset for one frame to avoid stale-history
603
+ * artifacts in the previously-occluded region.
604
+ */
605
+ setOccluderRects(rects: { x: number; y: number; w: number; h: number }[]): void {
606
+ const clamped = rects.slice(0, 8);
607
+ const key = clamped.map(r => `${r.x.toFixed(4)},${r.y.toFixed(4)},${r.w.toFixed(4)},${r.h.toFixed(4)}`).join("|");
608
+ if (key !== this.occluderKey) {
609
+ this.occluderKey = key;
610
+ this.taaHistoryReset = true;
611
+ }
612
+ this.occluderRects = clamped;
613
+ // Build uniform data: [count, 0, 0, 0, rect0.xyzw, rect1.xyzw, ...]
614
+ const data = new Float32Array(36);
615
+ data[0] = clamped.length;
616
+ for (let i = 0; i < clamped.length && i < 8; i++) {
617
+ data[4 + i * 4] = clamped[i].x;
618
+ data[4 + i * 4 + 1] = clamped[i].y;
619
+ data[4 + i * 4 + 2] = clamped[i].w;
620
+ data[4 + i * 4 + 3] = clamped[i].h;
621
+ }
622
+ this.device.queue.writeBuffer(this.occluderUniform, 0, data as unknown as Float32Array<ArrayBuffer>);
623
+ }
624
+
625
+ /** Returns true if any single occluder rect covers ≥95% of UV space. */
626
+ isFullyOccluded(): boolean {
627
+ return this.occluderRects.some(r => r.w * r.h >= 0.95);
628
+ }
629
+
630
+ getOccluderRects(): { x: number; y: number; w: number; h: number }[] {
631
+ return this.occluderRects;
632
+ }
633
+
634
+ // ── Public API: enable/disable ────────────────────────────────────────────
635
+
636
+ setEnabled(id: EffectId, enabled: boolean): void {
637
+ this.enabled[id] = enabled;
638
+ }
639
+
640
+ // ── Public API: custom (mod-defined) effects ──────────────────────────────
641
+
642
+ /** Register a mod-defined custom effect. Creates a render pipeline from the
643
+ * provided WGSL fragment shader. The effect is disabled by default; call
644
+ * `setCustomEffectEnabled` to turn it on. Throws if the id is already
645
+ * registered or the WGSL fails to compile. */
646
+ registerCustomEffect(effect: CustomEffect): void {
647
+ if (this.customEffects.has(effect.id)) {
648
+ throw new Error(`Custom effect "${effect.id}" is already registered`);
649
+ }
650
+ if (effect.layout !== "cc") {
651
+ throw new Error(`Custom effect "${effect.id}": only layout "cc" is supported (got "${effect.layout}")`);
652
+ }
653
+ const uniformSize = effect.uniforms ?? 64;
654
+ const uniform = this.device.createBuffer({
655
+ size: uniformSize,
656
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
657
+ });
658
+ let pipeline: GPURenderPipeline;
659
+ try {
660
+ pipeline = this.makePipeline(effect.wgsl, this.ccLayout, HDR_FORMAT);
661
+ } catch (e) {
662
+ log.error("PostProcessStack", `Failed to compile custom effect "${effect.id}": ${e}`);
663
+ throw e;
664
+ }
665
+ this.customEffects.set(effect.id, { effect, pipeline, uniform, uniformValues: null, enabled: false });
666
+ }
667
+
668
+ /** Unregister a custom effect. Destroys its pipeline + uniform buffer. */
669
+ unregisterCustomEffect(id: string): void {
670
+ const e = this.customEffects.get(id);
671
+ if (!e) return;
672
+ e.pipeline.destroy?.();
673
+ e.uniform.destroy();
674
+ this.customEffects.delete(id);
675
+ }
676
+
677
+ /** Enable/disable a custom effect. */
678
+ setCustomEffectEnabled(id: string, enabled: boolean): void {
679
+ const e = this.customEffects.get(id);
680
+ if (!e) throw new Error(`Custom effect "${id}" is not registered`);
681
+ e.enabled = enabled;
682
+ }
683
+
684
+ /** Check if a custom effect is enabled. */
685
+ isCustomEffectEnabled(id: string): boolean {
686
+ return this.customEffects.get(id)?.enabled ?? false;
687
+ }
688
+
689
+ /** Get all registered custom effect ids. */
690
+ getCustomEffectIds(): string[] {
691
+ return [...this.customEffects.keys()];
692
+ }
693
+
694
+ /** Get a custom effect's metadata (name, settings, enabled state, uniform size). */
695
+ getCustomEffectInfo(id: string): { name: string; enabled: boolean; uniforms: number; settings?: CustomEffect["settings"] } | null {
696
+ const e = this.customEffects.get(id);
697
+ if (!e) return null;
698
+ return { name: e.effect.name, enabled: e.enabled, uniforms: e.uniform.size, settings: e.effect.settings };
699
+ }
700
+
701
+ /** Get all custom effects with their metadata. */
702
+ getCustomEffects(): Array<{ id: string; name: string; enabled: boolean; settings?: CustomEffect["settings"] }> {
703
+ return [...this.customEffects.entries()].map(([id, e]) => ({
704
+ id, name: e.effect.name, enabled: e.enabled, settings: e.effect.settings,
705
+ }));
706
+ }
707
+
708
+ /** Write uniform data for a custom effect. */
709
+ setCustomEffectUniform(id: string, data: Float32Array): void {
710
+ const e = this.customEffects.get(id);
711
+ if (!e) throw new Error(`Custom effect "${id}" is not registered`);
712
+ // Store user settings (offset 3+); per-frame values (0-2) are written in applyCustomEffect.
713
+ const stored = new Float32Array(e.uniform.size / 4);
714
+ for (let i = 0; i < data.length && i < stored.length; i++) stored[i] = data[i];
715
+ e.uniformValues = stored;
716
+ this.device.queue.writeBuffer(e.uniform, 0, stored as unknown as Float32Array<ArrayBuffer>);
717
+ }
718
+
719
+ // ── Public API: per-effect setters ────────────────────────────────────────
720
+
721
+ setDOFFocusDist(v: number): void { this.dofFocusDist = v; }
722
+ setDOFFocusRange(v: number): void { this.dofFocusRange = v; }
723
+ setDOFMaxBlur(v: number): void { this.dofMaxBlur = v; }
724
+ setDOFBokehShape(v: number): void { this.dofBokehShape = Math.floor(v); }
725
+ setDOFSampleCount(v: number): void { this.dofSampleCount = Math.floor(v); }
726
+ setDOFNearOnly(v: boolean): void { this.dofNearOnly = v; }
727
+ setDOFFarOnly(v: boolean): void { this.dofFarOnly = v; }
728
+ setDOFBladeRotation(v: number): void { this.dofBladeRotation = v; }
729
+ setAfterimageDamp(v: number): void { this.afterimageDamp = v; }
730
+ setBloomThreshold(v: number): void { this.bloomThreshold = v; }
731
+ setBloomStrength(v: number): void { this.bloomStrength = v; }
732
+ setBloomMipCount(v: number): void { this.bloomMipCount = Math.max(1, Math.min(6, Math.floor(v))); }
733
+ setBloomTint(r: number, g: number, b: number): void { this.bloomTint = [r, g, b]; }
734
+ setBloomSoftKnee(v: number): void { this.bloomSoftKnee = v; }
735
+ setBloomMipWeights(weights: number[]): void { this.bloomMipWeights = weights; }
736
+ setBloomSoftThreshold(v: number): void { this.bloomSoftThreshold = v; }
737
+ setBloomSoftSoftThreshold(v: number): void { this.bloomSoftSoftThreshold = v; }
738
+ setBloomSoftIntensity(v: number): void { this.bloomSoftIntensity = v; }
739
+ setExposure(v: number): void { this.exposure = v; }
740
+ setGamma(v: number): void { this.gamma = v; }
741
+ setContrast(v: number): void { this.contrast = v; }
742
+ setSaturation(v: number): void { this.saturation = v; }
743
+ setVignette(v: number): void { this.vignette = v; }
744
+ setTAABlendFactor(v: number): void { this.taaBlendFactor = v; }
745
+ setTAAVarianceClamp(v: boolean): void { this.taaVarianceClamp = v; }
746
+ setJitter(x: number, y: number): void { this.taaJitterX = x; this.taaJitterY = y; }
747
+ setSSAORadius(v: number): void { this.ssaoRadius = v; }
748
+ setSSAOBias(v: number): void { this.ssaoBias = v; }
749
+ setSSAOKernelSize(v: number): void { this.ssaoKernelSize = Math.floor(v); this.ssaoDirections = Math.floor(v); }
750
+ setSSAODirections(v: number): void { this.ssaoDirections = Math.floor(v); }
751
+ setSSAOSlices(v: number): void { this.ssaoSlices = Math.floor(v); }
752
+ setSSAOPower(v: number): void { this.ssaoPower = v; }
753
+ setSSAOThickness(v: number): void { this.ssaoThickness = v; }
754
+ setSSRMaxSteps(v: number): void { this.ssrMaxSteps = Math.floor(v); }
755
+ setSSRThickness(v: number): void { this.ssrThickness = v; }
756
+ setSSRMaxDistance(v: number): void { this.ssrMaxDistance = v; }
757
+ setSSRFadeStart(v: number): void { this.ssrFadeStart = v; }
758
+ setSSRFadeEnd(v: number): void { this.ssrFadeEnd = v; }
759
+ setSSRBinarySteps(v: number): void { this.ssrBinarySteps = Math.floor(v); }
760
+ setSSRStride(v: number): void { this.ssrStride = v; }
761
+ setMotionBlurIntensity(v: number): void { this.motionBlurIntensity = v; }
762
+ setMotionBlurMaxSamples(v: number): void { this.motionBlurMaxSamples = Math.floor(v); }
763
+ setSharpenSharpness(v: number): void { this.sharpenSharpness = v; }
764
+ setGrainIntensity(v: number): void { this.grainIntensity = v; }
765
+ setGrainSize(v: number): void { this.grainSize = v; }
766
+ setGrainLuminanceAware(v: boolean): void { this.grainLuminanceAware = v; }
767
+ setEdgesThreshold(v: number): void { this.edgesThreshold = v; }
768
+ setEdgesOpacity(v: number): void { this.edgesOpacity = v; }
769
+ setEdgesEdgeColor(r: number, g: number, b: number): void { this.edgesEdgeColor = [r, g, b]; }
770
+ setLensFlareIntensity(v: number): void { this.lensFlareIntensity = v; }
771
+ setLensFlareThreshold(v: number): void { this.lensFlareThreshold = v; }
772
+ setLensFlareGhostCount(v: number): void { this.lensFlareGhostCount = Math.floor(v); }
773
+ setLensFlareGhostSpacing(v: number): void { this.lensFlareGhostSpacing = v; }
774
+ setLensFlareHaloWidth(v: number): void { this.lensFlareHaloWidth = v; }
775
+ setLensFlareTint(r: number, g: number, b: number): void { this.lensFlareTint = [r, g, b]; }
776
+ setPixelationPixelSize(v: number): void { this.pixelationPixelSize = Math.max(1, Math.floor(v)); }
777
+ getPixelationPixelSize(): number { return this.pixelationPixelSize; }
778
+ setPixelationDepthEdgeStrength(v: number): void { this.pixelationDepthEdgeStrength = v; }
779
+ setGaussianBlurRadius(v: number): void { this.gaussianBlurRadius = v; }
780
+ setOutlineColor(r: number, g: number, b: number): void { this.outlineColor = [r, g, b]; }
781
+ setOutlineWidth(v: number): void { this.outlineWidth = v; }
782
+ setOutlineOpacity(v: number): void { this.outlineOpacity = v; }
783
+ setHighlightIntensity(v: number): void { this.highlightIntensity = v; }
784
+ setHighlightInnerOpacity(v: number): void { this.highlightInnerOpacity = v; }
785
+ setHighlightBlurRadius(v: number): void { this.highlightBlurRadius = v; }
786
+ setGlowIntensity(v: number): void { this.glowIntensity = v; }
787
+ setGlowBlurRadius(v: number): void { this.glowBlurRadius = v; }
788
+ setASCIICellSize(v: number): void { this.asciiCellSize = Math.max(2, Math.floor(v)); }
789
+ setASCIIUseColor(v: boolean): void { this.asciiUseColor = v ? 1.0 : 0.0; }
790
+ // ── New effect setters ──
791
+ setLUT(data: Uint8Array, size: number): void {
792
+ this.lutSize = size;
793
+ this.lutTexture?.destroy();
794
+ this.lutTexture = this.device.createTexture({
795
+ dimension: "3d",
796
+ size: [size, size, size], format: "rgba8unorm",
797
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
798
+ });
799
+ // Copy into a fresh ArrayBuffer to satisfy GPUAllowSharedBufferSource typing
800
+ const buf = new Uint8Array(data.length);
801
+ buf.set(data);
802
+ this.device.queue.writeTexture(
803
+ { texture: this.lutTexture }, buf.buffer,
804
+ { bytesPerRow: size * 4, rowsPerImage: size }, [size, size, size],
805
+ );
806
+ this.lutView = this.lutTexture.createView({ dimension: "3d" });
807
+ }
808
+ setLUTEnabled(v: boolean): void { this.lutEnabled = v; }
809
+ setWhiteBalance(temperature: number, tint: number): void { this.wbTemperature = temperature; this.wbTint = tint; }
810
+ setChannelMixer(rr: number, rg: number, rb: number, gr: number, gg: number, gb: number, br: number, bg: number, bb: number): void {
811
+ this.cmWeights = [rr, rg, rb, gr, gg, gb, br, bg, bb];
812
+ }
813
+ setChannelMixerMonochrome(v: boolean): void { this.cmMonochrome = v; }
814
+ setSplitTone(shadowR: number, shadowG: number, shadowB: number, highlightR: number, highlightG: number, highlightB: number, balance: number): void {
815
+ this.stShadow = [shadowR, shadowG, shadowB];
816
+ this.stHighlight = [highlightR, highlightG, highlightB];
817
+ this.stBalance = balance;
818
+ }
819
+ setChromaticAberration(intensity: number, start: number, end: number): void {
820
+ this.caIntensity = intensity; this.caStart = start; this.caEnd = end;
821
+ }
822
+ setChromaticAberrationCenter(x: number, y: number): void { this.caCenterX = x; this.caCenterY = y; }
823
+ setLensDistortion(intensity: number, scale: number, chromaSplit: number): void {
824
+ this.ldIntensity = intensity; this.ldScale = scale; this.ldChromaSplit = chromaSplit;
825
+ }
826
+ setHalftone(cellSize: number, dotScale: number, angle: number, monochrome: boolean): void {
827
+ this.htCellSize = cellSize; this.htDotScale = dotScale; this.htAngle = angle; this.htMonochrome = monochrome;
828
+ }
829
+ setDithering(mode: number, strength: number, levels: number): void {
830
+ this.dMode = Math.floor(mode); this.dStrength = strength; this.dLevels = Math.floor(levels);
831
+ }
832
+ setWatercolor(edgeStrength: number, paperScale: number, blend: number): void {
833
+ this.wcEdgeStrength = edgeStrength; this.wcPaperScale = paperScale; this.wcBlend = blend;
834
+ }
835
+
836
+ // ── Public API: per-frame inputs ──────────────────────────────────────────
837
+
838
+ update(dt: number): void {
839
+ this.grainTime += dt;
840
+ }
841
+
842
+ setCameraMatrices(proj: Float32Array, invProj: Float32Array, view: Float32Array): void {
843
+ this.camProj = proj; this.camInvProj = invProj; this.camView = view;
844
+ }
845
+
846
+ setLightScreenPos(x: number, y: number): void {
847
+ this.lightScreenPos = [x, y];
848
+ }
849
+
850
+ // ── Target management ─────────────────────────────────────────────────────
851
+
852
+ private needsNormals(): boolean { return this.enabled["edges"] || this.enabled["ssao"] || this.enabled["ssr"]; }
853
+ private needsVelocity(): boolean { return this.enabled["taa"] || this.enabled["motion-blur"]; }
854
+ private needsMask(): boolean { return this.enabled["outline"] || this.enabled["highlight"] || this.enabled["glow"]; }
855
+
856
+ private effectsChanged(): boolean {
857
+ for (let _i = 0, _it = ALL_EFFECTS, _n = _it.length; _i < _n; _i++) { const id = _it[_i];
858
+ if (this.enabled[id] !== this.prevEnabled[id]) {
859
+ this.prevEnabled = { ...this.enabled };
860
+ return true;
861
+ }
862
+ }
863
+ return false;
864
+ }
865
+
866
+ ensureTargets(w: number, h: number): void {
867
+ const sizeChanged = (w !== this.texW || h !== this.texH || !this.sceneColor);
868
+ const fxChanged = this.effectsChanged();
869
+ if (!sizeChanged && !fxChanged) return;
870
+
871
+ if (sizeChanged) {
872
+ this.destroyAllTargets();
873
+ const usage = GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST;
874
+ this.sceneColor = this.device.createTexture({ size: [w, h], format: this.sceneFormat, usage });
875
+ this.sceneDepth = this.device.createTexture({ size: [w, h], format: this.depthFormat, usage });
876
+ this.pingPong[0] = this.device.createTexture({ size: [w, h], format: HDR_FORMAT, usage });
877
+ this.pingPong[1] = this.device.createTexture({ size: [w, h], format: HDR_FORMAT, usage });
878
+ }
879
+
880
+ // Always (re)create optional targets when effects or size change
881
+ this.destroyOptionalTargets();
882
+ this.createOptionalTargets(w, h);
883
+
884
+ this.texW = w; this.texH = h;
885
+ }
886
+
887
+ private createOptionalTargets(w: number, h: number): void {
888
+ const usage = GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST;
889
+ const mkTex = (fmt: GPUTextureFormat, tw: number, th: number): GPUTexture =>
890
+ this.device.createTexture({ size: [tw, th], format: fmt, usage });
891
+
892
+ if (this.needsNormals()) this.sceneNormals = mkTex("rgba8unorm", w, h);
893
+ if (this.needsVelocity()) this.sceneVelocity = mkTex("rg16float", w, h);
894
+ if (this.needsMask()) this.sceneMask = mkTex("rgba8unorm", w, h);
895
+ if (this.enabled["taa"]) { this.taaHistory = mkTex(HDR_FORMAT, w, h); this.taaHistory2 = mkTex(HDR_FORMAT, w, h); }
896
+ if (this.enabled["afterimage"]) this.afterimageTex = mkTex(HDR_FORMAT, w, h);
897
+ if (this.enabled["bloom"]) {
898
+ const hw = Math.max(1, w >> 1), hh = Math.max(1, h >> 1);
899
+ this.bloomBright = mkTex(HDR_FORMAT, hw, hh);
900
+ this.bloomBlurH = mkTex(HDR_FORMAT, hw, hh);
901
+ this.bloomBlurV = mkTex(HDR_FORMAT, hw, hh);
902
+ this.bloomBase = mkTex(HDR_FORMAT, w, h);
903
+ // Multi-MIP pyramid: bloomMip[0] = ½res, [1] = ¼res, [2] = ⅛res, [3] = 1/16, [4] = 1/32
904
+ this.bloomMip = [];
905
+ this.bloomTemp = [];
906
+ let mw = hw, mh = hh;
907
+ for (let i = 0; i < this.bloomMipCount; i++) {
908
+ this.bloomMip.push(mkTex(HDR_FORMAT, mw, mh));
909
+ this.bloomTemp.push(mkTex(HDR_FORMAT, mw, mh));
910
+ mw = Math.max(1, mw >> 1);
911
+ mh = Math.max(1, mh >> 1);
912
+ }
913
+ }
914
+ if (this.enabled["bloom-soft"]) {
915
+ const hw = Math.max(1, w >> 1), hh = Math.max(1, h >> 1);
916
+ this.halfResA = mkTex(HDR_FORMAT, hw, hh);
917
+ this.halfResB = mkTex(HDR_FORMAT, hw, hh);
918
+ }
919
+ if (this.enabled["ssao"]) { this.ssaoA = mkTex(HDR_FORMAT, w, h); this.ssaoB = mkTex(HDR_FORMAT, w, h); }
920
+ if (this.enabled["gaussian-blur"]) { this.blurA = mkTex(HDR_FORMAT, w, h); this.blurB = mkTex(HDR_FORMAT, w, h); }
921
+ if (this.enabled["highlight"]) { this.blurA = this.blurA ?? mkTex(HDR_FORMAT, w, h); this.blurB = this.blurB ?? mkTex(HDR_FORMAT, w, h); }
922
+ if (this.enabled["glow"]) { this.blurA = this.blurA ?? mkTex(HDR_FORMAT, w, h); this.blurB = this.blurB ?? mkTex(HDR_FORMAT, w, h); }
923
+ }
924
+
925
+ private destroyOptionalTargets(): void {
926
+ const destroy = (t: GPUTexture | null) => { t?.destroy(); };
927
+ destroy(this.sceneNormals); this.sceneNormals = null;
928
+ destroy(this.sceneVelocity); this.sceneVelocity = null;
929
+ destroy(this.sceneMask); this.sceneMask = null;
930
+ destroy(this.taaHistory); this.taaHistory = null;
931
+ destroy(this.taaHistory2); this.taaHistory2 = null;
932
+ destroy(this.afterimageTex); this.afterimageTex = null;
933
+ destroy(this.bloomBright); this.bloomBright = null;
934
+ destroy(this.bloomBlurH); this.bloomBlurH = null;
935
+ destroy(this.bloomBlurV); this.bloomBlurV = null;
936
+ this.bloomMip.forEach((m) => { m.destroy();; });
937
+ this.bloomMip = [];
938
+ this.bloomTemp.forEach((m) => { m.destroy();; });
939
+ this.bloomTemp = [];
940
+ destroy(this.bloomBase); this.bloomBase = null;
941
+ destroy(this.halfResA); this.halfResA = null;
942
+ destroy(this.halfResB); this.halfResB = null;
943
+ destroy(this.ssaoA); this.ssaoA = null;
944
+ destroy(this.ssaoB); this.ssaoB = null;
945
+ destroy(this.blurA); this.blurA = null;
946
+ destroy(this.blurB); this.blurB = null;
947
+ this.bloomSoftResult = null;
948
+ }
949
+
950
+ private destroyAllTargets(): void {
951
+ this.clearResourceCaches();
952
+ this.destroyOptionalTargets();
953
+ this.sceneColor?.destroy(); this.sceneColor = null;
954
+ this.sceneDepth?.destroy(); this.sceneDepth = null;
955
+ this.pingPong[0]?.destroy(); this.pingPong[0] = null;
956
+ this.pingPong[1]?.destroy(); this.pingPong[1] = null;
957
+ }
958
+
959
+ // ── Target getters (game renders into these) ──────────────────────────────
960
+
961
+ getSceneColorView(): GPUTextureView {
962
+ if (!this.sceneColor) throw new Error("PostProcess targets not created");
963
+ return this.viewOf(this.sceneColor);
964
+ }
965
+
966
+ getSceneColorTexture(): GPUTexture {
967
+ if (!this.sceneColor) throw new Error("PostProcess targets not created");
968
+ return this.sceneColor;
969
+ }
970
+
971
+ getSceneDepthView(): GPUTextureView {
972
+ if (!this.sceneDepth) throw new Error("PostProcess targets not created");
973
+ return this.viewOf(this.sceneDepth);
974
+ }
975
+
976
+ getSceneNormalsView(): GPUTextureView {
977
+ if (!this.sceneNormals) throw new Error("Normals target not created — enable edges/ssao/ssr first");
978
+ return this.viewOf(this.sceneNormals);
979
+ }
980
+
981
+ getSceneVelocityView(): GPUTextureView {
982
+ if (!this.sceneVelocity) throw new Error("Velocity target not created — enable taa/motion-blur first");
983
+ return this.viewOf(this.sceneVelocity);
984
+ }
985
+
986
+ getSceneMaskView(): GPUTextureView {
987
+ if (!this.sceneMask) throw new Error("Mask target not created — enable outline/highlight/glow first");
988
+ return this.viewOf(this.sceneMask);
989
+ }
990
+
991
+ // ── Main chain ────────────────────────────────────────────────────────────
992
+
993
+ applyChain(
994
+ encoder: GPUCommandEncoder,
995
+ depthView: GPUTextureView | null,
996
+ canvasView: GPUTextureView,
997
+ w: number, h: number,
998
+ time: number = 0,
999
+ ): void {
1000
+ const active = CHAIN_ORDER.filter(id => this.enabled[id]);
1001
+ // Collect enabled custom effects grouped by order.
1002
+ const customByOrder: Record<CustomEffectOrder, string[]> = {
1003
+ "hdr": [], "color-grading": [], "camera": [], "stylized": [],
1004
+ };
1005
+ for (const [id, e] of this.customEffects.entries()) {
1006
+ if (e.enabled) customByOrder[e.effect.order].push(id);
1007
+ }
1008
+ const hasCustom = (customByOrder["hdr"].length + customByOrder["color-grading"].length +
1009
+ customByOrder["camera"].length + customByOrder["stylized"].length) > 0;
1010
+ if (active.length === 0 && !hasCustom) {
1011
+ // Empty chain: still blit the scene color to the canvas. Callers that
1012
+ // render scene pipelines compiled for the HDR scene format must always
1013
+ // go through this stack — a direct-to-surface fallback binds those
1014
+ // pipelines against the swapchain format (e.g. bgra8unorm), which is a
1015
+ // validation error on native and renders nothing on Chrome.
1016
+ if (this.sceneColor) this.applyBlit(encoder, this.viewOf(this.sceneColor), canvasView, w, h);
1017
+ return;
1018
+ }
1019
+
1020
+ const dv = depthView ?? (this.sceneDepth ? this.viewOf(this.sceneDepth) : undefined) ?? null;
1021
+ let inputView = this.viewOf(this.sceneColor!);
1022
+ let pingIdx = 0;
1023
+
1024
+ // Build a combined chain: built-in effects with custom effects inserted
1025
+ // at the end of each order group. The CHAIN_ORDER groups are:
1026
+ // [0..7] = HDR (taa..tonemap)
1027
+ // [8..11] = Color grading (lut..split-tone)
1028
+ // [12..16] = Camera/lens (fxaa..grain)
1029
+ // [17..end] = Stylized (sobel..ascii)
1030
+ const HDR_END = 8, COLOR_END = 12, CAMERA_END = 17;
1031
+ const chain: Array<{ type: "builtin"; id: EffectId } | { type: "custom"; id: string }> = [];
1032
+ // Iterate the full CHAIN_ORDER so custom effects are inserted at the
1033
+ // correct group boundaries even when few built-in effects are enabled.
1034
+ for (let i = 0; i < CHAIN_ORDER.length; i++) {
1035
+ if (this.enabled[CHAIN_ORDER[i]]) {
1036
+ chain.push({ type: "builtin", id: CHAIN_ORDER[i] });
1037
+ }
1038
+ // Insert custom effects at group boundaries (after the last effect in each group).
1039
+ if (i === HDR_END - 1) customByOrder["hdr"].forEach((cid) => { chain.push({ type: "custom", id: cid });; });
1040
+ if (i === COLOR_END - 1) customByOrder["color-grading"].forEach((cid) => { chain.push({ type: "custom", id: cid });; });
1041
+ if (i === CAMERA_END - 1) customByOrder["camera"].forEach((cid) => { chain.push({ type: "custom", id: cid });; });
1042
+ }
1043
+ // Stylized custom effects run after all built-in stylized effects.
1044
+ customByOrder["stylized"].forEach((cid) => { chain.push({ type: "custom", id: cid });; });
1045
+ // If there are no built-in effects but there are custom effects, run them.
1046
+ if (active.length === 0) {
1047
+ (["hdr", "color-grading", "camera", "stylized"] as CustomEffectOrder[]).forEach((order) => {
1048
+ customByOrder[order].forEach((cid) => { chain.push({ type: "custom", id: cid });; });
1049
+ });
1050
+ }
1051
+
1052
+ chain.forEach((entry) => {
1053
+ const outputTex = this.pingPong[pingIdx]!;
1054
+ const outputView = this.viewOf(outputTex);
1055
+ if (entry.type === "builtin") {
1056
+ this.applyEffect(encoder, entry.id, inputView, outputView, dv, w, h);
1057
+ if (entry.id === "afterimage") {
1058
+ encoder.copyTextureToTexture({ texture: outputTex }, { texture: this.afterimageTex! }, [w, h]);
1059
+ } else if (entry.id === "taa") {
1060
+ encoder.copyTextureToTexture({ texture: outputTex }, { texture: this.taaHistory2! }, [w, h]);
1061
+ const tmp = this.taaHistory; this.taaHistory = this.taaHistory2; this.taaHistory2 = tmp;
1062
+ }
1063
+ } else {
1064
+ this.applyCustomEffect(encoder, entry.id, inputView, outputView, w, h, time);
1065
+ }
1066
+ inputView = outputView;
1067
+ pingIdx = 1 - pingIdx;
1068
+ });
1069
+
1070
+ // Blit final result to canvas
1071
+ this.applyBlit(encoder, inputView, canvasView, w, h);
1072
+ }
1073
+
1074
+ /** Apply a custom (mod-defined) effect as a full-screen pass. */
1075
+ private applyCustomEffect(
1076
+ encoder: GPUCommandEncoder,
1077
+ id: string,
1078
+ inputView: GPUTextureView,
1079
+ outputView: GPUTextureView,
1080
+ w: number, h: number,
1081
+ time: number,
1082
+ ): void {
1083
+ const e = this.customEffects.get(id);
1084
+ if (!e) return;
1085
+ // Write per-frame values (inv_w, inv_h, time) to the first 3 floats.
1086
+ // User settings start at offset 3 (set via setCustomEffectUniform).
1087
+ const perFrame = new Float32Array(e.uniform.size / 4);
1088
+ perFrame[0] = 1 / w;
1089
+ perFrame[1] = 1 / h;
1090
+ perFrame[2] = time;
1091
+ // Preserve existing user settings (offset 3+).
1092
+ const existing = e.uniformValues;
1093
+ if (existing) {
1094
+ for (let i = 3; i < existing.length && i < perFrame.length; i++) {
1095
+ perFrame[i] = existing[i];
1096
+ }
1097
+ }
1098
+ e.uniformValues = perFrame;
1099
+ this.device.queue.writeBuffer(e.uniform, 0, perFrame as unknown as Float32Array<ArrayBuffer>);
1100
+ const bg = this.bgCached(this.ccLayout, [
1101
+ { binding: 0, resource: inputView },
1102
+ { binding: 1, resource: this.viewOf(this.dummyTex) },
1103
+ { binding: 2, resource: this.linearSampler },
1104
+ { binding: 3, resource: { buffer: e.uniform } },
1105
+ ]);
1106
+ this.pass(encoder, e.pipeline, bg, outputView, w, h);
1107
+ }
1108
+
1109
+ private applyEffect(
1110
+ encoder: GPUCommandEncoder,
1111
+ effect: EffectId,
1112
+ inputView: GPUTextureView,
1113
+ outputView: GPUTextureView,
1114
+ depthView: GPUTextureView | null,
1115
+ w: number, h: number,
1116
+ ): void {
1117
+ switch (effect) {
1118
+ case "fxaa": this.applyCC(encoder, "fxaa", inputView, outputView, w, h, [1/w, 1/h]); break;
1119
+ case "sobel": this.applyCC(encoder, "sobel", inputView, outputView, w, h, [1/w, 1/h]); break;
1120
+ case "sharpen": this.applyCC(encoder, "sharpen", inputView, outputView, w, h, [1/w, 1/h, this.sharpenSharpness]); break;
1121
+ case "grain": this.applyCC(encoder, "grain", inputView, outputView, w, h, [1/w, 1/h, this.grainIntensity, this.grainSize, this.grainLuminanceAware ? 1 : 0, this.grainTime]); break;
1122
+ case "dof": this.applyCD(encoder, "dof", inputView, outputView, depthView!, w, h, [1/w, 1/h, this.dofFocusDist, this.dofFocusRange, this.dofMaxBlur, this.dofBokehShape, this.dofSampleCount, this.dofNearOnly ? 1 : 0, this.dofFarOnly ? 1 : 0, this.dofBladeRotation, 0, 0, 0, 0, 0, 0]); break;
1123
+ case "pixelation": this.applyCD(encoder, "pixelation", inputView, outputView, depthView!, w, h, [1/w, 1/h, this.pixelationPixelSize, this.pixelationDepthEdgeStrength, w, h]); break;
1124
+ case "lens-flare": this.applyCD(encoder, "lens-flare", inputView, outputView, depthView!, w, h, [this.lightScreenPos[0], this.lightScreenPos[1], this.lensFlareIntensity, this.lensFlareThreshold, this.lensFlareGhostCount, this.lensFlareGhostSpacing, this.lensFlareHaloWidth, 16, 0, 0, 0, 0, ...this.lensFlareTint, 0]); break;
1125
+ case "afterimage": this.applyAfterimage(encoder, inputView, outputView, w, h); break;
1126
+ case "ascii": this.applyASCII(encoder, inputView, outputView, w, h); break;
1127
+ case "bloom": this.applyBloom(encoder, inputView, outputView, w, h); break;
1128
+ case "bloom-soft": this.applyBloomSoft(encoder, inputView, outputView, w, h); break;
1129
+ case "tonemap": this.applyTonemap(encoder, inputView, outputView, w, h); break;
1130
+ case "taa": this.applyTAA(encoder, inputView, outputView, w, h); break;
1131
+ case "ssao": this.applySSAO(encoder, inputView, outputView, depthView!, w, h); break;
1132
+ case "ssr": this.applySSR(encoder, inputView, outputView, depthView!, w, h); break;
1133
+ case "motion-blur": this.applyMotionBlur(encoder, inputView, outputView, depthView!, w, h); break;
1134
+ case "edges": this.applyEdges(encoder, inputView, outputView, depthView!, w, h); break;
1135
+ case "gaussian-blur": this.applyGaussianBlur(encoder, inputView, outputView, w, h); break;
1136
+ case "outline": this.applyOutline(encoder, inputView, outputView, w, h); break;
1137
+ case "highlight": this.applyHighlight(encoder, inputView, outputView, w, h); break;
1138
+ case "glow": this.applyGlow(encoder, inputView, outputView, w, h); break;
1139
+ // New effects
1140
+ case "lut": this.applyLUT(encoder, inputView, outputView, w, h); break;
1141
+ case "white-balance": this.applyCC(encoder, "white-balance", inputView, outputView, w, h, [this.wbTemperature, this.wbTint, 0, 0, 0, 0, 0, 0]); break;
1142
+ case "channel-mixer": this.applyCC(encoder, "channel-mixer", inputView, outputView, w, h, [...this.cmWeights, this.cmMonochrome ? 1 : 0, 0, 0, 0]); break;
1143
+ case "split-tone": this.applyCC(encoder, "split-tone", inputView, outputView, w, h, [...this.stShadow, ...this.stHighlight, this.stBalance, 0]); break;
1144
+ case "chromatic-aberration": this.applyCC(encoder, "chromatic-aberration", inputView, outputView, w, h, [1/w, 1/h, this.caIntensity, this.caStart, this.caEnd, this.caCenterX, this.caCenterY, 0]); break;
1145
+ case "lens-distortion": this.applyCC(encoder, "lens-distortion", inputView, outputView, w, h, [1/w, 1/h, this.ldIntensity, this.ldScale, this.ldChromaSplit, 0, 0, 0]); break;
1146
+ case "halftone": this.applyCC(encoder, "halftone", inputView, outputView, w, h, [1/w, 1/h, this.htCellSize, this.htDotScale, this.htAngle, this.htMonochrome ? 1 : 0, 0, 0]); break;
1147
+ case "dithering": this.applyDithering(encoder, inputView, outputView, w, h); break;
1148
+ case "watercolor": this.applyWatercolor(encoder, inputView, outputView, w, h); break;
1149
+ }
1150
+ }
1151
+
1152
+ // ── Helpers ───────────────────────────────────────────────────────────────
1153
+
1154
+ // Scratch buffer for number[] uniform data — writeBuffer() captures the
1155
+ // contents synchronously at call time, so a single reusable scratch is safe
1156
+ // and avoids allocating a Float32Array per effect per frame.
1157
+ private wuScratch = new Float32Array(64);
1158
+
1159
+ private wu(key: string, data: Float32Array | number[]): void {
1160
+ let arr: Float32Array;
1161
+ if (data instanceof Float32Array) {
1162
+ arr = data;
1163
+ } else {
1164
+ if (this.wuScratch.length < data.length) this.wuScratch = new Float32Array(data.length);
1165
+ this.wuScratch.set(data);
1166
+ arr = this.wuScratch.subarray(0, data.length);
1167
+ }
1168
+ this.device.queue.writeBuffer(this.uniforms[key], 0, arr as unknown as Float32Array<ArrayBuffer>);
1169
+ }
1170
+
1171
+ private bg(layout: GPUBindGroupLayout, entries: GPUBindGroupEntry[]): GPUBindGroup {
1172
+ return this.device.createBindGroup({ layout, entries });
1173
+ }
1174
+
1175
+ // ── Per-frame allocation caches ───────────────────────────────────────────
1176
+ // createView()/createBindGroup() were called per effect per frame (~20+/frame).
1177
+ // Views are immutable descriptors → cache per texture. Bind groups are pure
1178
+ // functions of (layout, resources) → cache keyed by resource identity.
1179
+ // Both caches are invalidated when targets are destroyed (resize/dispose).
1180
+ private viewCache = new Map<GPUTexture, GPUTextureView>();
1181
+ private bgCache = new Map<string, GPUBindGroup>();
1182
+ private objIds = new WeakMap<object, number>();
1183
+ private nextObjId = 1;
1184
+
1185
+ private idOf(o: object): number {
1186
+ let id = this.objIds.get(o);
1187
+ if (id === undefined) this.objIds.set(o, (id = this.nextObjId++));
1188
+ return id;
1189
+ }
1190
+
1191
+ /** Cached createView() for a stable texture (cleared on target rebuild). */
1192
+ private viewOf(tex: GPUTexture): GPUTextureView {
1193
+ let v = this.viewCache.get(tex);
1194
+ if (!v) this.viewCache.set(tex, (v = tex.createView()));
1195
+ return v;
1196
+ }
1197
+
1198
+ /** Cached createBindGroup — entries must reference stable resources
1199
+ * (views from viewOf(), long-lived buffers/samplers). */
1200
+ private bgCached(layout: GPUBindGroupLayout, entries: GPUBindGroupEntry[]): GPUBindGroup {
1201
+ let key = `${this.idOf(layout)}`;
1202
+ entries.forEach((e) => {
1203
+ const r = e.resource as object;
1204
+ key += `|${e.binding}:`;
1205
+ // GPUBufferBinding literals are fresh objects per call — key on the
1206
+ // underlying buffer + offset/size instead.
1207
+ if (r && typeof r === "object" && "buffer" in (r as GPUBufferBinding)) {
1208
+ const b = r as GPUBufferBinding;
1209
+ key += `b${this.idOf(b.buffer)}@${b.offset ?? 0}+${b.size ?? -1}`;
1210
+ } else {
1211
+ key += `o${this.idOf(r)}`;
1212
+ }
1213
+ });
1214
+ let bg = this.bgCache.get(key);
1215
+ if (!bg) this.bgCache.set(key, (bg = this.bg(layout, entries)));
1216
+ return bg;
1217
+ }
1218
+
1219
+ private clearResourceCaches(): void {
1220
+ this.viewCache.clear();
1221
+ this.bgCache.clear();
1222
+ }
1223
+
1224
+ private pass(
1225
+ encoder: GPUCommandEncoder,
1226
+ pipeline: GPURenderPipeline,
1227
+ bindGroup: GPUBindGroup,
1228
+ outputView: GPUTextureView,
1229
+ w: number, h: number,
1230
+ ): void {
1231
+ const p = encoder.beginRenderPass({
1232
+ colorAttachments: [{ view: outputView, clearValue: { r: 0, g: 0, b: 0, a: 1 }, loadOp: "clear", storeOp: "store" }],
1233
+ });
1234
+ p.setViewport(0, 0, w, h, 0, 1);
1235
+ p.setScissorRect(0, 0, w, h);
1236
+ p.setPipeline(pipeline);
1237
+ p.setBindGroup(0, bindGroup);
1238
+ p.setBindGroup(1, this.occluderBindGroup);
1239
+ p.draw(3);
1240
+ p.end();
1241
+ }
1242
+
1243
+ // ── Simple cc-layout effects (color + dummy + sampler + uniform) ──────────
1244
+
1245
+ private applyCC(
1246
+ encoder: GPUCommandEncoder, key: string,
1247
+ inputView: GPUTextureView, outputView: GPUTextureView,
1248
+ w: number, h: number, data: number[],
1249
+ ): void {
1250
+ this.wu(key, data);
1251
+ const bg = this.bgCached(this.ccLayout, [
1252
+ { binding: 0, resource: inputView },
1253
+ { binding: 1, resource: this.viewOf(this.dummyTex) },
1254
+ { binding: 2, resource: this.linearSampler },
1255
+ { binding: 3, resource: { buffer: this.uniforms[key] } },
1256
+ ]);
1257
+ this.pass(encoder, this.pipelines[key], bg, outputView, w, h);
1258
+ }
1259
+
1260
+ // ── Simple cd-layout effects (color + depth + sampler + uniform) ──────────
1261
+
1262
+ private applyCD(
1263
+ encoder: GPUCommandEncoder, key: string,
1264
+ inputView: GPUTextureView, outputView: GPUTextureView,
1265
+ depthView: GPUTextureView,
1266
+ w: number, h: number, data: number[],
1267
+ ): void {
1268
+ this.wu(key, data);
1269
+ const bg = this.bgCached(this.cdLayout, [
1270
+ { binding: 0, resource: inputView },
1271
+ { binding: 1, resource: depthView },
1272
+ { binding: 2, resource: this.nearestSampler },
1273
+ { binding: 3, resource: { buffer: this.uniforms[key] } },
1274
+ ]);
1275
+ this.pass(encoder, this.pipelines[key], bg, outputView, w, h);
1276
+ }
1277
+
1278
+ // ── Afterimage ────────────────────────────────────────────────────────────
1279
+
1280
+ private applyAfterimage(encoder: GPUCommandEncoder, inputView: GPUTextureView, outputView: GPUTextureView, w: number, h: number): void {
1281
+ this.wu("afterimage", [1/w, 1/h, this.afterimageDamp]);
1282
+ const bg = this.bgCached(this.ccLayout, [
1283
+ { binding: 0, resource: inputView },
1284
+ { binding: 1, resource: this.viewOf(this.afterimageTex!) },
1285
+ { binding: 2, resource: this.linearSampler },
1286
+ { binding: 3, resource: { buffer: this.uniforms["afterimage"] } },
1287
+ ]);
1288
+ this.pass(encoder, this.pipelines["afterimage"], bg, outputView, w, h);
1289
+ }
1290
+
1291
+ // ── ASCII ─────────────────────────────────────────────────────────────────
1292
+
1293
+ private applyASCII(encoder: GPUCommandEncoder, inputView: GPUTextureView, outputView: GPUTextureView, w: number, h: number): void {
1294
+ this.wu("ascii", [1/w, 1/h, this.asciiCellSize, this.asciiUseColor, w, h]);
1295
+ const bg = this.bgCached(this.ccLayout, [
1296
+ { binding: 0, resource: inputView },
1297
+ { binding: 1, resource: this.viewOf(this.glyphTex!) },
1298
+ { binding: 2, resource: this.linearSampler },
1299
+ { binding: 3, resource: { buffer: this.uniforms["ascii"] } },
1300
+ ]);
1301
+ this.pass(encoder, this.pipelines["ascii"], bg, outputView, w, h);
1302
+ }
1303
+
1304
+ // ── Bloom (multi-MIP pyramid: downsample cascade + upsample composite) ───
1305
+
1306
+ private applyBloom(encoder: GPUCommandEncoder, inputView: GPUTextureView, outputView: GPUTextureView, w: number, h: number): void {
1307
+ const dummy = this.viewOf(this.dummyTex);
1308
+ const mipCount = this.bloomMip.length;
1309
+ if (mipCount === 0) {
1310
+ // Fallback: no MIP targets allocated — blit input to output
1311
+ this.applyBlitHDR(encoder, inputView, outputView, w, h);
1312
+ return;
1313
+ }
1314
+
1315
+ // Track dimensions for each MIP level
1316
+ const mipW: number[] = [Math.max(1, w >> 1)];
1317
+ const mipH: number[] = [Math.max(1, h >> 1)];
1318
+ for (let i = 1; i < mipCount; i++) {
1319
+ mipW.push(Math.max(1, mipW[i - 1] >> 1));
1320
+ mipH.push(Math.max(1, mipH[i - 1] >> 1));
1321
+ }
1322
+
1323
+ // ── Downsample cascade: full → ½ → ¼ → ⅛ → ... ──
1324
+ // First downsample: bright-pass extraction from the full-res input
1325
+ this.wu("bloom-downsample", [
1326
+ 1 / w, 1 / h, 1.0, this.bloomThreshold, this.bloomSoftKnee, 0, 0, 0,
1327
+ ]);
1328
+ this.pass(encoder, this.pipelines["bloom-downsample"], this.bgCached(this.ccLayout, [
1329
+ { binding: 0, resource: inputView }, { binding: 1, resource: dummy },
1330
+ { binding: 2, resource: this.linearSampler }, { binding: 3, resource: { buffer: this.uniforms["bloom-downsample"] } },
1331
+ ]), this.viewOf(this.bloomMip[0]), mipW[0], mipH[0]);
1332
+
1333
+ // Subsequent downsamples: plain downsample from previous MIP
1334
+ for (let i = 1; i < mipCount; i++) {
1335
+ const srcW = mipW[i - 1], srcH = mipH[i - 1];
1336
+ this.wu("bloom-downsample", [
1337
+ 1 / srcW, 1 / srcH, 0.0, this.bloomThreshold, this.bloomSoftKnee, 0, 0, 0,
1338
+ ]);
1339
+ this.pass(encoder, this.pipelines["bloom-downsample"], this.bgCached(this.ccLayout, [
1340
+ { binding: 0, resource: this.viewOf(this.bloomMip[i - 1]) }, { binding: 1, resource: dummy },
1341
+ { binding: 2, resource: this.linearSampler }, { binding: 3, resource: { buffer: this.uniforms["bloom-downsample"] } },
1342
+ ]), this.viewOf(this.bloomMip[i]), mipW[i], mipH[i]);
1343
+ }
1344
+
1345
+ // ── Upsample cascade: smallest MIP → ... → ½ → composite to output ──
1346
+ // Start from the smallest MIP, progressively upsample + additively blend
1347
+ // into the next larger MIP. The upsample shader reads the lower-res MIP
1348
+ // and adds it to the higher-res MIP (baseTex binding).
1349
+ // We use a temp texture for the base to avoid reading+writing the same
1350
+ // texture in the same render pass (WebGPU sync scope violation).
1351
+ for (let i = mipCount - 1; i >= 1; i--) {
1352
+ const srcW = mipW[i], srcH = mipH[i];
1353
+ const dstW = mipW[i - 1], dstH = mipH[i - 1];
1354
+ const weight = this.bloomMipWeights[Math.min(i, this.bloomMipWeights.length - 1)] ?? 0.5;
1355
+ this.wu("bloom-upsample", [
1356
+ 1 / srcW, 1 / srcH, weight, 0,
1357
+ this.bloomTint[0], this.bloomTint[1], this.bloomTint[2], 0,
1358
+ ]);
1359
+ // Blit bloomMip[i-1] → bloomTemp[i-1] to avoid read+write same texture
1360
+ this.applyBlitHDR(encoder, this.viewOf(this.bloomMip[i - 1]), this.viewOf(this.bloomTemp[i - 1]), dstW, dstH);
1361
+ this.pass(encoder, this.pipelines["bloom-upsample"], this.bgCached(this.ccLayout, [
1362
+ { binding: 0, resource: this.viewOf(this.bloomMip[i]) },
1363
+ { binding: 1, resource: this.viewOf(this.bloomTemp[i - 1]) },
1364
+ { binding: 2, resource: this.linearSampler },
1365
+ { binding: 3, resource: { buffer: this.uniforms["bloom-upsample"] } },
1366
+ ]), this.viewOf(this.bloomMip[i - 1]), dstW, dstH);
1367
+ }
1368
+
1369
+ // Final composite: upsample ½-res bloom → full-res, add to scene color
1370
+ const srcW = mipW[0], srcH = mipH[0];
1371
+ const finalWeight = this.bloomStrength * (this.bloomMipWeights[0] ?? 0.5);
1372
+ this.wu("bloom-upsample", [
1373
+ 1 / srcW, 1 / srcH, finalWeight, 0,
1374
+ this.bloomTint[0], this.bloomTint[1], this.bloomTint[2], 0,
1375
+ ]);
1376
+ // For the final pass, baseTex = inputView (scene color), output = outputView
1377
+ // We need to read inputView as base — but the upsample shader writes base+bloom.
1378
+ // Copy inputView to a dedicated scratch (bloomBase): outputView may be any
1379
+ // ping-pong texture, so neither pingPong[0] nor pingPong[1] is guaranteed
1380
+ // safe here (read+write same texture = WebGPU sync scope violation).
1381
+ this.applyBlitHDR(encoder, inputView, this.viewOf(this.bloomBase!), w, h);
1382
+ this.pass(encoder, this.pipelines["bloom-upsample"], this.bgCached(this.ccLayout, [
1383
+ { binding: 0, resource: this.viewOf(this.bloomMip[0]) },
1384
+ { binding: 1, resource: this.viewOf(this.bloomBase!) },
1385
+ { binding: 2, resource: this.linearSampler },
1386
+ { binding: 3, resource: { buffer: this.uniforms["bloom-upsample"] } },
1387
+ ]), outputView, w, h);
1388
+ }
1389
+
1390
+ // ── Bloom-soft (bright + blur H + blur V, stores result for tonemap) ──────
1391
+
1392
+ private applyBloomSoft(encoder: GPUCommandEncoder, inputView: GPUTextureView, outputView: GPUTextureView, w: number, h: number): void {
1393
+ const hw = Math.max(1, w >> 1), hh = Math.max(1, h >> 1);
1394
+ const dummy = this.viewOf(this.dummyTex);
1395
+
1396
+ // Bright pass (dirX > 1.0 triggers bright-pass extraction)
1397
+ this.wu("bloom-soft", [1/w, 1/h, 2.0, 0.0, this.bloomSoftThreshold, this.bloomSoftSoftThreshold]);
1398
+ this.pass(encoder, this.pipelines["bloom-soft"], this.bgCached(this.ccLayout, [
1399
+ { binding: 0, resource: inputView }, { binding: 1, resource: dummy },
1400
+ { binding: 2, resource: this.linearSampler }, { binding: 3, resource: { buffer: this.uniforms["bloom-soft"] } },
1401
+ ]), this.viewOf(this.halfResA!), hw, hh);
1402
+
1403
+ // Blur H
1404
+ this.wu("bloom-soft", [1/hw, 1/hh, 1.0, 0.0, this.bloomSoftThreshold, this.bloomSoftSoftThreshold]);
1405
+ this.pass(encoder, this.pipelines["bloom-soft"], this.bgCached(this.ccLayout, [
1406
+ { binding: 0, resource: this.viewOf(this.halfResA!) }, { binding: 1, resource: dummy },
1407
+ { binding: 2, resource: this.linearSampler }, { binding: 3, resource: { buffer: this.uniforms["bloom-soft"] } },
1408
+ ]), this.viewOf(this.halfResB!), hw, hh);
1409
+
1410
+ // Blur V
1411
+ this.wu("bloom-soft", [1/hw, 1/hh, 0.0, 1.0, this.bloomSoftThreshold, this.bloomSoftSoftThreshold]);
1412
+ this.pass(encoder, this.pipelines["bloom-soft"], this.bgCached(this.ccLayout, [
1413
+ { binding: 0, resource: this.viewOf(this.halfResB!) }, { binding: 1, resource: dummy },
1414
+ { binding: 2, resource: this.linearSampler }, { binding: 3, resource: { buffer: this.uniforms["bloom-soft"] } },
1415
+ ]), this.viewOf(this.halfResA!), hw, hh);
1416
+
1417
+ // Store result for tonemap
1418
+ this.bloomSoftResult = this.viewOf(this.halfResA!);
1419
+
1420
+ // Blit input to output (bloom-soft doesn't composite — tonemap does that)
1421
+ // Use HDR blit since outputView is an HDR ping-pong texture in the chain
1422
+ this.applyBlitHDR(encoder, inputView, outputView, w, h);
1423
+ }
1424
+
1425
+ // ── Tonemap (color + bloom + ACES + exposure/gamma/contrast/sat/vignette) ─
1426
+
1427
+ private applyTonemap(encoder: GPUCommandEncoder, inputView: GPUTextureView, outputView: GPUTextureView, w: number, h: number): void {
1428
+ this.wu("tonemap", [
1429
+ this.exposure, this.bloomSoftIntensity, this.gamma, this.contrast, this.saturation, this.vignette, 0, 0,
1430
+ ]);
1431
+ const bloomView = this.bloomSoftResult ?? this.viewOf(this.dummyTex);
1432
+ const bg = this.bgCached(this.ccLayout, [
1433
+ { binding: 0, resource: inputView },
1434
+ { binding: 1, resource: bloomView },
1435
+ { binding: 2, resource: this.linearSampler },
1436
+ { binding: 3, resource: { buffer: this.uniforms["tonemap"] } },
1437
+ ]);
1438
+ this.pass(encoder, this.pipelines["tonemap"], bg, outputView, w, h);
1439
+ }
1440
+
1441
+ // ── TAA (color + velocity + history + YCoCg neighborhood clamp) ───────────
1442
+
1443
+ private applyTAA(encoder: GPUCommandEncoder, inputView: GPUTextureView, outputView: GPUTextureView, w: number, h: number): void {
1444
+ // When occluder rects changed, use blendFactor=1.0 for one frame to
1445
+ // ignore stale history in the previously-occluded region (standard TAA
1446
+ // reset pattern, same as camera cuts).
1447
+ const blend = this.taaHistoryReset ? 1.0 : this.taaBlendFactor;
1448
+ this.wu("taa", [1/w, 1/h, blend, this.taaVarianceClamp ? 1 : 0, this.taaJitterX, this.taaJitterY, 0, 0]);
1449
+ const bg = this.bgCached(this.cvvhLayout, [
1450
+ { binding: 0, resource: inputView },
1451
+ { binding: 1, resource: this.viewOf(this.sceneVelocity!) },
1452
+ { binding: 2, resource: this.viewOf(this.taaHistory!) },
1453
+ { binding: 3, resource: this.linearSampler },
1454
+ { binding: 4, resource: { buffer: this.uniforms["taa"] } },
1455
+ ]);
1456
+ this.pass(encoder, this.pipelines["taa"], bg, outputView, w, h);
1457
+ this.taaHistoryReset = false;
1458
+ }
1459
+
1460
+ // ── SSAO (compute + blur + composite) ─────────────────────────────────────
1461
+
1462
+ private applySSAO(encoder: GPUCommandEncoder, inputView: GPUTextureView, outputView: GPUTextureView, depthView: GPUTextureView, w: number, h: number): void {
1463
+ // Write SSAO uniform (matrices + params)
1464
+ this.writeSSAOUniform(w, h);
1465
+
1466
+ // Compute SSAO
1467
+ const bg0 = this.bgCached(this.dnfnLayout, [
1468
+ { binding: 0, resource: depthView },
1469
+ { binding: 1, resource: this.viewOf(this.sceneNormals!) },
1470
+ { binding: 2, resource: this.viewOf(this.noiseTex!) },
1471
+ { binding: 3, resource: this.nearestSampler },
1472
+ { binding: 4, resource: { buffer: this.uniforms["ssao"] } },
1473
+ ]);
1474
+ this.pass(encoder, this.pipelines["ssao"], bg0, this.viewOf(this.ssaoA!), w, h);
1475
+
1476
+ // Blur SSAO
1477
+ this.wu("ssao-blur", [1/w, 1/h]);
1478
+ const bg1 = this.bgCached(this.cdLayout, [
1479
+ { binding: 0, resource: this.viewOf(this.ssaoA!) },
1480
+ { binding: 1, resource: depthView },
1481
+ { binding: 2, resource: this.nearestSampler },
1482
+ { binding: 3, resource: { buffer: this.uniforms["ssao-blur"] } },
1483
+ ]);
1484
+ this.pass(encoder, this.pipelines["ssao-blur"], bg1, this.viewOf(this.ssaoB!), w, h);
1485
+
1486
+ // Composite AO × color
1487
+ this.wu("ssao-composite", [1/w, 1/h]);
1488
+ const bg2 = this.bgCached(this.ccLayout, [
1489
+ { binding: 0, resource: inputView },
1490
+ { binding: 1, resource: this.viewOf(this.ssaoB!) },
1491
+ { binding: 2, resource: this.linearSampler },
1492
+ { binding: 3, resource: { buffer: this.uniforms["ssao-composite"] } },
1493
+ ]);
1494
+ this.pass(encoder, this.pipelines["ssao-composite"], bg2, outputView, w, h);
1495
+ }
1496
+
1497
+ private writeSSAOUniform(w: number, h: number): void {
1498
+ // Layout: invProjection(16) + view(16) + projection(16) + directions + radius + bias
1499
+ // + noiseScale(2) + screenSize(2) + power + thickness + slices + pad(2) = 56 floats
1500
+ const data = new Float32Array(64);
1501
+ if (this.camInvProj) data.set(this.camInvProj, 0);
1502
+ if (this.camView) data.set(this.camView, 16);
1503
+ if (this.camProj) data.set(this.camProj, 32);
1504
+ data[48] = this.ssaoDirections;
1505
+ data[49] = this.ssaoRadius;
1506
+ data[50] = this.ssaoBias;
1507
+ data[51] = 4; // noiseSize X
1508
+ data[52] = 4; // noiseSize Y
1509
+ data[53] = w;
1510
+ data[54] = h;
1511
+ data[55] = this.ssaoPower;
1512
+ data[56] = this.ssaoThickness;
1513
+ data[57] = this.ssaoSlices;
1514
+ this.device.queue.writeBuffer(this.uniforms["ssao"], 0, data as unknown as Float32Array<ArrayBuffer>);
1515
+ }
1516
+
1517
+ // ── SSR (color + normal + depth + ray march) ──────────────────────────────
1518
+
1519
+ private applySSR(encoder: GPUCommandEncoder, inputView: GPUTextureView, outputView: GPUTextureView, depthView: GPUTextureView, w: number, h: number): void {
1520
+ const data = new Float32Array(64);
1521
+ if (this.camInvProj) data.set(this.camInvProj, 0);
1522
+ if (this.camView) data.set(this.camView, 16);
1523
+ if (this.camProj) data.set(this.camProj, 32);
1524
+ data[48] = this.ssrMaxSteps;
1525
+ data[49] = this.ssrThickness;
1526
+ data[50] = this.ssrMaxDistance;
1527
+ data[51] = this.ssrFadeStart;
1528
+ data[52] = this.ssrFadeEnd;
1529
+ data[53] = this.ssrBinarySteps;
1530
+ data[54] = this.ssrStride;
1531
+ this.device.queue.writeBuffer(this.uniforms["ssr"], 0, data as unknown as Float32Array<ArrayBuffer>);
1532
+
1533
+ const bg = this.bgCached(this.cvdLayout, [
1534
+ { binding: 0, resource: inputView },
1535
+ { binding: 1, resource: this.viewOf(this.sceneNormals!) },
1536
+ { binding: 2, resource: depthView },
1537
+ { binding: 3, resource: this.nearestSampler },
1538
+ { binding: 4, resource: { buffer: this.uniforms["ssr"] } },
1539
+ ]);
1540
+ this.pass(encoder, this.pipelines["ssr"], bg, outputView, w, h);
1541
+ }
1542
+
1543
+ // ── Motion blur (color + velocity + depth) ────────────────────────────────
1544
+
1545
+ private applyMotionBlur(encoder: GPUCommandEncoder, inputView: GPUTextureView, outputView: GPUTextureView, depthView: GPUTextureView, w: number, h: number): void {
1546
+ this.wu("motion-blur", [1/w, 1/h, this.motionBlurIntensity, this.motionBlurMaxSamples]);
1547
+ const bg = this.bgCached(this.cvdLayout, [
1548
+ { binding: 0, resource: inputView },
1549
+ { binding: 1, resource: this.viewOf(this.sceneVelocity!) },
1550
+ { binding: 2, resource: depthView },
1551
+ { binding: 3, resource: this.nearestSampler },
1552
+ { binding: 4, resource: { buffer: this.uniforms["motion-blur"] } },
1553
+ ]);
1554
+ this.pass(encoder, this.pipelines["motion-blur"], bg, outputView, w, h);
1555
+ }
1556
+
1557
+ // ── Edges (color + normal + depth) ────────────────────────────────────────
1558
+
1559
+ private applyEdges(encoder: GPUCommandEncoder, inputView: GPUTextureView, outputView: GPUTextureView, depthView: GPUTextureView, w: number, h: number): void {
1560
+ this.wu("edges", [1/w, 1/h, this.edgesThreshold, this.edgesOpacity, 0, 0, 0, 0, ...this.edgesEdgeColor, 0, 0]);
1561
+ const bg = this.bgCached(this.cvdLayout, [
1562
+ { binding: 0, resource: inputView },
1563
+ { binding: 1, resource: this.viewOf(this.sceneNormals!) },
1564
+ { binding: 2, resource: depthView },
1565
+ { binding: 3, resource: this.nearestSampler },
1566
+ { binding: 4, resource: { buffer: this.uniforms["edges"] } },
1567
+ ]);
1568
+ this.pass(encoder, this.pipelines["edges"], bg, outputView, w, h);
1569
+ }
1570
+
1571
+ // ── Gaussian blur (two-pass: H then V) ────────────────────────────────────
1572
+
1573
+ private applyGaussianBlur(encoder: GPUCommandEncoder, inputView: GPUTextureView, outputView: GPUTextureView, w: number, h: number): void {
1574
+ const dummy = this.viewOf(this.dummyTex);
1575
+ // H pass
1576
+ this.wu("gaussian-blur", [1/w, 1/h, 1.0, 0.0, this.gaussianBlurRadius]);
1577
+ this.pass(encoder, this.pipelines["gaussian-blur"], this.bgCached(this.ccLayout, [
1578
+ { binding: 0, resource: inputView }, { binding: 1, resource: dummy },
1579
+ { binding: 2, resource: this.linearSampler }, { binding: 3, resource: { buffer: this.uniforms["gaussian-blur"] } },
1580
+ ]), this.viewOf(this.blurA!), w, h);
1581
+ // V pass
1582
+ this.wu("gaussian-blur", [1/w, 1/h, 0.0, 1.0, this.gaussianBlurRadius]);
1583
+ this.pass(encoder, this.pipelines["gaussian-blur"], this.bgCached(this.ccLayout, [
1584
+ { binding: 0, resource: this.viewOf(this.blurA!) }, { binding: 1, resource: dummy },
1585
+ { binding: 2, resource: this.linearSampler }, { binding: 3, resource: { buffer: this.uniforms["gaussian-blur"] } },
1586
+ ]), outputView, w, h);
1587
+ }
1588
+
1589
+ // ── Outline (color + mask) ────────────────────────────────────────────────
1590
+
1591
+ private applyOutline(encoder: GPUCommandEncoder, inputView: GPUTextureView, outputView: GPUTextureView, w: number, h: number): void {
1592
+ this.wu("outline", [1/w, 1/h, this.outlineWidth, this.outlineOpacity, 0, 0, 0, 0, ...this.outlineColor, 0, 0]);
1593
+ const bg = this.bgCached(this.ccLayout, [
1594
+ { binding: 0, resource: inputView },
1595
+ { binding: 1, resource: this.viewOf(this.sceneMask!) },
1596
+ { binding: 2, resource: this.linearSampler },
1597
+ { binding: 3, resource: { buffer: this.uniforms["outline"] } },
1598
+ ]);
1599
+ this.pass(encoder, this.pipelines["outline"], bg, outputView, w, h);
1600
+ }
1601
+
1602
+ // ── Highlight (blur H + blur V + composite) ───────────────────────────────
1603
+
1604
+ private applyHighlight(encoder: GPUCommandEncoder, inputView: GPUTextureView, outputView: GPUTextureView, w: number, h: number): void {
1605
+ const dummy = this.viewOf(this.dummyTex);
1606
+ const maskView = this.viewOf(this.sceneMask!);
1607
+
1608
+ // Blur H
1609
+ this.wu("highlight-blur", [1/w, 1/h, 1.0, 0.0, this.highlightBlurRadius]);
1610
+ this.pass(encoder, this.pipelines["highlight-blur"], this.bgCached(this.ccLayout, [
1611
+ { binding: 0, resource: maskView }, { binding: 1, resource: dummy },
1612
+ { binding: 2, resource: this.linearSampler }, { binding: 3, resource: { buffer: this.uniforms["highlight-blur"] } },
1613
+ ]), this.viewOf(this.blurA!), w, h);
1614
+
1615
+ // Blur V
1616
+ this.wu("highlight-blur", [1/w, 1/h, 0.0, 1.0, this.highlightBlurRadius]);
1617
+ this.pass(encoder, this.pipelines["highlight-blur"], this.bgCached(this.ccLayout, [
1618
+ { binding: 0, resource: this.viewOf(this.blurA!) }, { binding: 1, resource: dummy },
1619
+ { binding: 2, resource: this.linearSampler }, { binding: 3, resource: { buffer: this.uniforms["highlight-blur"] } },
1620
+ ]), this.viewOf(this.blurB!), w, h);
1621
+
1622
+ // Composite
1623
+ this.wu("highlight-composite", [this.highlightIntensity, this.highlightInnerOpacity]);
1624
+ const bg = this.bgCached(this.cvvhLayout, [
1625
+ { binding: 0, resource: inputView },
1626
+ { binding: 1, resource: this.viewOf(this.blurB!) },
1627
+ { binding: 2, resource: maskView },
1628
+ { binding: 3, resource: this.linearSampler },
1629
+ { binding: 4, resource: { buffer: this.uniforms["highlight-composite"] } },
1630
+ ]);
1631
+ this.pass(encoder, this.pipelines["highlight-composite"], bg, outputView, w, h);
1632
+ }
1633
+
1634
+ // ── Glow (blur H + blur V + composite) ────────────────────────────────────
1635
+
1636
+ private applyGlow(encoder: GPUCommandEncoder, inputView: GPUTextureView, outputView: GPUTextureView, w: number, h: number): void {
1637
+ const dummy = this.viewOf(this.dummyTex);
1638
+ const maskView = this.viewOf(this.sceneMask!);
1639
+
1640
+ // Blur H
1641
+ this.wu("glow-blur", [1/w, 1/h, 1.0, 0.0, this.glowBlurRadius]);
1642
+ this.pass(encoder, this.pipelines["glow-blur"], this.bgCached(this.ccLayout, [
1643
+ { binding: 0, resource: maskView }, { binding: 1, resource: dummy },
1644
+ { binding: 2, resource: this.linearSampler }, { binding: 3, resource: { buffer: this.uniforms["glow-blur"] } },
1645
+ ]), this.viewOf(this.blurA!), w, h);
1646
+
1647
+ // Blur V
1648
+ this.wu("glow-blur", [1/w, 1/h, 0.0, 1.0, this.glowBlurRadius]);
1649
+ this.pass(encoder, this.pipelines["glow-blur"], this.bgCached(this.ccLayout, [
1650
+ { binding: 0, resource: this.viewOf(this.blurA!) }, { binding: 1, resource: dummy },
1651
+ { binding: 2, resource: this.linearSampler }, { binding: 3, resource: { buffer: this.uniforms["glow-blur"] } },
1652
+ ]), this.viewOf(this.blurB!), w, h);
1653
+
1654
+ // Composite
1655
+ this.wu("glow-composite", [this.glowIntensity]);
1656
+ const bg = this.bgCached(this.ccLayout, [
1657
+ { binding: 0, resource: inputView },
1658
+ { binding: 1, resource: this.viewOf(this.blurB!) },
1659
+ { binding: 2, resource: this.linearSampler },
1660
+ { binding: 3, resource: { buffer: this.uniforms["glow-composite"] } },
1661
+ ]);
1662
+ this.pass(encoder, this.pipelines["glow-composite"], bg, outputView, w, h);
1663
+ }
1664
+
1665
+ // ── LUT (3D color grading — dedicated lutLayout with texture_3d) ──────────
1666
+
1667
+ private applyLUT(encoder: GPUCommandEncoder, inputView: GPUTextureView, outputView: GPUTextureView, w: number, h: number): void {
1668
+ if (!this.lutView) {
1669
+ // No LUT loaded — blit input to output (HDR ping-pong in chain)
1670
+ this.applyBlitHDR(encoder, inputView, outputView, w, h);
1671
+ return;
1672
+ }
1673
+ this.wu("lut", [this.lutEnabled ? 1 : 0, this.lutSize, 0, 0]);
1674
+ const bg = this.bgCached(this.lutLayout, [
1675
+ { binding: 0, resource: inputView },
1676
+ { binding: 1, resource: this.lutView },
1677
+ { binding: 2, resource: this.linearSampler },
1678
+ { binding: 3, resource: { buffer: this.uniforms["lut"] } },
1679
+ ]);
1680
+ this.pass(encoder, this.pipelines["lut"], bg, outputView, w, h);
1681
+ }
1682
+
1683
+ // ── Dithering (color + noise + sampler + uniform, ccLayout) ───────────────
1684
+
1685
+ private applyDithering(encoder: GPUCommandEncoder, inputView: GPUTextureView, outputView: GPUTextureView, w: number, h: number): void {
1686
+ this.wu("dithering", [1/w, 1/h, this.dMode, this.dStrength, this.dLevels, 0, 0, 0]);
1687
+ const bg = this.bgCached(this.ccLayout, [
1688
+ { binding: 0, resource: inputView },
1689
+ { binding: 1, resource: this.viewOf(this.noiseTex!) },
1690
+ { binding: 2, resource: this.linearSampler },
1691
+ { binding: 3, resource: { buffer: this.uniforms["dithering"] } },
1692
+ ]);
1693
+ this.pass(encoder, this.pipelines["dithering"], bg, outputView, w, h);
1694
+ }
1695
+
1696
+ // ── Watercolor (color + noise + sampler + uniform, ccLayout) ──────────────
1697
+
1698
+ private applyWatercolor(encoder: GPUCommandEncoder, inputView: GPUTextureView, outputView: GPUTextureView, w: number, h: number): void {
1699
+ this.wu("watercolor", [1/w, 1/h, this.wcEdgeStrength, this.wcPaperScale, this.wcBlend, 0, 0, 0]);
1700
+ const bg = this.bgCached(this.ccLayout, [
1701
+ { binding: 0, resource: inputView },
1702
+ { binding: 1, resource: this.viewOf(this.noiseTex!) },
1703
+ { binding: 2, resource: this.linearSampler },
1704
+ { binding: 3, resource: { buffer: this.uniforms["watercolor"] } },
1705
+ ]);
1706
+ this.pass(encoder, this.pipelines["watercolor"], bg, outputView, w, h);
1707
+ }
1708
+
1709
+ // ── Blit (copy rgba16float → canvas format) ───────────────────────────────
1710
+
1711
+ private applyBlit(encoder: GPUCommandEncoder, inputView: GPUTextureView, outputView: GPUTextureView, w: number, h: number): void {
1712
+ this.wu("blit", [1/w, 1/h]);
1713
+ const bg = this.bgCached(this.ccLayout, [
1714
+ { binding: 0, resource: inputView },
1715
+ { binding: 1, resource: this.viewOf(this.dummyTex) },
1716
+ { binding: 2, resource: this.linearSampler },
1717
+ { binding: 3, resource: { buffer: this.uniforms["blit"] } },
1718
+ ]);
1719
+ this.pass(encoder, this.pipelines["blit"], bg, outputView, w, h);
1720
+ }
1721
+
1722
+ /** HDR blit variant for intermediate chain passes (writes to rgba16float ping-pong). */
1723
+ private applyBlitHDR(encoder: GPUCommandEncoder, inputView: GPUTextureView, outputView: GPUTextureView, w: number, h: number): void {
1724
+ this.wu("blit", [1/w, 1/h]);
1725
+ const bg = this.bgCached(this.ccLayout, [
1726
+ { binding: 0, resource: inputView },
1727
+ { binding: 1, resource: this.viewOf(this.dummyTex) },
1728
+ { binding: 2, resource: this.linearSampler },
1729
+ { binding: 3, resource: { buffer: this.uniforms["blit"] } },
1730
+ ]);
1731
+ this.pass(encoder, this.pipelines["blit-hdr"], bg, outputView, w, h);
1732
+ }
1733
+
1734
+ // ── Destroy ───────────────────────────────────────────────────────────────
1735
+
1736
+ destroy(): void {
1737
+ this.destroyAllTargets();
1738
+ this.dummyTex?.destroy();
1739
+ this.glyphTex?.destroy();
1740
+ this.noiseTex?.destroy();
1741
+ this.lutTexture?.destroy();
1742
+ this.occluderUniform?.destroy();
1743
+ for (const key in this.uniforms) this.uniforms[key]?.destroy();
1744
+ for (const key in this.pipelines) this.pipelines[key]?.destroy?.();
1745
+ // Custom effects
1746
+ for (const [, e] of this.customEffects.entries()) {
1747
+ e.pipeline.destroy?.();
1748
+ e.uniform.destroy();
1749
+ }
1750
+ this.customEffects.clear();
1751
+ }
1752
+ }