@aura3d/rendering 2.0.4 → 3.0.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 (374) hide show
  1. package/dist/AtmosphereWetness.d.ts +61 -0
  2. package/dist/AtmosphereWetness.d.ts.map +1 -0
  3. package/dist/AtmosphereWetness.js +99 -0
  4. package/dist/AtmosphereWetness.js.map +1 -0
  5. package/dist/CascadedShadowMaps.d.ts.map +1 -1
  6. package/dist/CascadedShadowMaps.js +3 -1
  7. package/dist/CascadedShadowMaps.js.map +1 -1
  8. package/dist/ClusteredForwardLighting.d.ts +25 -1
  9. package/dist/ClusteredForwardLighting.d.ts.map +1 -1
  10. package/dist/ClusteredForwardLighting.js +80 -3
  11. package/dist/ClusteredForwardLighting.js.map +1 -1
  12. package/dist/DayNightSky.d.ts +63 -0
  13. package/dist/DayNightSky.d.ts.map +1 -0
  14. package/dist/DayNightSky.js +156 -0
  15. package/dist/DayNightSky.js.map +1 -0
  16. package/dist/EnvironmentPresetPack.d.ts +30 -0
  17. package/dist/EnvironmentPresetPack.d.ts.map +1 -0
  18. package/dist/EnvironmentPresetPack.js +49 -0
  19. package/dist/EnvironmentPresetPack.js.map +1 -0
  20. package/dist/ExtensionScalarAtlas.d.ts +18 -0
  21. package/dist/ExtensionScalarAtlas.d.ts.map +1 -0
  22. package/dist/ExtensionScalarAtlas.js +107 -0
  23. package/dist/ExtensionScalarAtlas.js.map +1 -0
  24. package/dist/ForwardPass.d.ts +65 -0
  25. package/dist/ForwardPass.d.ts.map +1 -1
  26. package/dist/ForwardPass.js +196 -2
  27. package/dist/ForwardPass.js.map +1 -1
  28. package/dist/InstancedPBRMaterial.d.ts +1 -0
  29. package/dist/InstancedPBRMaterial.d.ts.map +1 -1
  30. package/dist/InstancedPBRMaterial.js +3 -0
  31. package/dist/InstancedPBRMaterial.js.map +1 -1
  32. package/dist/InstancingDiagnostics.d.ts +69 -0
  33. package/dist/InstancingDiagnostics.d.ts.map +1 -0
  34. package/dist/InstancingDiagnostics.js +98 -0
  35. package/dist/InstancingDiagnostics.js.map +1 -0
  36. package/dist/LeanWebGL2Device.d.ts +265 -0
  37. package/dist/LeanWebGL2Device.d.ts.map +1 -0
  38. package/dist/LeanWebGL2Device.js +4001 -0
  39. package/dist/LeanWebGL2Device.js.map +1 -0
  40. package/dist/LightingRig.d.ts +19 -1
  41. package/dist/LightingRig.d.ts.map +1 -1
  42. package/dist/LightingRig.js +61 -1
  43. package/dist/LightingRig.js.map +1 -1
  44. package/dist/MaterialBinding.js +1 -1
  45. package/dist/MaterialBinding.js.map +1 -1
  46. package/dist/MeshConsolidation.d.ts +12 -0
  47. package/dist/MeshConsolidation.d.ts.map +1 -1
  48. package/dist/MeshConsolidation.js +28 -0
  49. package/dist/MeshConsolidation.js.map +1 -1
  50. package/dist/MorphTargetPlan.d.ts +29 -2
  51. package/dist/MorphTargetPlan.d.ts.map +1 -1
  52. package/dist/MorphTargetPlan.js +23 -4
  53. package/dist/MorphTargetPlan.js.map +1 -1
  54. package/dist/NormalMappedPBRMaterial.d.ts.map +1 -1
  55. package/dist/NormalMappedPBRMaterial.js +22 -2
  56. package/dist/NormalMappedPBRMaterial.js.map +1 -1
  57. package/dist/OceanSurface.d.ts +75 -0
  58. package/dist/OceanSurface.d.ts.map +1 -1
  59. package/dist/OceanSurface.js +211 -0
  60. package/dist/OceanSurface.js.map +1 -1
  61. package/dist/PBRMaterial.d.ts +1 -0
  62. package/dist/PBRMaterial.d.ts.map +1 -1
  63. package/dist/PBRMaterial.js +23 -2
  64. package/dist/PBRMaterial.js.map +1 -1
  65. package/dist/PbrReference.d.ts +48 -0
  66. package/dist/PbrReference.d.ts.map +1 -1
  67. package/dist/PbrReference.js +126 -0
  68. package/dist/PbrReference.js.map +1 -1
  69. package/dist/PlanarReflection.d.ts +221 -0
  70. package/dist/PlanarReflection.d.ts.map +1 -0
  71. package/dist/PlanarReflection.js +541 -0
  72. package/dist/PlanarReflection.js.map +1 -0
  73. package/dist/PostProcessPass.d.ts +32 -0
  74. package/dist/PostProcessPass.d.ts.map +1 -1
  75. package/dist/PostProcessPass.js +79 -36
  76. package/dist/PostProcessPass.js.map +1 -1
  77. package/dist/ProjectionMath.d.ts +3 -0
  78. package/dist/ProjectionMath.d.ts.map +1 -0
  79. package/dist/ProjectionMath.js +26 -0
  80. package/dist/ProjectionMath.js.map +1 -0
  81. package/dist/ReflectionSurfaces.d.ts +17 -0
  82. package/dist/ReflectionSurfaces.d.ts.map +1 -1
  83. package/dist/ReflectionSurfaces.js +103 -1
  84. package/dist/ReflectionSurfaces.js.map +1 -1
  85. package/dist/RenderDevice.d.ts +92 -0
  86. package/dist/RenderDevice.d.ts.map +1 -1
  87. package/dist/RenderDevice.js +85 -0
  88. package/dist/RenderDevice.js.map +1 -1
  89. package/dist/Renderer.d.ts +9 -0
  90. package/dist/Renderer.d.ts.map +1 -1
  91. package/dist/Renderer.js +97 -9
  92. package/dist/Renderer.js.map +1 -1
  93. package/dist/RendererPostprocessPlan.d.ts +64 -3
  94. package/dist/RendererPostprocessPlan.d.ts.map +1 -1
  95. package/dist/RendererPostprocessPlan.js +116 -12
  96. package/dist/RendererPostprocessPlan.js.map +1 -1
  97. package/dist/RendererVisualPipelineReport.d.ts.map +1 -1
  98. package/dist/RendererVisualPipelineReport.js +3 -1
  99. package/dist/RendererVisualPipelineReport.js.map +1 -1
  100. package/dist/Sampler.d.ts +29 -0
  101. package/dist/Sampler.d.ts.map +1 -1
  102. package/dist/Sampler.js +47 -0
  103. package/dist/Sampler.js.map +1 -1
  104. package/dist/ScreenSpaceReflectionPass.d.ts +43 -0
  105. package/dist/ScreenSpaceReflectionPass.d.ts.map +1 -0
  106. package/dist/ScreenSpaceReflectionPass.js +58 -0
  107. package/dist/ScreenSpaceReflectionPass.js.map +1 -0
  108. package/dist/SdfText.d.ts +250 -0
  109. package/dist/SdfText.d.ts.map +1 -0
  110. package/dist/SdfText.js +551 -0
  111. package/dist/SdfText.js.map +1 -0
  112. package/dist/ShaderChunks.d.ts.map +1 -1
  113. package/dist/ShaderChunks.js +30 -12
  114. package/dist/ShaderChunks.js.map +1 -1
  115. package/dist/ShaderLibrary.d.ts.map +1 -1
  116. package/dist/ShaderLibrary.js +601 -186
  117. package/dist/ShaderLibrary.js.map +1 -1
  118. package/dist/ShaderLibraryCore.d.ts +3 -0
  119. package/dist/ShaderLibraryCore.d.ts.map +1 -1
  120. package/dist/ShaderLibraryCore.js +73 -12
  121. package/dist/ShaderLibraryCore.js.map +1 -1
  122. package/dist/ShadowMap.d.ts +24 -0
  123. package/dist/ShadowMap.d.ts.map +1 -1
  124. package/dist/ShadowMap.js +43 -0
  125. package/dist/ShadowMap.js.map +1 -1
  126. package/dist/SkinnedLitMaterial.d.ts.map +1 -1
  127. package/dist/SkinnedLitMaterial.js +2 -0
  128. package/dist/SkinnedLitMaterial.js.map +1 -1
  129. package/dist/SkinningBounds.d.ts +10 -0
  130. package/dist/SkinningBounds.d.ts.map +1 -1
  131. package/dist/SkinningBounds.js +27 -0
  132. package/dist/SkinningBounds.js.map +1 -1
  133. package/dist/SpriteFlipbook.d.ts +60 -0
  134. package/dist/SpriteFlipbook.d.ts.map +1 -0
  135. package/dist/SpriteFlipbook.js +128 -0
  136. package/dist/SpriteFlipbook.js.map +1 -0
  137. package/dist/TemporalHistory.d.ts +37 -0
  138. package/dist/TemporalHistory.d.ts.map +1 -0
  139. package/dist/TemporalHistory.js +135 -0
  140. package/dist/TemporalHistory.js.map +1 -0
  141. package/dist/TemporalMath.d.ts +3 -0
  142. package/dist/TemporalMath.d.ts.map +1 -0
  143. package/dist/TemporalMath.js +9 -0
  144. package/dist/TemporalMath.js.map +1 -0
  145. package/dist/TerrainTiles.d.ts +120 -0
  146. package/dist/TerrainTiles.d.ts.map +1 -0
  147. package/dist/TerrainTiles.js +221 -0
  148. package/dist/TerrainTiles.js.map +1 -0
  149. package/dist/TexturedPBRMaterial.d.ts +4 -0
  150. package/dist/TexturedPBRMaterial.d.ts.map +1 -1
  151. package/dist/TexturedPBRMaterial.js +88 -5
  152. package/dist/TexturedPBRMaterial.js.map +1 -1
  153. package/dist/TexturedUnlitMaterial.d.ts.map +1 -1
  154. package/dist/TexturedUnlitMaterial.js +20 -2
  155. package/dist/TexturedUnlitMaterial.js.map +1 -1
  156. package/dist/UnlitMaterial.d.ts.map +1 -1
  157. package/dist/UnlitMaterial.js +20 -2
  158. package/dist/UnlitMaterial.js.map +1 -1
  159. package/dist/VertexFormat.d.ts +6 -0
  160. package/dist/VertexFormat.d.ts.map +1 -1
  161. package/dist/VertexFormat.js +12 -0
  162. package/dist/VertexFormat.js.map +1 -1
  163. package/dist/VolumetricFog.d.ts +55 -0
  164. package/dist/VolumetricFog.d.ts.map +1 -0
  165. package/dist/VolumetricFog.js +91 -0
  166. package/dist/VolumetricFog.js.map +1 -0
  167. package/dist/WaterSurface.d.ts +77 -0
  168. package/dist/WaterSurface.d.ts.map +1 -0
  169. package/dist/WaterSurface.js +133 -0
  170. package/dist/WaterSurface.js.map +1 -0
  171. package/dist/WebGL2Device.d.ts +73 -0
  172. package/dist/WebGL2Device.d.ts.map +1 -1
  173. package/dist/WebGL2Device.js +836 -79
  174. package/dist/WebGL2Device.js.map +1 -1
  175. package/dist/WebGPUDevice.d.ts +62 -1
  176. package/dist/WebGPUDevice.d.ts.map +1 -1
  177. package/dist/WebGPUDevice.js +844 -51
  178. package/dist/WebGPUDevice.js.map +1 -1
  179. package/dist/WebGPUExtensionAtlas.d.ts +6 -0
  180. package/dist/WebGPUExtensionAtlas.d.ts.map +1 -0
  181. package/dist/WebGPUExtensionAtlas.js +225 -0
  182. package/dist/WebGPUExtensionAtlas.js.map +1 -0
  183. package/dist/WebGPUSkinningLimits.d.ts +40 -0
  184. package/dist/WebGPUSkinningLimits.d.ts.map +1 -1
  185. package/dist/WebGPUSkinningLimits.js +32 -0
  186. package/dist/WebGPUSkinningLimits.js.map +1 -1
  187. package/dist/effects/CollisionModule.d.ts +9 -0
  188. package/dist/effects/CollisionModule.d.ts.map +1 -1
  189. package/dist/effects/CollisionModule.js +10 -0
  190. package/dist/effects/CollisionModule.js.map +1 -1
  191. package/dist/effects/ColorModule.d.ts +7 -0
  192. package/dist/effects/ColorModule.d.ts.map +1 -1
  193. package/dist/effects/ColorModule.js +23 -0
  194. package/dist/effects/ColorModule.js.map +1 -1
  195. package/dist/effects/ForceModule.d.ts +35 -0
  196. package/dist/effects/ForceModule.d.ts.map +1 -1
  197. package/dist/effects/ForceModule.js +49 -0
  198. package/dist/effects/ForceModule.js.map +1 -1
  199. package/dist/effects/GPUParticleBackend.d.ts +188 -0
  200. package/dist/effects/GPUParticleBackend.d.ts.map +1 -1
  201. package/dist/effects/GPUParticleBackend.js +710 -1
  202. package/dist/effects/GPUParticleBackend.js.map +1 -1
  203. package/dist/effects/HeightfieldModule.d.ts +58 -0
  204. package/dist/effects/HeightfieldModule.d.ts.map +1 -0
  205. package/dist/effects/HeightfieldModule.js +129 -0
  206. package/dist/effects/HeightfieldModule.js.map +1 -0
  207. package/dist/effects/LightingModule.d.ts +34 -0
  208. package/dist/effects/LightingModule.d.ts.map +1 -0
  209. package/dist/effects/LightingModule.js +54 -0
  210. package/dist/effects/LightingModule.js.map +1 -0
  211. package/dist/effects/ParticleDiagnostics.d.ts +8 -0
  212. package/dist/effects/ParticleDiagnostics.d.ts.map +1 -1
  213. package/dist/effects/ParticleDiagnostics.js +3 -0
  214. package/dist/effects/ParticleDiagnostics.js.map +1 -1
  215. package/dist/effects/ParticleModule.d.ts +8 -0
  216. package/dist/effects/ParticleModule.d.ts.map +1 -1
  217. package/dist/effects/ParticleModule.js.map +1 -1
  218. package/dist/effects/ParticleRenderer.d.ts +20 -0
  219. package/dist/effects/ParticleRenderer.d.ts.map +1 -1
  220. package/dist/effects/ParticleRenderer.js +57 -7
  221. package/dist/effects/ParticleRenderer.js.map +1 -1
  222. package/dist/effects/ParticleSystem.d.ts +45 -1
  223. package/dist/effects/ParticleSystem.d.ts.map +1 -1
  224. package/dist/effects/ParticleSystem.js +373 -4
  225. package/dist/effects/ParticleSystem.js.map +1 -1
  226. package/dist/effects/ResidentGPUParticleRenderer.d.ts +158 -0
  227. package/dist/effects/ResidentGPUParticleRenderer.d.ts.map +1 -0
  228. package/dist/effects/ResidentGPUParticleRenderer.js +576 -0
  229. package/dist/effects/ResidentGPUParticleRenderer.js.map +1 -0
  230. package/dist/effects/SizeModule.d.ts +4 -0
  231. package/dist/effects/SizeModule.d.ts.map +1 -1
  232. package/dist/effects/SizeModule.js +16 -0
  233. package/dist/effects/SizeModule.js.map +1 -1
  234. package/dist/effects/SubEmitterModule.d.ts +61 -0
  235. package/dist/effects/SubEmitterModule.d.ts.map +1 -0
  236. package/dist/effects/SubEmitterModule.js +119 -0
  237. package/dist/effects/SubEmitterModule.js.map +1 -0
  238. package/dist/effects/TrailModule.d.ts +38 -0
  239. package/dist/effects/TrailModule.d.ts.map +1 -1
  240. package/dist/effects/TrailModule.js +95 -2
  241. package/dist/effects/TrailModule.js.map +1 -1
  242. package/dist/effects/TurbulenceModule.d.ts +43 -0
  243. package/dist/effects/TurbulenceModule.d.ts.map +1 -0
  244. package/dist/effects/TurbulenceModule.js +130 -0
  245. package/dist/effects/TurbulenceModule.js.map +1 -0
  246. package/dist/effects/index.d.ts +4 -0
  247. package/dist/effects/index.d.ts.map +1 -1
  248. package/dist/effects/index.js +4 -0
  249. package/dist/effects/index.js.map +1 -1
  250. package/dist/extension-scalar-atlas.d.ts +3 -0
  251. package/dist/extension-scalar-atlas.d.ts.map +1 -0
  252. package/dist/extension-scalar-atlas.js +2 -0
  253. package/dist/extension-scalar-atlas.js.map +1 -0
  254. package/dist/index.d.ts +58 -19
  255. package/dist/index.d.ts.map +1 -1
  256. package/dist/index.js +32 -10
  257. package/dist/index.js.map +1 -1
  258. package/dist/lean/LeanProductionRenderer.d.ts.map +1 -1
  259. package/dist/lean/LeanProductionRenderer.js +2 -2
  260. package/dist/lean/LeanProductionRenderer.js.map +1 -1
  261. package/dist/lean-core-runtime.d.ts +13 -0
  262. package/dist/lean-core-runtime.d.ts.map +1 -0
  263. package/dist/lean-core-runtime.js +10 -0
  264. package/dist/lean-core-runtime.js.map +1 -0
  265. package/dist/materials/MaterialExtensions.js +3 -3
  266. package/dist/materials/MaterialExtensions.js.map +1 -1
  267. package/dist/postprocess/NativeBloomPyramid.d.ts +44 -0
  268. package/dist/postprocess/NativeBloomPyramid.d.ts.map +1 -0
  269. package/dist/postprocess/NativeBloomPyramid.js +84 -0
  270. package/dist/postprocess/NativeBloomPyramid.js.map +1 -0
  271. package/dist/postprocess/NativeLdrEffectLuts.d.ts +10 -1
  272. package/dist/postprocess/NativeLdrEffectLuts.d.ts.map +1 -1
  273. package/dist/postprocess/NativeLdrEffectLuts.js +19 -2
  274. package/dist/postprocess/NativeLdrEffectLuts.js.map +1 -1
  275. package/dist/postprocess/index.d.ts +2 -0
  276. package/dist/postprocess/index.d.ts.map +1 -1
  277. package/dist/postprocess/index.js +1 -0
  278. package/dist/postprocess/index.js.map +1 -1
  279. package/dist/production-runtime/PBRHDRPipeline.d.ts +22 -0
  280. package/dist/production-runtime/PBRHDRPipeline.d.ts.map +1 -1
  281. package/dist/production-runtime/PBRHDRPipeline.js +125 -0
  282. package/dist/production-runtime/PBRHDRPipeline.js.map +1 -1
  283. package/dist/production-runtime/ProductionRendererTypes.d.ts +1 -0
  284. package/dist/production-runtime/ProductionRendererTypes.d.ts.map +1 -1
  285. package/dist/production-runtime/ProductionRendererTypes.js.map +1 -1
  286. package/dist/production-runtime/ProductionRuntimeRenderer.d.ts +3 -0
  287. package/dist/production-runtime/ProductionRuntimeRenderer.d.ts.map +1 -1
  288. package/dist/production-runtime/ProductionRuntimeRenderer.js +8 -1
  289. package/dist/production-runtime/ProductionRuntimeRenderer.js.map +1 -1
  290. package/dist/production-runtime/ProductionWebGL2Renderer.d.ts +4 -0
  291. package/dist/production-runtime/ProductionWebGL2Renderer.d.ts.map +1 -1
  292. package/dist/production-runtime/ProductionWebGL2Renderer.js +15 -0
  293. package/dist/production-runtime/ProductionWebGL2Renderer.js.map +1 -1
  294. package/dist/production-runtime/ProductionWebGPURenderer.d.ts +58 -0
  295. package/dist/production-runtime/ProductionWebGPURenderer.d.ts.map +1 -1
  296. package/dist/production-runtime/ProductionWebGPURenderer.js +127 -0
  297. package/dist/production-runtime/ProductionWebGPURenderer.js.map +1 -1
  298. package/dist/production-runtime/framegraph/FrameGraph.d.ts +4 -2
  299. package/dist/production-runtime/framegraph/FrameGraph.d.ts.map +1 -1
  300. package/dist/production-runtime/framegraph/FrameGraph.js +62 -4
  301. package/dist/production-runtime/framegraph/FrameGraph.js.map +1 -1
  302. package/dist/production-runtime/framegraph/NativeFrameGraphBindings.d.ts +32 -0
  303. package/dist/production-runtime/framegraph/NativeFrameGraphBindings.d.ts.map +1 -0
  304. package/dist/production-runtime/framegraph/NativeFrameGraphBindings.js +111 -0
  305. package/dist/production-runtime/framegraph/NativeFrameGraphBindings.js.map +1 -0
  306. package/dist/production-runtime/framegraph/RenderPass.d.ts +23 -0
  307. package/dist/production-runtime/framegraph/RenderPass.d.ts.map +1 -1
  308. package/dist/production-runtime/framegraph/RenderPass.js +87 -1
  309. package/dist/production-runtime/framegraph/RenderPass.js.map +1 -1
  310. package/dist/production-runtime/index.d.ts +7 -4
  311. package/dist/production-runtime/index.d.ts.map +1 -1
  312. package/dist/production-runtime/index.js +4 -2
  313. package/dist/production-runtime/index.js.map +1 -1
  314. package/dist/production-runtime/passes/DepthPrepass.d.ts +18 -1
  315. package/dist/production-runtime/passes/DepthPrepass.d.ts.map +1 -1
  316. package/dist/production-runtime/passes/DepthPrepass.js +41 -2
  317. package/dist/production-runtime/passes/DepthPrepass.js.map +1 -1
  318. package/dist/production-runtime/passes/FramegraphTopology.d.ts +13 -0
  319. package/dist/production-runtime/passes/FramegraphTopology.d.ts.map +1 -0
  320. package/dist/production-runtime/passes/FramegraphTopology.js +59 -0
  321. package/dist/production-runtime/passes/FramegraphTopology.js.map +1 -0
  322. package/dist/production-runtime/passes/OpaquePass.d.ts +19 -1
  323. package/dist/production-runtime/passes/OpaquePass.d.ts.map +1 -1
  324. package/dist/production-runtime/passes/OpaquePass.js +43 -2
  325. package/dist/production-runtime/passes/OpaquePass.js.map +1 -1
  326. package/dist/production-runtime/passes/ShadowPass.d.ts +19 -1
  327. package/dist/production-runtime/passes/ShadowPass.d.ts.map +1 -1
  328. package/dist/production-runtime/passes/ShadowPass.js +45 -2
  329. package/dist/production-runtime/passes/ShadowPass.js.map +1 -1
  330. package/dist/production-runtime/passes/SkyboxPass.d.ts +17 -1
  331. package/dist/production-runtime/passes/SkyboxPass.d.ts.map +1 -1
  332. package/dist/production-runtime/passes/SkyboxPass.js +41 -2
  333. package/dist/production-runtime/passes/SkyboxPass.js.map +1 -1
  334. package/dist/production-runtime/passes/ToneMappingPass.d.ts +26 -1
  335. package/dist/production-runtime/passes/ToneMappingPass.d.ts.map +1 -1
  336. package/dist/production-runtime/passes/ToneMappingPass.js +62 -2
  337. package/dist/production-runtime/passes/ToneMappingPass.js.map +1 -1
  338. package/dist/production-runtime/passes/TransparentPass.d.ts +19 -1
  339. package/dist/production-runtime/passes/TransparentPass.d.ts.map +1 -1
  340. package/dist/production-runtime/passes/TransparentPass.js +47 -2
  341. package/dist/production-runtime/passes/TransparentPass.js.map +1 -1
  342. package/dist/reflection-surfaces.d.ts +5 -0
  343. package/dist/reflection-surfaces.d.ts.map +1 -0
  344. package/dist/reflection-surfaces.js +3 -0
  345. package/dist/reflection-surfaces.js.map +1 -0
  346. package/dist/shadows/CascadeHysteresis.d.ts +67 -0
  347. package/dist/shadows/CascadeHysteresis.d.ts.map +1 -0
  348. package/dist/shadows/CascadeHysteresis.js +104 -0
  349. package/dist/shadows/CascadeHysteresis.js.map +1 -0
  350. package/dist/shadows/ContactShadows.d.ts +52 -0
  351. package/dist/shadows/ContactShadows.d.ts.map +1 -1
  352. package/dist/shadows/ContactShadows.js +122 -0
  353. package/dist/shadows/ContactShadows.js.map +1 -1
  354. package/dist/shadows/SpotShadowMaps.d.ts +77 -0
  355. package/dist/shadows/SpotShadowMaps.d.ts.map +1 -0
  356. package/dist/shadows/SpotShadowMaps.js +106 -0
  357. package/dist/shadows/SpotShadowMaps.js.map +1 -0
  358. package/dist/webgpu/WebGPUPostProcess.d.ts +2 -0
  359. package/dist/webgpu/WebGPUPostProcess.d.ts.map +1 -1
  360. package/dist/webgpu/WebGPUPostProcess.js +1 -0
  361. package/dist/webgpu/WebGPUPostProcess.js.map +1 -1
  362. package/dist/webgpu/WebGPUPostShaders.d.ts +77 -0
  363. package/dist/webgpu/WebGPUPostShaders.d.ts.map +1 -0
  364. package/dist/webgpu/WebGPUPostShaders.js +261 -0
  365. package/dist/webgpu/WebGPUPostShaders.js.map +1 -0
  366. package/dist/webgpu/WebGPUTemporal.d.ts +17 -0
  367. package/dist/webgpu/WebGPUTemporal.d.ts.map +1 -0
  368. package/dist/webgpu/WebGPUTemporal.js +64 -0
  369. package/dist/webgpu/WebGPUTemporal.js.map +1 -0
  370. package/dist/webgpu.d.ts +6 -0
  371. package/dist/webgpu.d.ts.map +1 -1
  372. package/dist/webgpu.js +4 -0
  373. package/dist/webgpu.js.map +1 -1
  374. package/package.json +15 -3
@@ -1,9 +1,12 @@
1
- import { RenderDeviceError, } from "./RenderDevice.js";
1
+ import { temporalAccumulationWeight } from "./TemporalMath.js";
2
+ import { invertSsrProjection } from "./ProjectionMath.js";
3
+ import { RenderDeviceError, resolveGpuTargetOwner, spreadGpuTargetInventory, } from "./RenderDevice.js";
2
4
  import { Texture, isCompressedTextureFormat, isFloatColorTextureFormat } from "./Texture.js";
3
5
  import { isTextureBinding, TextureBinding } from "./TextureBinding.js";
4
6
  import {} from "./VertexFormat.js";
5
7
  import { WebGL2StateCache } from "./WebGL2StateCache.js";
6
8
  import { BLOOM_BRIGHT_LUT_HEIGHT, BLOOM_BRIGHT_LUT_WIDTH, BLOOM_COMPOSITE_LUT_SIZE, OUTLINE_BLEND_LUT_WIDTH, OUTLINE_LIMB_RADIX, createOutlineBlendLut, createOutlineGradientBound, createBloomBrightThresholdLut, createBloomCompositeLut } from "./postprocess/NativeLdrEffectLuts.js";
9
+ import { normalizeBloomQualityPreset, resolveBloomPyramidBlurRadii, resolveBloomPyramidPlan, resolveBloomPyramidResponseGain, } from "./postprocess/NativeBloomPyramid.js";
7
10
  const WEBGL_CUBE_FACES = ["px", "nx", "py", "ny", "pz", "nz"];
8
11
  class WebGL2Buffer {
9
12
  id;
@@ -111,6 +114,8 @@ export class WebGL2Device {
111
114
  gl;
112
115
  nextId = 1;
113
116
  drawCalls = 0;
117
+ nativeTemporalPasses = 0;
118
+ nativeTemporalBindings = 0;
114
119
  buffers = new Set();
115
120
  shaders = new Set();
116
121
  renderTargets = new Set();
@@ -125,19 +130,27 @@ export class WebGL2Device {
125
130
  bloomBrightExtractProgram = null;
126
131
  bloomBlurProgram = null;
127
132
  bloomCompositeProgram = null;
133
+ bloomDownsampleProgram = null;
134
+ bloomAccumulateProgram = null;
135
+ bloomPyramidResources = null;
128
136
  outlineProgram = null;
129
137
  ssaoProgram = null;
130
138
  ssrProgram = null;
131
139
  depthOfFieldProgram = null;
132
140
  motionBlurProgram = null;
133
141
  motionBlurVelocityTexture = null;
142
+ motionBlurVelocitySize = null;
134
143
  taaProgram = null;
144
+ taaPresentationProgram = null;
135
145
  taaHistoryTexture = null;
146
+ taaHistorySize = null;
136
147
  bloomPingPongResources = null;
137
148
  bloomBrightLutTexture = null;
138
149
  bloomBrightLutThreshold = null;
139
150
  bloomCompositeLutTexture = null;
140
151
  bloomCompositeLutIntensity = null;
152
+ bloomCompositeLutShoulder = null;
153
+ lastBloomDiagnostics = null;
141
154
  outlineBlendLutTexture = null;
142
155
  outlineBlendLutKey = null;
143
156
  depthReadbackProgram = null;
@@ -508,7 +521,9 @@ export class WebGL2Device {
508
521
  this.renderTargets.add(target);
509
522
  // Shadow passes label their depth target after the shadow map texture, so the
510
523
  // label is the device-side signal that a shadow depth target was allocated.
511
- if ((descriptor.label ?? "").toLowerCase().includes("shadow"))
524
+ // Cascaded maps use the established renderer-csm-cascade-* labels.
525
+ // Count their actual allocation too, including the first shadow frame.
526
+ if (/shadow|(?:^|-)csm(?:-|$)/i.test(descriptor.label ?? ""))
512
527
  this.shadowRenderTargetsAllocated += 1;
513
528
  this.textures.set(target.colorTexture, colorHandle);
514
529
  this.textureUploadModes.set(target.colorTexture, "rgba8");
@@ -610,6 +625,29 @@ export class WebGL2Device {
610
625
  });
611
626
  }
612
627
  }
628
+ /** Executes the same native SSR kernel used by postprocessing into an owned target. */
629
+ executeReflectionSurfaceSsr(source, normalMask, output, options) {
630
+ this.assertAlive();
631
+ for (const target of [source, normalMask, output]) {
632
+ if (!(target instanceof WebGL2RenderTarget) || !this.renderTargets.has(target) || target.disposed) {
633
+ throw new RenderDeviceError("SSR inputs must be live targets owned by this device", "INVALID_RESOURCE");
634
+ }
635
+ }
636
+ const color = source, normals = normalMask, destination = output;
637
+ if (!color.depthTextureHandle || color === destination || normals === destination
638
+ || color.width !== normals.width || color.height !== normals.height) {
639
+ throw new RenderDeviceError("SSR requires sampleable scene depth, matching normal-mask input, and a separate output", "INVALID_RESOURCE");
640
+ }
641
+ this.resolveMultisampleTarget(color);
642
+ this.resolveMultisampleTarget(normals);
643
+ const state = this.prepareFullscreenPresentation(destination.framebuffer, destination.width, destination.height);
644
+ try {
645
+ this.drawSsrKernel(color.colorHandle, color.depthTextureHandle, destination.width, destination.height, { ...options, normalMask: normals.colorHandle }, this.ensurePresentationVertexArray(), { near: 0.1, far: 1000 });
646
+ }
647
+ finally {
648
+ this.restoreFullscreenPresentationState(state, destination.width, destination.height);
649
+ }
650
+ }
613
651
  presentLdrPostprocess(source, options) {
614
652
  this.assertAlive();
615
653
  if (!(source instanceof WebGL2RenderTarget) || !this.renderTargets.has(source) || source.disposed) {
@@ -645,6 +683,10 @@ export class WebGL2Device {
645
683
  pass: depthOfFieldPass ? "depth-of-field" : ssaoPass ? "ssao" : "ssr"
646
684
  });
647
685
  }
686
+ // Raw GL depth is nonlinear (0.1/1000 defaults park the play area past
687
+ // 0.97), which blinded every depth-gated native pass. SSR and DOF compare
688
+ // linearized depth; SSAO keeps its legacy response (documented partial).
689
+ const depthRange = normalizeLdrDepthRange(options.depthRange);
648
690
  const outputTarget = options.outputTarget;
649
691
  if (outputTarget && (!(outputTarget instanceof WebGL2RenderTarget) || !this.renderTargets.has(outputTarget) || outputTarget.disposed)) {
650
692
  throw new RenderDeviceError("Output render target is not a live WebGL2 resource owned by this device", "INVALID_RESOURCE", {
@@ -689,10 +731,10 @@ export class WebGL2Device {
689
731
  if (outlineOptions) {
690
732
  this.ensureOutlineBlendLutTexture(outlineOptions);
691
733
  }
692
- if (motionBlurOptions) {
734
+ if (motionBlurOptions?.velocity) {
693
735
  this.ensureMotionBlurVelocityTexture(source.width, source.height, motionBlurOptions.velocity);
694
736
  }
695
- if (taaOptions) {
737
+ if (taaOptions?.history) {
696
738
  this.ensureTaaHistoryTexture(source.width, source.height, taaOptions.history);
697
739
  }
698
740
  const outputWidth = webglOutputTarget?.width ?? (this.viewportWidth || this.gl.drawingBufferWidth);
@@ -702,8 +744,43 @@ export class WebGL2Device {
702
744
  let ldrSourceHandle = source.colorHandle;
703
745
  let temporarySourceIndex = -1;
704
746
  if (bloomOptions && bloomResources) {
705
- ldrSourceHandle = this.executeNativeBloomPasses(ldrSourceHandle, bloomResources, bloomOptions, vertexArray, sourceIsHdr);
706
- temporarySourceIndex = 1;
747
+ if (bloomOptions.quality === "performance") {
748
+ ldrSourceHandle = this.executeNativeBloomPasses(ldrSourceHandle, bloomResources, bloomOptions, vertexArray, sourceIsHdr);
749
+ temporarySourceIndex = 1;
750
+ this.recordBloomDiagnostics(bloomOptions, source.width, source.height, sourceIsHdr, 1);
751
+ }
752
+ else {
753
+ const plan = resolveBloomPyramidPlan(source.width, source.height, bloomOptions.quality, sourceIsHdr);
754
+ const pyramid = this.ensureBloomPyramidResources(plan, sourceIsHdr);
755
+ const pyramidResult = this.executeNativeBloomPyramidPasses(ldrSourceHandle, source.width, source.height, pyramid, {
756
+ textureA: bloomResources.textures[0],
757
+ framebufferA: bloomResources.framebuffers[0],
758
+ textureB: bloomResources.textures[1],
759
+ framebufferB: bloomResources.framebuffers[1]
760
+ }, bloomOptions, vertexArray, sourceIsHdr);
761
+ // The pyramid sum lands in ping-pong texture A; composite it with the
762
+ // source through the proven single-scale composite program.
763
+ this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, bloomResources.framebuffers[1]);
764
+ this.gl.viewport(0, 0, source.width, source.height);
765
+ const pyramidComposite = this.ensureBloomCompositeProgram();
766
+ this.gl.useProgram(pyramidComposite);
767
+ this.gl.bindVertexArray(vertexArray);
768
+ this.bindFullscreenTexture(0, ldrSourceHandle);
769
+ this.bindFullscreenTexture(1, pyramidResult);
770
+ if (this.bloomCompositeLutTexture)
771
+ this.bindFullscreenTexture(2, this.bloomCompositeLutTexture);
772
+ this.gl.uniform1i(this.gl.getUniformLocation(pyramidComposite, "u_source"), 0);
773
+ this.gl.uniform1i(this.gl.getUniformLocation(pyramidComposite, "u_blurred"), 1);
774
+ this.gl.uniform1i(this.gl.getUniformLocation(pyramidComposite, "u_compositeLut"), 2);
775
+ this.gl.uniform1i(this.gl.getUniformLocation(pyramidComposite, "u_hdr"), sourceIsHdr ? 1 : 0);
776
+ this.gl.uniform1f(this.gl.getUniformLocation(pyramidComposite, "u_intensity"), bloomOptions.intensity * resolveBloomPyramidResponseGain(plan));
777
+ this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);
778
+ ldrSourceHandle = bloomResources.textures[1];
779
+ // The composited result lives in texture B, so subsequent passes
780
+ // must write texture A (index 0) to avoid sampling a bound target.
781
+ temporarySourceIndex = 0;
782
+ this.recordBloomDiagnostics(bloomOptions, source.width, source.height, sourceIsHdr, plan.mipCount);
783
+ }
707
784
  }
708
785
  if ((depthOfFieldOptions || motionBlurOptions || ssaoOptions || ssrOptions || taaOptions || outlineOptions) && bloomResources) {
709
786
  if (tonePass || colorPass) {
@@ -714,7 +791,7 @@ export class WebGL2Device {
714
791
  }
715
792
  if (depthOfFieldOptions && source.depthTextureHandle) {
716
793
  const depthOfFieldTargetIndex = temporarySourceIndex === 0 ? 1 : 0;
717
- ldrSourceHandle = this.executeNativeDepthOfFieldPass(ldrSourceHandle, source.depthTextureHandle, bloomResources, depthOfFieldOptions, vertexArray, depthOfFieldTargetIndex);
794
+ ldrSourceHandle = this.executeNativeDepthOfFieldPass(ldrSourceHandle, source.depthTextureHandle, bloomResources, depthOfFieldOptions, vertexArray, depthOfFieldTargetIndex, depthRange);
718
795
  temporarySourceIndex = depthOfFieldTargetIndex;
719
796
  }
720
797
  if (motionBlurOptions) {
@@ -729,7 +806,7 @@ export class WebGL2Device {
729
806
  }
730
807
  if (ssrOptions && source.depthTextureHandle) {
731
808
  const ssrTargetIndex = temporarySourceIndex === 0 ? 1 : 0;
732
- ldrSourceHandle = this.executeNativeSsrPass(ldrSourceHandle, source.depthTextureHandle, bloomResources, ssrOptions, vertexArray, ssrTargetIndex);
809
+ ldrSourceHandle = this.executeNativeSsrPass(ldrSourceHandle, source.depthTextureHandle, bloomResources, ssrOptions, vertexArray, ssrTargetIndex, depthRange);
733
810
  temporarySourceIndex = ssrTargetIndex;
734
811
  }
735
812
  if (taaOptions) {
@@ -994,6 +1071,8 @@ export class WebGL2Device {
994
1071
  ["actualViewportHeight", viewport[3] ?? 0],
995
1072
  ["renderTarget", this.activeRenderTarget?.label ?? null],
996
1073
  ["drawCalls", this.drawCalls],
1074
+ ["nativeTemporalPasses", this.nativeTemporalPasses],
1075
+ ["nativeTemporalBindings", this.nativeTemporalBindings],
997
1076
  ["shaderProgramCreates", this.shaderProgramCreateCount],
998
1077
  ["uniformLocationLookups", this.uniformLocationLookupCount],
999
1078
  ["bufferUpdates", this.bufferUpdateCount],
@@ -1023,6 +1102,7 @@ export class WebGL2Device {
1023
1102
  const stateCacheStats = this.stateCache.stats();
1024
1103
  const bufferBytes = [...this.buffers].filter((buffer) => !buffer.disposed).reduce((total, buffer) => total + buffer.byteLength, 0);
1025
1104
  const textureBytes = liveTextureList.reduce((total, texture) => total + texture.byteLength, 0);
1105
+ const gpuTargetEntries = this.describeGpuTargetInventory(liveRenderTargets);
1026
1106
  return {
1027
1107
  drawCalls: this.drawCalls,
1028
1108
  buffers: [...this.buffers].filter((buffer) => !buffer.disposed).length,
@@ -1041,6 +1121,9 @@ export class WebGL2Device {
1041
1121
  nativeShadowMapBindings: this.nativeShadowMapBindings,
1042
1122
  shadowRenderTargetsAllocated: this.shadowRenderTargetsAllocated,
1043
1123
  nativeInstancedSubmissions: this.nativeInstancedSubmissions,
1124
+ bloom: this.lastBloomDiagnostics,
1125
+ nativeTemporalPasses: this.nativeTemporalPasses,
1126
+ nativeTemporalBindings: this.nativeTemporalBindings,
1044
1127
  samplerAnisotropyUploads: this.samplerAnisotropyUploadCount,
1045
1128
  maxTextureAnisotropy: this.maxTextureAnisotropy,
1046
1129
  stateCacheIssued: stateCacheStats.issued,
@@ -1054,6 +1137,7 @@ export class WebGL2Device {
1054
1137
  disposedShaders: [...this.shaders].filter((shader) => shader.disposed).length,
1055
1138
  disposedRenderTargets: [...this.renderTargets].filter((target) => target.disposed).length,
1056
1139
  disposedTextures: [...this.renderTargets].filter((target) => target.colorTexture.disposed).length + this.releasedTextureHandles,
1140
+ ...spreadGpuTargetInventory(gpuTargetEntries),
1057
1141
  lastError: this.lastError,
1058
1142
  contextLost: this.contextLost
1059
1143
  };
@@ -1116,6 +1200,14 @@ export class WebGL2Device {
1116
1200
  this.gl.deleteProgram(this.bloomCompositeProgram);
1117
1201
  this.bloomCompositeProgram = null;
1118
1202
  }
1203
+ if (this.bloomDownsampleProgram) {
1204
+ this.gl.deleteProgram(this.bloomDownsampleProgram);
1205
+ this.bloomDownsampleProgram = null;
1206
+ }
1207
+ if (this.bloomAccumulateProgram) {
1208
+ this.gl.deleteProgram(this.bloomAccumulateProgram);
1209
+ this.bloomAccumulateProgram = null;
1210
+ }
1119
1211
  if (this.outlineProgram) {
1120
1212
  this.gl.deleteProgram(this.outlineProgram);
1121
1213
  this.outlineProgram = null;
@@ -1139,16 +1231,23 @@ export class WebGL2Device {
1139
1231
  if (this.motionBlurVelocityTexture) {
1140
1232
  this.gl.deleteTexture(this.motionBlurVelocityTexture);
1141
1233
  this.motionBlurVelocityTexture = null;
1234
+ this.motionBlurVelocitySize = null;
1142
1235
  }
1143
1236
  if (this.taaProgram) {
1144
1237
  this.gl.deleteProgram(this.taaProgram);
1145
1238
  this.taaProgram = null;
1146
1239
  }
1240
+ if (this.taaPresentationProgram) {
1241
+ this.gl.deleteProgram(this.taaPresentationProgram);
1242
+ this.taaPresentationProgram = null;
1243
+ }
1147
1244
  if (this.taaHistoryTexture) {
1148
1245
  this.gl.deleteTexture(this.taaHistoryTexture);
1149
1246
  this.taaHistoryTexture = null;
1247
+ this.taaHistorySize = null;
1150
1248
  }
1151
1249
  this.disposeBloomPingPongResources();
1250
+ this.disposeBloomPyramidResources();
1152
1251
  if (this.bloomBrightLutTexture) {
1153
1252
  this.gl.deleteTexture(this.bloomBrightLutTexture);
1154
1253
  this.bloomBrightLutTexture = null;
@@ -1158,6 +1257,7 @@ export class WebGL2Device {
1158
1257
  this.gl.deleteTexture(this.bloomCompositeLutTexture);
1159
1258
  this.bloomCompositeLutTexture = null;
1160
1259
  this.bloomCompositeLutIntensity = null;
1260
+ this.bloomCompositeLutShoulder = null;
1161
1261
  }
1162
1262
  if (this.outlineBlendLutTexture) {
1163
1263
  this.gl.deleteTexture(this.outlineBlendLutTexture);
@@ -1283,6 +1383,7 @@ export class WebGL2Device {
1283
1383
  return current;
1284
1384
  }
1285
1385
  this.disposeBloomPingPongResources();
1386
+ this.disposeBloomPyramidResources();
1286
1387
  const textureUnit0 = this.captureTextureUnit0();
1287
1388
  const previousFramebuffer = this.gl.getParameter(this.gl.FRAMEBUFFER_BINDING);
1288
1389
  const textures = [];
@@ -1347,7 +1448,8 @@ export class WebGL2Device {
1347
1448
  }
1348
1449
  ensureBloomLutTextures(options) {
1349
1450
  const updateBright = this.bloomBrightLutThreshold !== options.threshold;
1350
- const updateComposite = this.bloomCompositeLutIntensity !== options.intensity;
1451
+ const updateComposite = this.bloomCompositeLutIntensity !== options.intensity ||
1452
+ this.bloomCompositeLutShoulder !== options.shoulder;
1351
1453
  if (this.bloomBrightLutTexture && this.bloomCompositeLutTexture && !updateBright && !updateComposite) {
1352
1454
  return;
1353
1455
  }
@@ -1366,10 +1468,11 @@ export class WebGL2Device {
1366
1468
  this.bloomCompositeLutTexture = this.createNativeBloomDataTexture();
1367
1469
  }
1368
1470
  if (updateComposite) {
1369
- const pixels = createBloomCompositeLut(options.intensity);
1471
+ const pixels = createBloomCompositeLut(options.intensity, { shoulder: options.shoulder });
1370
1472
  this.gl.bindTexture(this.gl.TEXTURE_2D, this.bloomCompositeLutTexture);
1371
1473
  this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA8, BLOOM_COMPOSITE_LUT_SIZE, BLOOM_COMPOSITE_LUT_SIZE, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, pixels);
1372
1474
  this.bloomCompositeLutIntensity = options.intensity;
1475
+ this.bloomCompositeLutShoulder = options.shoulder;
1373
1476
  }
1374
1477
  }
1375
1478
  finally {
@@ -1414,6 +1517,7 @@ export class WebGL2Device {
1414
1517
  if (!this.motionBlurVelocityTexture) {
1415
1518
  this.motionBlurVelocityTexture = this.createNativeBloomDataTexture();
1416
1519
  }
1520
+ this.motionBlurVelocitySize = { width, height };
1417
1521
  this.gl.bindTexture(this.gl.TEXTURE_2D, this.motionBlurVelocityTexture);
1418
1522
  this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RG32F, width, height, 0, this.gl.RG, this.gl.FLOAT, velocity);
1419
1523
  }
@@ -1428,6 +1532,7 @@ export class WebGL2Device {
1428
1532
  if (!this.taaHistoryTexture) {
1429
1533
  this.taaHistoryTexture = this.createNativeBloomDataTexture();
1430
1534
  }
1535
+ this.taaHistorySize = { width, height };
1431
1536
  this.gl.bindTexture(this.gl.TEXTURE_2D, this.taaHistoryTexture);
1432
1537
  this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA8, width, height, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, history);
1433
1538
  }
@@ -1487,6 +1592,7 @@ export class WebGL2Device {
1487
1592
  this.gl.uniform1i(this.gl.getUniformLocation(brightProgram, "u_brightLut"), 1);
1488
1593
  this.gl.uniform1i(this.gl.getUniformLocation(brightProgram, "u_hdr"), hdr ? 1 : 0);
1489
1594
  this.gl.uniform1f(this.gl.getUniformLocation(brightProgram, "u_threshold"), options.threshold);
1595
+ this.gl.uniform1f(this.gl.getUniformLocation(brightProgram, "u_softKnee"), options.softKnee);
1490
1596
  this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);
1491
1597
  const blurProgram = this.ensureBloomBlurProgram();
1492
1598
  this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, framebufferB);
@@ -1517,6 +1623,158 @@ export class WebGL2Device {
1517
1623
  this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);
1518
1624
  return textureB;
1519
1625
  }
1626
+ /**
1627
+ * Balanced/cinematic bloom: bright-extract once, then a half-resolution mip
1628
+ * chain where every level is downsampled, separably blurred, and accumulated
1629
+ * with its energy-preserving weight. Returns a full-resolution bloom texture
1630
+ * holding the weighted sum, ready for the existing composite program.
1631
+ */
1632
+ executeNativeBloomPyramidPasses(sourceTexture, sourceWidth, sourceHeight, resources, fullResStaging, options, vertexArray, hdr) {
1633
+ const plan = resources.plan;
1634
+ const mipCount = plan.mips.length;
1635
+ const blurRadii = resolveBloomPyramidBlurRadii(plan, options.radius);
1636
+ const downsampleProgram = this.ensureBloomDownsampleProgram();
1637
+ const brightProgram = this.ensureBloomBrightExtractProgram();
1638
+ const blurProgram = this.ensureBloomBlurProgram();
1639
+ const accumulateProgram = this.ensureBloomAccumulateProgram();
1640
+ const brightLut = this.bloomBrightLutTexture;
1641
+ const mipTexture = (level, slot) => resources.textures[level * 2 + slot];
1642
+ const mipFramebuffer = (level, slot) => resources.framebuffers[level * 2 + slot];
1643
+ const mipSize = (level) => plan.mips[level];
1644
+ // Every level settles in slot 0 after the vertical blur.
1645
+ const blurredSlot = (_level) => 0;
1646
+ const drawDownsample = (fromTexture, fromWidth, fromHeight, toFramebuffer, toWidth, toHeight) => {
1647
+ this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, toFramebuffer);
1648
+ this.gl.viewport(0, 0, toWidth, toHeight);
1649
+ this.gl.useProgram(downsampleProgram);
1650
+ this.gl.bindVertexArray(vertexArray);
1651
+ this.bindFullscreenTexture(0, fromTexture);
1652
+ this.gl.uniform1i(this.gl.getUniformLocation(downsampleProgram, "u_source"), 0);
1653
+ this.gl.uniform2f(this.gl.getUniformLocation(downsampleProgram, "u_texelSize"), 1 / fromWidth, 1 / fromHeight);
1654
+ this.gl.uniform2f(this.gl.getUniformLocation(downsampleProgram, "u_targetSize"), toWidth, toHeight);
1655
+ this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);
1656
+ };
1657
+ const drawBrightExtract = (fromTexture, toFramebuffer, size) => {
1658
+ this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, toFramebuffer);
1659
+ this.gl.viewport(0, 0, size.width, size.height);
1660
+ this.gl.useProgram(brightProgram);
1661
+ this.gl.bindVertexArray(vertexArray);
1662
+ this.bindFullscreenTexture(0, fromTexture);
1663
+ if (brightLut)
1664
+ this.bindFullscreenTexture(1, brightLut);
1665
+ this.gl.uniform1i(this.gl.getUniformLocation(brightProgram, "u_source"), 0);
1666
+ this.gl.uniform1i(this.gl.getUniformLocation(brightProgram, "u_brightLut"), 1);
1667
+ this.gl.uniform1i(this.gl.getUniformLocation(brightProgram, "u_hdr"), hdr ? 1 : 0);
1668
+ this.gl.uniform1f(this.gl.getUniformLocation(brightProgram, "u_threshold"), options.threshold);
1669
+ this.gl.uniform1f(this.gl.getUniformLocation(brightProgram, "u_softKnee"), options.softKnee);
1670
+ this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);
1671
+ };
1672
+ const drawSeparableBlur = (readTexture, tmpTexture, tmpFramebuffer, outFramebuffer, size, radius) => {
1673
+ // Horizontal into the temp target, vertical back into the output target.
1674
+ this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, tmpFramebuffer);
1675
+ this.gl.viewport(0, 0, size.width, size.height);
1676
+ this.gl.useProgram(blurProgram);
1677
+ this.gl.bindVertexArray(vertexArray);
1678
+ this.bindFullscreenTexture(0, readTexture);
1679
+ this.gl.uniform1i(this.gl.getUniformLocation(blurProgram, "u_source"), 0);
1680
+ this.gl.uniform2i(this.gl.getUniformLocation(blurProgram, "u_size"), size.width, size.height);
1681
+ this.gl.uniform1i(this.gl.getUniformLocation(blurProgram, "u_radius"), radius);
1682
+ this.gl.uniform1i(this.gl.getUniformLocation(blurProgram, "u_horizontal"), 1);
1683
+ this.gl.uniform1i(this.gl.getUniformLocation(blurProgram, "u_hdr"), hdr ? 1 : 0);
1684
+ this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);
1685
+ this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, outFramebuffer);
1686
+ this.bindFullscreenTexture(0, tmpTexture);
1687
+ this.gl.uniform1i(this.gl.getUniformLocation(blurProgram, "u_horizontal"), 0);
1688
+ this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);
1689
+ };
1690
+ // Extract highlights at the source resolution before the first downsample.
1691
+ // Downsampling the complete scene first averaged narrow emissive sources
1692
+ // below the authored threshold, so balanced/cinematic bloom could execute
1693
+ // all native passes yet remain pixel-identical to disabled output.
1694
+ this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, fullResStaging.framebufferA);
1695
+ this.gl.viewport(0, 0, sourceWidth, sourceHeight);
1696
+ this.gl.useProgram(brightProgram);
1697
+ this.gl.bindVertexArray(vertexArray);
1698
+ this.bindFullscreenTexture(0, sourceTexture);
1699
+ if (brightLut)
1700
+ this.bindFullscreenTexture(1, brightLut);
1701
+ this.gl.uniform1i(this.gl.getUniformLocation(brightProgram, "u_source"), 0);
1702
+ this.gl.uniform1i(this.gl.getUniformLocation(brightProgram, "u_brightLut"), 1);
1703
+ this.gl.uniform1i(this.gl.getUniformLocation(brightProgram, "u_hdr"), hdr ? 1 : 0);
1704
+ this.gl.uniform1f(this.gl.getUniformLocation(brightProgram, "u_threshold"), options.threshold);
1705
+ this.gl.uniform1f(this.gl.getUniformLocation(brightProgram, "u_softKnee"), options.softKnee);
1706
+ this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);
1707
+ // Level 0 downsamples the extracted highlight field, then blurs H into
1708
+ // slot 1 and V back into slot 0. The result therefore settles in slot 0.
1709
+ const level0 = mipSize(0);
1710
+ drawDownsample(fullResStaging.textureA, sourceWidth, sourceHeight, mipFramebuffer(0, 0), level0.width, level0.height);
1711
+ drawSeparableBlur(mipTexture(0, 0), mipTexture(0, 1), mipFramebuffer(0, 1), mipFramebuffer(0, 0), level0, blurRadii[0]);
1712
+ for (let level = 1; level < mipCount; level += 1) {
1713
+ const size = mipSize(level);
1714
+ const previous = mipSize(level - 1);
1715
+ drawDownsample(mipTexture(level - 1, blurredSlot(level - 1)), previous.width, previous.height, mipFramebuffer(level, 0), size.width, size.height);
1716
+ // H into slot 1, V back into slot 0: blurred result settles in slot 0.
1717
+ drawSeparableBlur(mipTexture(level, 0), mipTexture(level, 1), mipFramebuffer(level, 1), mipFramebuffer(level, 0), size, blurRadii[level]);
1718
+ }
1719
+ // Accumulate smallest-to-largest, ping-ponging between the two
1720
+ // accumulators (a pass must never sample its own bound target).
1721
+ const weights = plan.weights;
1722
+ let readAccumulator = { texture: resources.accumulatorTextureA, framebuffer: resources.accumulatorFramebufferA };
1723
+ let writeAccumulator = { texture: resources.accumulatorTextureB, framebuffer: resources.accumulatorFramebufferB };
1724
+ // Clear both accumulators; the seed reads the cleared sibling, never the
1725
+ // bound target (feedback loop).
1726
+ for (const acc of [readAccumulator, writeAccumulator]) {
1727
+ this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, acc.framebuffer);
1728
+ this.gl.viewport(0, 0, level0.width, level0.height);
1729
+ this.gl.clearColor(0, 0, 0, 0);
1730
+ this.gl.clear(this.gl.COLOR_BUFFER_BIT);
1731
+ }
1732
+ // Seed: smallest mip onto the cleared sibling accumulator.
1733
+ this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, writeAccumulator.framebuffer);
1734
+ this.gl.viewport(0, 0, level0.width, level0.height);
1735
+ this.gl.useProgram(accumulateProgram);
1736
+ this.gl.bindVertexArray(vertexArray);
1737
+ this.bindFullscreenTexture(0, readAccumulator.texture);
1738
+ this.bindFullscreenTexture(1, mipTexture(mipCount - 1, blurredSlot(mipCount - 1)));
1739
+ this.gl.uniform1i(this.gl.getUniformLocation(accumulateProgram, "u_base"), 0);
1740
+ this.gl.uniform1i(this.gl.getUniformLocation(accumulateProgram, "u_bloom"), 1);
1741
+ this.gl.uniform1f(this.gl.getUniformLocation(accumulateProgram, "u_weight"), weights[mipCount - 1]);
1742
+ this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);
1743
+ for (let level = mipCount - 2; level >= 0; level -= 1) {
1744
+ const nextRead = writeAccumulator;
1745
+ const nextWrite = readAccumulator;
1746
+ readAccumulator = nextRead;
1747
+ writeAccumulator = nextWrite;
1748
+ this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, writeAccumulator.framebuffer);
1749
+ this.gl.viewport(0, 0, level0.width, level0.height);
1750
+ this.gl.useProgram(accumulateProgram);
1751
+ this.gl.bindVertexArray(vertexArray);
1752
+ this.bindFullscreenTexture(0, readAccumulator.texture);
1753
+ this.bindFullscreenTexture(1, mipTexture(level, blurredSlot(level)));
1754
+ this.gl.uniform1i(this.gl.getUniformLocation(accumulateProgram, "u_base"), 0);
1755
+ this.gl.uniform1i(this.gl.getUniformLocation(accumulateProgram, "u_bloom"), 1);
1756
+ this.gl.uniform1f(this.gl.getUniformLocation(accumulateProgram, "u_weight"), weights[level]);
1757
+ this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);
1758
+ }
1759
+ // Upsample the weighted sum onto full-resolution staging target A, using
1760
+ // the cleared staging target B as the zero base (never sample A while
1761
+ // rendering into it).
1762
+ this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, fullResStaging.framebufferB);
1763
+ this.gl.viewport(0, 0, sourceWidth, sourceHeight);
1764
+ this.gl.clearColor(0, 0, 0, 0);
1765
+ this.gl.clear(this.gl.COLOR_BUFFER_BIT);
1766
+ this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, fullResStaging.framebufferA);
1767
+ this.gl.viewport(0, 0, sourceWidth, sourceHeight);
1768
+ this.gl.useProgram(accumulateProgram);
1769
+ this.gl.bindVertexArray(vertexArray);
1770
+ this.bindFullscreenTexture(0, fullResStaging.textureB);
1771
+ this.bindFullscreenTexture(1, writeAccumulator.texture);
1772
+ this.gl.uniform1i(this.gl.getUniformLocation(accumulateProgram, "u_base"), 0);
1773
+ this.gl.uniform1i(this.gl.getUniformLocation(accumulateProgram, "u_bloom"), 1);
1774
+ this.gl.uniform1f(this.gl.getUniformLocation(accumulateProgram, "u_weight"), 1);
1775
+ this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);
1776
+ return fullResStaging.textureA;
1777
+ }
1520
1778
  executeNativeOutlinePasses(sourceTexture, resources, options, vertexArray, targetIndex) {
1521
1779
  const blendLut = this.outlineBlendLutTexture;
1522
1780
  if (!blendLut) {
@@ -1559,24 +1817,38 @@ export class WebGL2Device {
1559
1817
  this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);
1560
1818
  return targetTexture;
1561
1819
  }
1562
- executeNativeSsrPass(sourceTexture, depthTexture, resources, options, vertexArray, targetIndex) {
1563
- const program = this.ensureSsrProgram();
1820
+ executeNativeSsrPass(sourceTexture, depthTexture, resources, options, vertexArray, targetIndex, depthRange) {
1564
1821
  const targetTexture = resources.textures[targetIndex];
1565
1822
  this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, resources.framebuffers[targetIndex]);
1566
- this.gl.viewport(0, 0, resources.width, resources.height);
1823
+ this.drawSsrKernel(sourceTexture, depthTexture, resources.width, resources.height, options, vertexArray, depthRange);
1824
+ return targetTexture;
1825
+ }
1826
+ drawSsrKernel(sourceTexture, depthTexture, width, height, options, vertexArray, depthRange) {
1827
+ const program = this.ensureSsrProgram();
1828
+ this.gl.viewport(0, 0, width, height);
1567
1829
  this.gl.useProgram(program);
1568
1830
  this.gl.bindVertexArray(vertexArray);
1569
1831
  this.bindFullscreenTexture(0, sourceTexture);
1570
1832
  this.bindFullscreenTexture(1, depthTexture);
1833
+ this.bindFullscreenTexture(2, options.normalMask ?? sourceTexture);
1571
1834
  this.gl.uniform1i(this.gl.getUniformLocation(program, "u_source"), 0);
1572
1835
  this.gl.uniform1i(this.gl.getUniformLocation(program, "u_depth"), 1);
1573
- this.gl.uniform2i(this.gl.getUniformLocation(program, "u_size"), resources.width, resources.height);
1836
+ this.gl.uniform1i(this.gl.getUniformLocation(program, "u_normalMask"), 2);
1837
+ this.gl.uniform1i(this.gl.getUniformLocation(program, "u_hasNormalMask"), options.normalMask ? 1 : 0);
1838
+ this.gl.uniform2i(this.gl.getUniformLocation(program, "u_size"), width, height);
1574
1839
  this.gl.uniform1f(this.gl.getUniformLocation(program, "u_intensity"), options.intensity);
1575
- this.gl.uniform1i(this.gl.getUniformLocation(program, "u_maxDistance"), options.maxDistance);
1840
+ this.gl.uniform1f(this.gl.getUniformLocation(program, "u_maxDistance"), options.maxDistance);
1841
+ this.gl.uniform1i(this.gl.getUniformLocation(program, "u_maxSteps"), options.maxSteps ?? 32);
1842
+ this.gl.uniform1f(this.gl.getUniformLocation(program, "u_thickness"), options.thickness ?? 0.2);
1843
+ if (options.projection && options.inverseProjection) {
1844
+ this.gl.uniformMatrix4fv(this.gl.getUniformLocation(program, "u_projection"), false, options.projection);
1845
+ this.gl.uniformMatrix4fv(this.gl.getUniformLocation(program, "u_inverseProjection"), false, options.inverseProjection);
1846
+ }
1847
+ this.gl.uniform1f(this.gl.getUniformLocation(program, "u_depthNear"), depthRange.near);
1848
+ this.gl.uniform1f(this.gl.getUniformLocation(program, "u_depthFar"), depthRange.far);
1576
1849
  this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);
1577
- return targetTexture;
1578
1850
  }
1579
- executeNativeDepthOfFieldPass(sourceTexture, depthTexture, resources, options, vertexArray, targetIndex) {
1851
+ executeNativeDepthOfFieldPass(sourceTexture, depthTexture, resources, options, vertexArray, targetIndex, depthRange) {
1580
1852
  const program = this.ensureDepthOfFieldProgram();
1581
1853
  const targetTexture = resources.textures[targetIndex];
1582
1854
  this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, resources.framebuffers[targetIndex]);
@@ -1591,11 +1863,13 @@ export class WebGL2Device {
1591
1863
  this.gl.uniform1f(this.gl.getUniformLocation(program, "u_focusDepth"), options.focusDepth);
1592
1864
  this.gl.uniform1f(this.gl.getUniformLocation(program, "u_focusRange"), options.focusRange);
1593
1865
  this.gl.uniform1i(this.gl.getUniformLocation(program, "u_maxRadius"), options.maxRadius);
1866
+ this.gl.uniform1f(this.gl.getUniformLocation(program, "u_depthNear"), depthRange.near);
1867
+ this.gl.uniform1f(this.gl.getUniformLocation(program, "u_depthFar"), depthRange.far);
1594
1868
  this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);
1595
1869
  return targetTexture;
1596
1870
  }
1597
1871
  executeNativeMotionBlurPass(sourceTexture, resources, options, vertexArray, targetIndex) {
1598
- const velocityTexture = this.motionBlurVelocityTexture;
1872
+ const velocityTexture = options.temporal ? this.requireTemporalTarget(options.temporal.velocity, resources.width, resources.height).colorHandle : this.motionBlurVelocityTexture;
1599
1873
  if (!velocityTexture) {
1600
1874
  throw new RenderDeviceError("WebGL2 motion-blur velocity texture was not initialized", "WEBGL_ALLOCATION_FAILED");
1601
1875
  }
@@ -1612,28 +1886,66 @@ export class WebGL2Device {
1612
1886
  this.gl.uniform2i(this.gl.getUniformLocation(program, "u_size"), resources.width, resources.height);
1613
1887
  this.gl.uniform1i(this.gl.getUniformLocation(program, "u_samples"), options.samples);
1614
1888
  this.gl.uniform1f(this.gl.getUniformLocation(program, "u_scale"), options.scale);
1889
+ this.gl.uniform1i(this.gl.getUniformLocation(program, "u_velocityUv"), options.temporal ? 1 : 0);
1890
+ if (options.temporal) {
1891
+ this.nativeTemporalPasses++;
1892
+ this.nativeTemporalBindings++;
1893
+ }
1615
1894
  this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);
1616
1895
  return targetTexture;
1617
1896
  }
1618
1897
  executeNativeTaaPass(sourceTexture, resources, options, vertexArray, targetIndex) {
1619
- const historyTexture = this.taaHistoryTexture;
1898
+ const temporal = options.temporal;
1899
+ const historyTarget = temporal ? this.requireTemporalTarget(temporal.history, resources.width, resources.height) : undefined;
1900
+ const historyTexture = historyTarget?.colorHandle ?? this.taaHistoryTexture;
1620
1901
  if (!historyTexture) {
1621
1902
  throw new RenderDeviceError("WebGL2 TAA history texture was not initialized", "WEBGL_ALLOCATION_FAILED");
1622
1903
  }
1623
- const program = this.ensureTaaProgram();
1904
+ const accumulationProgram = this.ensureTaaProgram();
1624
1905
  const targetTexture = resources.textures[targetIndex];
1625
- this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, resources.framebuffers[targetIndex]);
1626
1906
  this.gl.viewport(0, 0, resources.width, resources.height);
1627
- this.gl.useProgram(program);
1907
+ this.gl.useProgram(accumulationProgram);
1628
1908
  this.gl.bindVertexArray(vertexArray);
1629
1909
  this.bindFullscreenTexture(0, sourceTexture);
1630
1910
  this.bindFullscreenTexture(1, historyTexture);
1631
- this.gl.uniform1i(this.gl.getUniformLocation(program, "u_source"), 0);
1632
- this.gl.uniform1i(this.gl.getUniformLocation(program, "u_history"), 1);
1633
- this.gl.uniform1f(this.gl.getUniformLocation(program, "u_blend"), options.blend);
1634
- this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);
1911
+ this.gl.uniform1i(this.gl.getUniformLocation(accumulationProgram, "u_source"), 0);
1912
+ this.gl.uniform1i(this.gl.getUniformLocation(accumulationProgram, "u_history"), 1);
1913
+ this.gl.uniform1f(this.gl.getUniformLocation(accumulationProgram, "u_blend"), temporal && !temporal.historyValid ? 0 : temporalAccumulationWeight(options.blend, temporal?.historyFrames));
1914
+ this.gl.uniform1i(this.gl.getUniformLocation(accumulationProgram, "u_temporal"), temporal ? 1 : 0);
1915
+ if (temporal) {
1916
+ const velocity = this.requireTemporalTarget(temporal.velocity, resources.width, resources.height);
1917
+ const destination = this.requireTemporalTarget(temporal.historyOutput, resources.width, resources.height);
1918
+ if (destination === historyTarget || destination === velocity || historyTarget === velocity)
1919
+ throw new RenderDeviceError("Temporal targets must not alias", "TEMPORAL_TARGET_ALIAS");
1920
+ this.bindFullscreenTexture(2, velocity.colorHandle);
1921
+ this.gl.uniform1i(this.gl.getUniformLocation(accumulationProgram, "u_velocity"), 2);
1922
+ // Resolve exactly once into the next history target. Presentation reads
1923
+ // this immutable result through a separate program, so display tuning
1924
+ // cannot alter the accumulated history shader or its stored bytes.
1925
+ this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, destination.framebuffer);
1926
+ this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);
1927
+ const presentationProgram = this.ensureTaaPresentationProgram();
1928
+ this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, resources.framebuffers[targetIndex]);
1929
+ this.gl.useProgram(presentationProgram);
1930
+ this.bindFullscreenTexture(0, sourceTexture);
1931
+ this.bindFullscreenTexture(1, destination.colorHandle);
1932
+ this.gl.uniform1i(this.gl.getUniformLocation(presentationProgram, "u_source"), 0);
1933
+ this.gl.uniform1i(this.gl.getUniformLocation(presentationProgram, "u_accumulated"), 1);
1934
+ this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);
1935
+ this.nativeTemporalPasses++;
1936
+ this.nativeTemporalBindings += 3;
1937
+ }
1938
+ else {
1939
+ this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, resources.framebuffers[targetIndex]);
1940
+ this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);
1941
+ }
1635
1942
  return targetTexture;
1636
1943
  }
1944
+ requireTemporalTarget(target, width, height) {
1945
+ if (!(target instanceof WebGL2RenderTarget) || !this.renderTargets.has(target) || target.disposed || target.width !== width || target.height !== height || target.colorTexture.format !== "rgba16f")
1946
+ throw new RenderDeviceError("Temporal input must be a live same-device same-size RGBA16F target", "TEMPORAL_TARGET_INVALID");
1947
+ return target;
1948
+ }
1637
1949
  bindFullscreenTexture(unit, texture) {
1638
1950
  this.gl.activeTexture(this.gl.TEXTURE0 + unit);
1639
1951
  this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
@@ -1648,6 +1960,223 @@ export class WebGL2Device {
1648
1960
  this.gl.deleteFramebuffer(framebuffer);
1649
1961
  this.bloomPingPongResources = null;
1650
1962
  }
1963
+ /**
1964
+ * Diagnostics for the most recent native bloom execution (muse3jsparity-PRD
1965
+ * A1). Null when no native bloom pass has run yet on this device.
1966
+ */
1967
+ getBloomDiagnostics() {
1968
+ return this.lastBloomDiagnostics;
1969
+ }
1970
+ recordBloomDiagnostics(options, sourceWidth, sourceHeight, hdr, mipCount) {
1971
+ const plan = resolveBloomPyramidPlan(sourceWidth, sourceHeight, options.quality, hdr);
1972
+ const pingPongBytes = sourceWidth * sourceHeight * 4 * (hdr ? 2 : 1) * 2;
1973
+ const bytesPerChannel = plan.halfFloat ? 2 : 1;
1974
+ const level0 = plan.mips[0];
1975
+ const accumulatorBytes = level0.width * level0.height * 4 * bytesPerChannel;
1976
+ this.lastBloomDiagnostics = {
1977
+ quality: options.quality,
1978
+ mipCount,
1979
+ targetCount: options.quality === "performance" ? 2 : plan.mips.length * 2 + 1,
1980
+ targetBytes: (options.quality === "performance" ? pingPongBytes : plan.targetBytes + accumulatorBytes + pingPongBytes),
1981
+ compositeGain: options.intensity * resolveBloomPyramidResponseGain(plan),
1982
+ threshold: options.threshold,
1983
+ intensity: options.intensity,
1984
+ softKnee: options.softKnee,
1985
+ shoulder: options.shoulder,
1986
+ halfFloat: hdr || plan.halfFloat,
1987
+ };
1988
+ }
1989
+ /**
1990
+ * PART U: every live GPU allocation with bytes + lane owner — registry
1991
+ * render targets (B1 shadow maps arrive labeled, B4 captures arrive as
1992
+ * `-mirror-target`/`-scene-color-target`) plus the raw-handle residents the
1993
+ * registry set cannot see: the A1 bloom pyramid (two targets per mip plus
1994
+ * two level-0 accumulators), the legacy bloom ping-pong pair, the A1/A3
1995
+ * lookup textures, and the motion-blur/taa history textures. The
1996
+ * depth-readback scratch pair is intentionally absent: it is allocated and
1997
+ * deleted inside one synchronous call, so it is never live at a diagnostics
1998
+ * boundary. A5 volumetric light, C4 decals, and the G1 SDF atlas own no GPU
1999
+ * target (forward uniforms + CPU kernel, CPU geometry, CPU atlas data), so
2000
+ * no entry is emitted for them; a target appearing under their owner would
2001
+ * be the signal that the lane grew one.
2002
+ */
2003
+ describeGpuTargetInventory(liveRenderTargets) {
2004
+ const entries = liveRenderTargets.map((target) => ({
2005
+ label: target.label,
2006
+ kind: "render-target",
2007
+ bytes: target.colorTexture.byteLength + (target.depthTexture?.byteLength ?? 0),
2008
+ owner: resolveGpuTargetOwner(target.label)
2009
+ }));
2010
+ const pyramid = this.bloomPyramidResources;
2011
+ if (pyramid) {
2012
+ const bytesPerPixel = pyramid.hdr ? 8 : 4;
2013
+ pyramid.plan.mips.forEach((mip, index) => {
2014
+ for (const side of ["a", "b"]) {
2015
+ entries.push({
2016
+ label: `bloom-pyramid-mip${index}${side}`,
2017
+ kind: "pyramid-mip",
2018
+ bytes: mip.width * mip.height * bytesPerPixel,
2019
+ owner: "a1-bloom"
2020
+ });
2021
+ }
2022
+ });
2023
+ const level0 = pyramid.plan.mips[0];
2024
+ if (level0) {
2025
+ for (const side of ["a", "b"]) {
2026
+ entries.push({
2027
+ label: `bloom-pyramid-accumulator-${side}`,
2028
+ kind: "pyramid-accumulator",
2029
+ bytes: level0.width * level0.height * bytesPerPixel,
2030
+ owner: "a1-bloom"
2031
+ });
2032
+ }
2033
+ }
2034
+ }
2035
+ const pingPong = this.bloomPingPongResources;
2036
+ if (pingPong) {
2037
+ const bytesPerPixel = pingPong.hdr ? 8 : 4;
2038
+ for (const side of ["a", "b"]) {
2039
+ entries.push({
2040
+ label: `bloom-ping-pong-${side}`,
2041
+ kind: "ping-pong",
2042
+ bytes: pingPong.width * pingPong.height * bytesPerPixel,
2043
+ owner: "a1-bloom"
2044
+ });
2045
+ }
2046
+ }
2047
+ if (this.bloomBrightLutTexture) {
2048
+ entries.push({
2049
+ label: "bloom-bright-lut",
2050
+ kind: "lut",
2051
+ bytes: BLOOM_BRIGHT_LUT_WIDTH * BLOOM_BRIGHT_LUT_HEIGHT * 4,
2052
+ owner: "a1-bloom"
2053
+ });
2054
+ }
2055
+ if (this.bloomCompositeLutTexture) {
2056
+ entries.push({
2057
+ label: "bloom-composite-lut",
2058
+ kind: "lut",
2059
+ bytes: BLOOM_COMPOSITE_LUT_SIZE * BLOOM_COMPOSITE_LUT_SIZE * 4,
2060
+ owner: "a1-bloom"
2061
+ });
2062
+ }
2063
+ if (this.outlineBlendLutTexture) {
2064
+ entries.push({ label: "outline-blend-lut", kind: "lut", bytes: OUTLINE_BLEND_LUT_WIDTH * 4, owner: "post" });
2065
+ }
2066
+ if (this.motionBlurVelocityTexture && this.motionBlurVelocitySize) {
2067
+ entries.push({
2068
+ label: "motion-blur-velocity",
2069
+ kind: "history",
2070
+ bytes: this.motionBlurVelocitySize.width * this.motionBlurVelocitySize.height * 8,
2071
+ owner: "post"
2072
+ });
2073
+ }
2074
+ if (this.taaHistoryTexture && this.taaHistorySize) {
2075
+ entries.push({
2076
+ label: "taa-history",
2077
+ kind: "history",
2078
+ bytes: this.taaHistorySize.width * this.taaHistorySize.height * 4,
2079
+ owner: "post"
2080
+ });
2081
+ }
2082
+ return entries;
2083
+ }
2084
+ disposeBloomPyramidResources() {
2085
+ if (!this.bloomPyramidResources)
2086
+ return;
2087
+ for (const texture of this.bloomPyramidResources.textures)
2088
+ this.gl.deleteTexture(texture);
2089
+ for (const framebuffer of this.bloomPyramidResources.framebuffers)
2090
+ this.gl.deleteFramebuffer(framebuffer);
2091
+ this.gl.deleteTexture(this.bloomPyramidResources.accumulatorTextureA);
2092
+ this.gl.deleteFramebuffer(this.bloomPyramidResources.accumulatorFramebufferA);
2093
+ this.gl.deleteTexture(this.bloomPyramidResources.accumulatorTextureB);
2094
+ this.gl.deleteFramebuffer(this.bloomPyramidResources.accumulatorFramebufferB);
2095
+ this.bloomPyramidResources = null;
2096
+ }
2097
+ /**
2098
+ * Mip-chain targets for the balanced/cinematic bloom pyramid. Each level is a
2099
+ * single texture+framebuffer at the planned size with linear filtering so the
2100
+ * downsample and accumulate passes can sample across levels. Half-float backs
2101
+ * the chain when the source is HDR or the cinematic preset requests it.
2102
+ */
2103
+ ensureBloomPyramidResources(plan, hdr) {
2104
+ const current = this.bloomPyramidResources;
2105
+ if (current && current.plan.quality === plan.quality && current.hdr === (hdr || plan.halfFloat) &&
2106
+ current.plan.mips.length === plan.mips.length &&
2107
+ current.plan.mips.every((mip, index) => mip.width === plan.mips[index].width && mip.height === plan.mips[index].height)) {
2108
+ return current;
2109
+ }
2110
+ this.disposeBloomPyramidResources();
2111
+ const textureUnit0 = this.captureTextureUnit0();
2112
+ const previousFramebuffer = this.gl.getParameter(this.gl.FRAMEBUFFER_BINDING);
2113
+ const textures = [];
2114
+ const framebuffers = [];
2115
+ const useHalfFloat = hdr || plan.halfFloat;
2116
+ const allocateTarget = (width, height, label) => {
2117
+ const texture = this.gl.createTexture();
2118
+ const framebuffer = this.gl.createFramebuffer();
2119
+ if (!texture || !framebuffer) {
2120
+ if (texture)
2121
+ this.gl.deleteTexture(texture);
2122
+ if (framebuffer)
2123
+ this.gl.deleteFramebuffer(framebuffer);
2124
+ throw new RenderDeviceError(`Failed to allocate WebGL2 bloom pyramid ${label}`, "WEBGL_ALLOCATION_FAILED", { width, height });
2125
+ }
2126
+ textures.push(texture);
2127
+ framebuffers.push(framebuffer);
2128
+ this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
2129
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
2130
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);
2131
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
2132
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
2133
+ this.gl.texImage2D(this.gl.TEXTURE_2D, 0, useHalfFloat ? this.gl.RGBA16F : this.gl.RGBA8, width, height, 0, this.gl.RGBA, useHalfFloat ? this.gl.HALF_FLOAT : this.gl.UNSIGNED_BYTE, null);
2134
+ this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, framebuffer);
2135
+ this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, texture, 0);
2136
+ const status = this.gl.checkFramebufferStatus(this.gl.FRAMEBUFFER);
2137
+ if (status !== this.gl.FRAMEBUFFER_COMPLETE) {
2138
+ throw new RenderDeviceError("WebGL2 bloom pyramid framebuffer status is invalid", "FRAMEBUFFER_INVALID", { width, height, status });
2139
+ }
2140
+ return { texture, framebuffer };
2141
+ };
2142
+ try {
2143
+ // Two ping-pong targets per mip (separable blur needs distinct read/write
2144
+ // targets at the same size), plus two level-0-sized accumulators: the
2145
+ // weighted sum ping-pongs between them so no pass ever samples the
2146
+ // texture of its own bound framebuffer (feedback loop).
2147
+ for (const mip of plan.mips) {
2148
+ allocateTarget(mip.width, mip.height, "mip");
2149
+ allocateTarget(mip.width, mip.height, "mip");
2150
+ }
2151
+ const level0 = plan.mips[0];
2152
+ const accumulatorA = allocateTarget(level0.width, level0.height, "accumulator-a");
2153
+ const accumulatorB = allocateTarget(level0.width, level0.height, "accumulator-b");
2154
+ const finalResources = {
2155
+ plan,
2156
+ hdr: useHalfFloat,
2157
+ textures,
2158
+ framebuffers,
2159
+ accumulatorTextureA: accumulatorA.texture,
2160
+ accumulatorFramebufferA: accumulatorA.framebuffer,
2161
+ accumulatorTextureB: accumulatorB.texture,
2162
+ accumulatorFramebufferB: accumulatorB.framebuffer
2163
+ };
2164
+ this.bloomPyramidResources = finalResources;
2165
+ return finalResources;
2166
+ }
2167
+ catch (error) {
2168
+ for (const texture of textures)
2169
+ this.gl.deleteTexture(texture);
2170
+ for (const framebuffer of framebuffers)
2171
+ this.gl.deleteFramebuffer(framebuffer);
2172
+ throw error;
2173
+ }
2174
+ finally {
2175
+ this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, previousFramebuffer);
2176
+ this.restoreTextureUnit0(textureUnit0);
2177
+ this.stateCache.invalidate();
2178
+ }
2179
+ }
1651
2180
  captureTextureUnit0() {
1652
2181
  const activeTexture = this.gl.getParameter(this.gl.ACTIVE_TEXTURE);
1653
2182
  this.gl.activeTexture(this.gl.TEXTURE0);
@@ -1856,6 +2385,7 @@ uniform sampler2D u_source;
1856
2385
  uniform sampler2D u_brightLut;
1857
2386
  uniform int u_hdr;
1858
2387
  uniform float u_threshold;
2388
+ uniform float u_softKnee;
1859
2389
  out vec4 outColor;
1860
2390
 
1861
2391
  uvec4 byteTexel(sampler2D source, ivec2 coordinate) {
@@ -1867,7 +2397,12 @@ void main() {
1867
2397
  if (u_hdr == 1) {
1868
2398
  vec4 source = texelFetch(u_source, pixel, 0);
1869
2399
  float luma = dot(source.rgb, vec3(0.2126, 0.7152, 0.0722));
1870
- outColor = luma >= u_threshold ? source : vec4(0.0);
2400
+ float kneeLow = max(u_threshold - u_softKnee, 0.0);
2401
+ float kneeHigh = min(u_threshold + u_softKnee, 1.0);
2402
+ float weight = u_softKnee <= 0.0
2403
+ ? (luma >= u_threshold ? 1.0 : 0.0)
2404
+ : smoothstep(kneeLow, kneeHigh, luma);
2405
+ outColor = source * weight;
1871
2406
  return;
1872
2407
  }
1873
2408
  uvec4 source = byteTexel(u_source, pixel);
@@ -1907,38 +2442,25 @@ uniform int u_horizontal;
1907
2442
  uniform int u_hdr;
1908
2443
  out vec4 outColor;
1909
2444
 
1910
- uvec4 sourceByte(ivec2 coordinate) {
1911
- ivec2 bounded = clamp(coordinate, ivec2(0), u_size - ivec2(1));
1912
- return uvec4(texelFetch(u_source, bounded, 0) * 255.0 + 0.5);
2445
+ vec4 sourcePixel(ivec2 coordinate) {
2446
+ return texelFetch(u_source, clamp(coordinate, ivec2(0), u_size - ivec2(1)), 0);
1913
2447
  }
1914
2448
 
1915
2449
  void main() {
1916
2450
  ivec2 pixel = ivec2(gl_FragCoord.xy);
1917
- if (u_hdr == 1) {
1918
- vec4 sum = vec4(0.0);
1919
- for (int offset = -16; offset <= 16; offset += 1) {
1920
- if (abs(offset) > u_radius) continue;
1921
- ivec2 sampleCoordinate = pixel + (u_horizontal == 1 ? ivec2(offset, 0) : ivec2(0, offset));
1922
- sum += texelFetch(u_source, clamp(sampleCoordinate, ivec2(0), u_size - ivec2(1)), 0);
1923
- }
1924
- outColor = sum / float(u_radius * 2 + 1);
1925
- return;
1926
- }
1927
- uvec4 sum = uvec4(0u);
1928
- for (int offset = -16; offset <= 16; offset += 1) {
1929
- if (abs(offset) > u_radius) {
1930
- continue;
1931
- }
1932
- ivec2 sampleCoordinate = pixel + (
1933
- u_horizontal == 1
1934
- ? ivec2(offset, 0)
1935
- : ivec2(0, offset)
1936
- );
1937
- sum += sourceByte(sampleCoordinate);
2451
+ float sigma = max(float(u_radius) / 3.0, 0.5);
2452
+ float coefficient = 0.39894 / sigma;
2453
+ vec4 sum = sourcePixel(pixel) * coefficient;
2454
+ float weightSum = coefficient;
2455
+ for (int offset = 1; offset <= 16; offset += 1) {
2456
+ if (offset > u_radius) continue;
2457
+ float distance = float(offset);
2458
+ float weight = coefficient * exp(-0.5 * distance * distance / (sigma * sigma));
2459
+ ivec2 delta = u_horizontal == 1 ? ivec2(offset, 0) : ivec2(0, offset);
2460
+ sum += (sourcePixel(pixel + delta) + sourcePixel(pixel - delta)) * weight;
2461
+ weightSum += 2.0 * weight;
1938
2462
  }
1939
- uint kernelSize = uint(u_radius * 2 + 1);
1940
- uvec4 rounded = (sum * 2u + uvec4(kernelSize)) / uvec4(kernelSize * 2u);
1941
- outColor = vec4(rounded) / 255.0;
2463
+ outColor = sum / max(weightSum, 0.000001);
1942
2464
  }
1943
2465
  `, "webgl2-bloom-blur");
1944
2466
  return this.bloomBlurProgram;
@@ -1985,6 +2507,48 @@ void main() {
1985
2507
  `, "webgl2-bloom-composite");
1986
2508
  return this.bloomCompositeProgram;
1987
2509
  }
2510
+ ensureBloomDownsampleProgram() {
2511
+ if (this.bloomDownsampleProgram)
2512
+ return this.bloomDownsampleProgram;
2513
+ this.bloomDownsampleProgram = this.createFullscreenProgram(`#version 300 es
2514
+ precision highp float;
2515
+ uniform sampler2D u_source;
2516
+ uniform vec2 u_texelSize;
2517
+ uniform vec2 u_targetSize;
2518
+ out vec4 outColor;
2519
+ void main() {
2520
+ // gl_FragCoord is expressed in destination pixels. Normalize by the
2521
+ // destination size so every downsample covers the complete source image;
2522
+ // multiplying by source texel size cropped a half-size target to the
2523
+ // lower-left quarter of the preceding level.
2524
+ vec2 uv = gl_FragCoord.xy / u_targetSize;
2525
+ vec2 e = u_texelSize * 0.5;
2526
+ vec4 sum = texture(u_source, uv - e) + texture(u_source, uv + vec2(e.x, -e.y))
2527
+ + texture(u_source, uv + vec2(-e.x, e.y)) + texture(u_source, uv + e);
2528
+ outColor = sum * 0.25;
2529
+ }
2530
+ `, "webgl2-bloom-downsample");
2531
+ return this.bloomDownsampleProgram;
2532
+ }
2533
+ ensureBloomAccumulateProgram() {
2534
+ if (this.bloomAccumulateProgram)
2535
+ return this.bloomAccumulateProgram;
2536
+ this.bloomAccumulateProgram = this.createFullscreenProgram(`#version 300 es
2537
+ precision highp float;
2538
+ uniform sampler2D u_base;
2539
+ uniform sampler2D u_bloom;
2540
+ uniform float u_weight;
2541
+ out vec4 outColor;
2542
+ void main() {
2543
+ ivec2 pixel = ivec2(gl_FragCoord.xy);
2544
+ vec4 base = texelFetch(u_base, pixel, 0);
2545
+ vec2 uv = (vec2(pixel) + 0.5) / vec2(textureSize(u_base, 0));
2546
+ vec3 bloom = texture(u_bloom, uv).rgb;
2547
+ outColor = vec4(base.rgb + bloom * u_weight, base.a);
2548
+ }
2549
+ `, "webgl2-bloom-accumulate");
2550
+ return this.bloomAccumulateProgram;
2551
+ }
1988
2552
  ensureOutlineProgram() {
1989
2553
  if (this.outlineProgram)
1990
2554
  return this.outlineProgram;
@@ -2124,29 +2688,73 @@ precision highp float;
2124
2688
  precision highp int;
2125
2689
  uniform sampler2D u_source;
2126
2690
  uniform sampler2D u_depth;
2691
+ uniform sampler2D u_normalMask;
2127
2692
  uniform ivec2 u_size;
2128
2693
  uniform float u_intensity;
2129
- uniform int u_maxDistance;
2694
+ uniform float u_maxDistance;
2695
+ uniform int u_maxSteps;
2696
+ uniform float u_thickness;
2697
+ uniform int u_hasNormalMask;
2698
+ uniform mat4 u_projection;
2699
+ uniform mat4 u_inverseProjection;
2130
2700
  out vec4 outColor;
2131
-
2701
+ vec3 positionAtDepth(vec2 uv, float depth) {
2702
+ vec4 p = u_inverseProjection * vec4(uv * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);
2703
+ return p.xyz / p.w;
2704
+ }
2705
+ vec3 positionAt(vec2 uv) {
2706
+ return positionAtDepth(uv, texture(u_depth, uv).r);
2707
+ }
2708
+ vec3 viewDirection(vec2 uv) {
2709
+ return normalize(positionAtDepth(uv, 0.999) - positionAtDepth(uv, 0.0));
2710
+ }
2711
+ vec2 projectPosition(vec3 p) {
2712
+ vec4 clip = u_projection * vec4(p, 1.0);
2713
+ return clip.xy / clip.w * 0.5 + 0.5;
2714
+ }
2715
+ float depthDifference(vec3 p, vec2 uv) {
2716
+ return dot(p - positionAt(uv), viewDirection(uv));
2717
+ }
2132
2718
  void main() {
2133
- ivec2 pixel = ivec2(gl_FragCoord.xy);
2134
- vec4 target = texelFetch(u_source, pixel, 0);
2135
- float depth = texelFetch(u_depth, pixel, 0).r;
2136
- int rayDistance = max(1, int(round((1.0 - depth) * float(u_maxDistance))));
2137
- int direction = pixel.x < u_size.x / 2 ? 1 : -1;
2138
- ivec2 samplePixel = ivec2(
2139
- clamp(pixel.x + direction * rayDistance, 0, u_size.x - 1),
2140
- clamp(u_size.y - 1 - pixel.y, 0, u_size.y - 1)
2141
- );
2142
- vec3 reflected = texelFetch(u_source, samplePixel, 0).rgb;
2143
- float sourceLuma = (reflected.r + reflected.g + reflected.b) / 3.0;
2144
- if (sourceLuma < 0.18 || depth > 0.92) {
2145
- outColor = target;
2146
- return;
2719
+ vec2 uv = gl_FragCoord.xy / vec2(u_size);
2720
+ vec4 base = texture(u_source, uv);
2721
+ outColor = base;
2722
+ vec3 origin = positionAt(uv);
2723
+ vec4 normalMask = texture(u_normalMask, uv);
2724
+ vec3 normal = u_hasNormalMask == 1 ? normalize(normalMask.xyz * 2.0 - 1.0) : normalize(cross(dFdx(origin), dFdy(origin)));
2725
+ float roughness = u_hasNormalMask == 1 ? normalMask.a : 0.25;
2726
+ if (texture(u_depth, uv).r >= 0.999999 || (u_hasNormalMask == 1 && normalMask.a == 0.0) || roughness >= 1.0) return;
2727
+ vec3 ray = normalize(reflect(viewDirection(uv), normal));
2728
+ float stepSize = u_maxDistance / float(u_maxSteps);
2729
+ vec3 start = origin + normal * u_thickness;
2730
+ float previous = 0.0;
2731
+ for (int i = 1; i <= 64; ++i) {
2732
+ if (i > u_maxSteps) break;
2733
+ float distanceAlong = float(i) * stepSize;
2734
+ vec3 candidate = start + ray * distanceAlong;
2735
+ vec4 candidateClip = u_projection * vec4(candidate, 1.0);
2736
+ if (candidateClip.w <= 0.0 || abs(candidateClip.z) >= candidateClip.w) break;
2737
+ vec2 hitUV = projectPosition(candidate);
2738
+ if (any(lessThanEqual(hitUV, vec2(0.0))) || any(greaterThanEqual(hitUV, vec2(1.0)))) break;
2739
+ float difference = depthDifference(candidate, hitUV);
2740
+ if (difference >= 0.0 && previous < 0.0) {
2741
+ float lo = distanceAlong - stepSize, hi = distanceAlong;
2742
+ for (int j = 0; j < 5; ++j) {
2743
+ float mid = (lo + hi) * 0.5;
2744
+ vec3 point = start + ray * mid;
2745
+ if (depthDifference(point, projectPosition(point)) >= 0.0) hi = mid; else lo = mid;
2746
+ }
2747
+ candidate = start + ray * hi;
2748
+ hitUV = projectPosition(candidate);
2749
+ difference = depthDifference(candidate, hitUV);
2750
+ if (difference > u_thickness || texture(u_depth, hitUV).r >= 0.999999) break;
2751
+ float edge = smoothstep(0.0, 0.08, min(min(hitUV.x, hitUV.y), min(1.0 - hitUV.x, 1.0 - hitUV.y)));
2752
+ float response = clamp(u_intensity * edge * (1.0 - roughness) * (1.0 - hi / u_maxDistance), 0.0, 1.0);
2753
+ outColor = vec4(mix(base.rgb, texture(u_source, hitUV).rgb, response), base.a);
2754
+ break;
2755
+ }
2756
+ previous = difference;
2147
2757
  }
2148
- float boost = sourceLuma * u_intensity * (1.0 - depth);
2149
- outColor = vec4(clamp(target.rgb + reflected * boost, 0.0, 1.0), target.a);
2150
2758
  }
2151
2759
  `, "webgl2-ssr");
2152
2760
  return this.ssrProgram;
@@ -2163,13 +2771,26 @@ uniform ivec2 u_size;
2163
2771
  uniform float u_focusDepth;
2164
2772
  uniform float u_focusRange;
2165
2773
  uniform int u_maxRadius;
2774
+ uniform float u_depthNear;
2775
+ uniform float u_depthFar;
2166
2776
  out vec4 outColor;
2167
2777
 
2778
+ // Same linearization contract as the SSR program, except focus is authored
2779
+ // directly as a linear-distance fraction (0 = near plane, 1 = far plane):
2780
+ // buffer-unit focus values are unusable once depth is linearized, because the
2781
+ // projection curve parks the whole play area inside a sliver of buffer units.
2782
+ // The CPU byte kernel keeps buffer-unit semantics; the divergence is
2783
+ // documented at normalizeLdrDepthRange.
2784
+ float a3dDofLinearDepth(float depth) {
2785
+ float viewZ = u_depthNear * u_depthFar / max(u_depthFar - depth * (u_depthFar - u_depthNear), 0.0001);
2786
+ return clamp((viewZ - u_depthNear) / max(u_depthFar - u_depthNear, 0.0001), 0.0, 1.0);
2787
+ }
2788
+
2168
2789
  void main() {
2169
2790
  ivec2 pixel = ivec2(gl_FragCoord.xy);
2170
2791
  vec4 source = texelFetch(u_source, pixel, 0);
2171
- float depth = texelFetch(u_depth, pixel, 0).r;
2172
- float normalizedBlur = max(0.0, abs(depth - u_focusDepth) - u_focusRange)
2792
+ float linearDepth = a3dDofLinearDepth(texelFetch(u_depth, pixel, 0).r);
2793
+ float normalizedBlur = max(0.0, abs(linearDepth - u_focusDepth) - u_focusRange)
2173
2794
  / max(u_focusRange, 0.001) * float(u_maxRadius);
2174
2795
  int radius = min(u_maxRadius, int(round(normalizedBlur)));
2175
2796
  if (radius == 0) {
@@ -2200,6 +2821,7 @@ precision highp float;
2200
2821
  precision highp int;
2201
2822
  uniform sampler2D u_source;
2202
2823
  uniform sampler2D u_velocity;
2824
+ uniform bool u_velocityUv;
2203
2825
  uniform ivec2 u_size;
2204
2826
  uniform int u_samples;
2205
2827
  uniform float u_scale;
@@ -2209,6 +2831,8 @@ void main() {
2209
2831
  ivec2 pixel = ivec2(gl_FragCoord.xy);
2210
2832
  vec4 source = texelFetch(u_source, pixel, 0);
2211
2833
  vec2 velocity = texelFetch(u_velocity, pixel, 0).rg * u_scale;
2834
+ if (u_velocityUv) velocity *= vec2(u_size);
2835
+ velocity = clamp(velocity, vec2(-64.0), vec2(64.0));
2212
2836
  if (length(velocity) < 0.01) {
2213
2837
  outColor = source;
2214
2838
  return;
@@ -2233,18 +2857,108 @@ void main() {
2233
2857
  precision highp float;
2234
2858
  uniform sampler2D u_source;
2235
2859
  uniform sampler2D u_history;
2860
+ uniform sampler2D u_velocity;
2236
2861
  uniform float u_blend;
2862
+ uniform bool u_temporal;
2237
2863
  out vec4 outColor;
2864
+ vec4 cubicWeights(float f) {
2865
+ float f2=f*f, f3=f2*f;
2866
+ return vec4(-0.5*f+f2-0.5*f3,1.0-2.5*f2+1.5*f3,0.5*f+2.0*f2-1.5*f3,-0.5*f2+0.5*f3);
2867
+ }
2868
+ vec4 historyAt(vec2 uv) {
2869
+ ivec2 size=textureSize(u_history,0);
2870
+ vec2 position=uv*vec2(size)-0.5;
2871
+ ivec2 base=ivec2(floor(position));
2872
+ vec4 wx=cubicWeights(fract(position.x)), wy=cubicWeights(fract(position.y));
2873
+ vec4 result=vec4(0.0);
2874
+ // Repeated bilinear history reconstruction diffuses a moving edge every
2875
+ // frame. Cubic reconstruction preserves it; the resolve's current-neighbor
2876
+ // clipping below bounds ringing before accumulation.
2877
+ for(int y=0;y<4;y++) for(int x=0;x<4;x++) {
2878
+ result+=texelFetch(u_history,clamp(base+ivec2(x-1,y-1),ivec2(0),size-1),0)*wx[x]*wy[y];
2879
+ }
2880
+ return result;
2881
+ }
2238
2882
 
2239
2883
  void main() {
2240
2884
  ivec2 pixel = ivec2(gl_FragCoord.xy);
2885
+ ivec2 size = textureSize(u_source, 0);
2886
+ vec2 outputUV=(vec2(pixel)+0.5)/vec2(size);
2887
+ // Raster jitter supplies quadrature samples inside the output pixel.
2888
+ // Keep that sample on its pixel's accumulation grid; undoing the jitter
2889
+ // spatially filters it and defeats temporal coverage integration.
2890
+ vec2 sourceUV=outputUV;
2891
+ ivec2 sourcePixel=clamp(ivec2(floor(sourceUV*vec2(size))),ivec2(0),size-1);
2892
+ // Reconstruct current color on the stable output grid before accumulation.
2241
2893
  vec4 source = texelFetch(u_source, pixel, 0);
2242
- vec3 history = texelFetch(u_history, pixel, 0).rgb;
2243
- outColor = vec4(mix(source.rgb, history, u_blend), source.a);
2894
+ vec4 motion = u_temporal ? texelFetch(u_velocity, sourcePixel, 0) : vec4(0.0);
2895
+ // A currently uncovered sample may still carry integrated foreground
2896
+ // coverage in history. Track nearby foreground motion for that coverage,
2897
+ // but retain this pixel's own current/previous depth tags: copying RGBA
2898
+ // wholesale falsely labels background color as a foreground surface.
2899
+ if (u_temporal) {
2900
+ float nearestDepth=motion.b;
2901
+ for(int y=-1;y<=1;y++) for(int x=-1;x<=1;x++) {
2902
+ vec4 candidate=texelFetch(u_velocity,clamp(pixel+ivec2(x,y),ivec2(0),size-1),0);
2903
+ if(candidate.b<nearestDepth) {nearestDepth=candidate.b;motion.xy=candidate.xy;}
2904
+ }
2905
+ }
2906
+ vec2 previousUV = outputUV-motion.xy;
2907
+ vec4 history = u_temporal ? historyAt(previousUV) : texture(u_history, previousUV);
2908
+ float weight = u_blend;
2909
+ if (u_temporal) {
2910
+ // Reprojected history incurs reconstruction error as the footprint moves
2911
+ // across the pixel grid. Reduce its confidence with displacement instead
2912
+ // of repeatedly giving a softened moving edge the stationary .9 weight.
2913
+ // Physical motion itself remains fully represented in previousUV.
2914
+ weight /= 1.0 + length(motion.xy * vec2(size));
2915
+ // Depth is categorical at a silhouette: bilinear alpha interpolation creates
2916
+ // nonexistent surfaces and rejects valid history on every jittered edge.
2917
+ // Test surface presence in the central history footprint. RGB remains
2918
+ // an integrated coverage value reconstructed from the wider cubic kernel.
2919
+ ivec2 previousBase=ivec2(floor(previousUV*vec2(size)-vec2(0.5)));
2920
+ float depthError=1.0;
2921
+ for(int y=0;y<2;y++) for(int x=0;x<2;x++) {
2922
+ float previousDepth=texelFetch(u_history,clamp(previousBase+ivec2(x,y),ivec2(0),size-1),0).a;
2923
+ depthError=min(depthError,abs(previousDepth-motion.a));
2924
+ }
2925
+ if (any(lessThan(previousUV,vec2(0.0))) || any(greaterThanEqual(previousUV,vec2(1.0))) || depthError>0.002) weight=0.0;
2926
+ vec3 lo=source.rgb, hi=source.rgb;
2927
+ for(int y=-1;y<=1;y++) for(int x=-1;x<=1;x++) { vec3 tap=texelFetch(u_source,clamp(pixel+ivec2(x,y),ivec2(0),size-1),0).rgb; lo=min(lo,tap); hi=max(hi,tap); }
2928
+ history.rgb=clamp(history.rgb,lo,hi);
2929
+ }
2930
+ vec3 resolved=mix(source.rgb,history.rgb,weight);
2931
+ outColor=vec4(resolved,u_temporal?motion.b:source.a);
2244
2932
  }
2245
- `, "webgl2-taa");
2933
+ `, "webgl2-taa-accumulation");
2246
2934
  return this.taaProgram;
2247
2935
  }
2936
+ ensureTaaPresentationProgram() {
2937
+ if (this.taaPresentationProgram)
2938
+ return this.taaPresentationProgram;
2939
+ this.taaPresentationProgram = this.createFullscreenProgram(`#version 300 es
2940
+ precision highp float;
2941
+ uniform sampler2D u_source;
2942
+ uniform sampler2D u_accumulated;
2943
+ out vec4 outColor;
2944
+ void main() {
2945
+ ivec2 pixel=ivec2(gl_FragCoord.xy);
2946
+ ivec2 size=textureSize(u_source,0);
2947
+ vec4 source=texelFetch(u_source,pixel,0);
2948
+ vec3 accumulated=texelFetch(u_accumulated,pixel,0).rgb;
2949
+ // Retained native pixels isolate the remaining spatial excess to the
2950
+ // horizontally moving silhouette. Apply a bounded three-tap unsharp resolve
2951
+ // to presentation only; accumulated history remains immutable.
2952
+ vec3 center=mix(source.rgb,accumulated,0.90);
2953
+ ivec2 leftPixel=clamp(pixel+ivec2(-1,0),ivec2(0),size-1);
2954
+ ivec2 rightPixel=clamp(pixel+ivec2(1,0),ivec2(0),size-1);
2955
+ vec3 left=mix(texelFetch(u_source,leftPixel,0).rgb,texelFetch(u_accumulated,leftPixel,0).rgb,0.90);
2956
+ vec3 right=mix(texelFetch(u_source,rightPixel,0).rgb,texelFetch(u_accumulated,rightPixel,0).rgb,0.90);
2957
+ outColor=vec4(clamp(center*1.04-(left+right)*0.02,0.0,1.0),source.a);
2958
+ }
2959
+ `, "webgl2-taa-presentation");
2960
+ return this.taaPresentationProgram;
2961
+ }
2248
2962
  createFullscreenProgram(fragmentSource, label) {
2249
2963
  const vertex = this.compileShader(this.gl.VERTEX_SHADER, `#version 300 es
2250
2964
  precision highp float;
@@ -3248,7 +3962,22 @@ function normalizeNativeBloomOptions(options) {
3248
3962
  if (!Number.isInteger(radius) || radius < 0 || radius > 16) {
3249
3963
  throw new RenderDeviceError("Bloom radius must be an integer in [0, 16].", "INVALID_POSTPROCESS_OPTIONS", { radius });
3250
3964
  }
3251
- return { threshold, intensity, radius };
3965
+ const softKnee = bloomNumberOption(options, "softKnee", 0);
3966
+ if (!(softKnee >= 0) || softKnee > 0.5) {
3967
+ throw new RenderDeviceError("Bloom softKnee must be finite and in [0, 0.5].", "INVALID_POSTPROCESS_OPTIONS", { softKnee });
3968
+ }
3969
+ const shoulder = bloomNumberOption(options, "shoulder", 0);
3970
+ if (!(shoulder >= 0) || shoulder > 1) {
3971
+ throw new RenderDeviceError("Bloom shoulder must be finite and in [0, 1].", "INVALID_POSTPROCESS_OPTIONS", { shoulder });
3972
+ }
3973
+ let quality;
3974
+ try {
3975
+ quality = normalizeBloomQualityPreset(options.quality);
3976
+ }
3977
+ catch {
3978
+ throw new RenderDeviceError("Bloom quality must be one of performance|balanced|cinematic.", "INVALID_POSTPROCESS_OPTIONS", { quality: options.quality });
3979
+ }
3980
+ return { threshold, intensity, radius, quality, softKnee, shoulder };
3252
3981
  }
3253
3982
  function normalizeNativeOutlineOptions(options) {
3254
3983
  const width = outlineNumberOption(options, "width", 1);
@@ -3293,6 +4022,20 @@ function normalizeNativeSsaoOptions(options) {
3293
4022
  }
3294
4023
  return { radius, intensity, bias };
3295
4024
  }
4025
+ /**
4026
+ * Camera depth range for linearizing sampleable GL depth in the native
4027
+ * SSR/depth-of-field programs (muse3jsparity-PRD A3). Missing or invalid
4028
+ * ranges fall back to the engine perspective defaults (0.1/1000) rather than
4029
+ * failing the frame: the range is a calibration hint, and every engine camera
4030
+ * builder defaults to exactly 0.1/1000. The CPU byte kernels intentionally
4031
+ * keep fixture-unit depth semantics (deterministic reference); the native
4032
+ * programs document their GL-depth contract in-shader.
4033
+ */
4034
+ function normalizeLdrDepthRange(range) {
4035
+ const near = range && Number.isFinite(range.near) && range.near > 0 ? range.near : 0.1;
4036
+ const far = range && Number.isFinite(range.far) && range.far > near ? range.far : 1000;
4037
+ return { near, far };
4038
+ }
3296
4039
  function normalizeNativeSsrOptions(options) {
3297
4040
  const intensity = ssrNumberOption(options, "intensity", 0.32);
3298
4041
  const maxDistance = ssrNumberOption(options, "maxDistance", 16);
@@ -3302,7 +4045,11 @@ function normalizeNativeSsrOptions(options) {
3302
4045
  if (!Number.isInteger(maxDistance) || maxDistance < 1 || maxDistance > 64) {
3303
4046
  throw new RenderDeviceError("SSR maxDistance must be an integer in [1, 64].", "INVALID_POSTPROCESS_OPTIONS", { maxDistance });
3304
4047
  }
3305
- return { intensity, maxDistance };
4048
+ const projection = options["projection"];
4049
+ if (!(projection instanceof Float32Array))
4050
+ throw new RenderDeviceError("Native SSR requires the actual frame projection or view-projection matrix.", "INVALID_POSTPROCESS_OPTIONS");
4051
+ const inverseProjection = invertSsrProjection(projection);
4052
+ return { intensity, maxDistance, projection, inverseProjection };
3306
4053
  }
3307
4054
  function normalizeNativeDepthOfFieldOptions(options) {
3308
4055
  const focusDepth = depthOfFieldNumberOption(options, "focusDepth", 0.5);
@@ -3322,6 +4069,11 @@ function normalizeNativeDepthOfFieldOptions(options) {
3322
4069
  function normalizeNativeMotionBlurOptions(options, width, height) {
3323
4070
  const samples = motionBlurNumberOption(options, "samples", 5);
3324
4071
  const scale = motionBlurNumberOption(options, "scale", 1);
4072
+ if (options.temporal) {
4073
+ if (!Number.isInteger(samples) || samples < 2 || samples > 16 || scale < 0 || scale > 8)
4074
+ throw new RenderDeviceError("Invalid motion blur samples/scale", "INVALID_POSTPROCESS_OPTIONS");
4075
+ return { samples, scale, temporal: options.temporal };
4076
+ }
3325
4077
  const value = options.velocity;
3326
4078
  if (!(value instanceof Float32Array) && !Array.isArray(value)) {
3327
4079
  throw new RenderDeviceError("Motion blur requires a velocity Float32Array or number array.", "WEBGL_LDR_POSTPROCESS_VELOCITY_REQUIRED");
@@ -3343,6 +4095,11 @@ function normalizeNativeMotionBlurOptions(options, width, height) {
3343
4095
  }
3344
4096
  function normalizeNativeTaaOptions(options, width, height) {
3345
4097
  const blend = taaNumberOption(options, "blend", 0.18);
4098
+ if (options.temporal) {
4099
+ if (blend < 0 || blend > .95)
4100
+ throw new RenderDeviceError("Invalid TAA blend", "INVALID_POSTPROCESS_OPTIONS");
4101
+ return { blend, temporal: options.temporal };
4102
+ }
3346
4103
  const history = options.history;
3347
4104
  if (!(history instanceof Uint8Array)) {
3348
4105
  throw new RenderDeviceError("TAA requires a Uint8Array history buffer.", "WEBGL_LDR_POSTPROCESS_HISTORY_REQUIRED");