@aura3d/engine 3.0.0 → 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 (349) hide show
  1. package/README.md +24 -5
  2. package/dist/animation/AnimationMixer.d.ts +4 -1
  3. package/dist/animation/AnimationMixer.d.ts.map +1 -1
  4. package/dist/animation/AnimationMixer.js +36 -29
  5. package/dist/animation/AnimationMixer.js.map +1 -1
  6. package/dist/animation/FootIk.d.ts +9 -0
  7. package/dist/animation/FootIk.d.ts.map +1 -1
  8. package/dist/animation/FootIk.js +22 -8
  9. package/dist/animation/FootIk.js.map +1 -1
  10. package/dist/animation/HumanoidRetargeting.d.ts +6 -2
  11. package/dist/animation/HumanoidRetargeting.d.ts.map +1 -1
  12. package/dist/animation/HumanoidRetargeting.js +18 -8
  13. package/dist/animation/HumanoidRetargeting.js.map +1 -1
  14. package/dist/animation/LocomotionEvidence.d.ts +34 -0
  15. package/dist/animation/LocomotionEvidence.d.ts.map +1 -0
  16. package/dist/animation/LocomotionEvidence.js +48 -0
  17. package/dist/animation/LocomotionEvidence.js.map +1 -0
  18. package/dist/animation/RootMotion.d.ts +13 -4
  19. package/dist/animation/RootMotion.d.ts.map +1 -1
  20. package/dist/animation/RootMotion.js +21 -1
  21. package/dist/animation/RootMotion.js.map +1 -1
  22. package/dist/animation/index.d.ts +1 -0
  23. package/dist/animation/index.d.ts.map +1 -1
  24. package/dist/animation/index.js +1 -0
  25. package/dist/animation/index.js.map +1 -1
  26. package/dist/assets/GLTFAnimationRuntime.d.ts +84 -4
  27. package/dist/assets/GLTFAnimationRuntime.d.ts.map +1 -1
  28. package/dist/assets/GLTFAnimationRuntime.js +308 -23
  29. package/dist/assets/GLTFAnimationRuntime.js.map +1 -1
  30. package/dist/assets/GLTFCompressionDecoders.d.ts +1 -0
  31. package/dist/assets/GLTFCompressionDecoders.d.ts.map +1 -1
  32. package/dist/assets/GLTFCompressionDecoders.js +20 -5
  33. package/dist/assets/GLTFCompressionDecoders.js.map +1 -1
  34. package/dist/assets/GLTFLoader.d.ts.map +1 -1
  35. package/dist/assets/GLTFLoader.js +21 -7
  36. package/dist/assets/GLTFLoader.js.map +1 -1
  37. package/dist/assets/KTX2BasisTextureTranscoder.d.ts.map +1 -1
  38. package/dist/assets/KTX2BasisTextureTranscoder.js +8 -0
  39. package/dist/assets/KTX2BasisTextureTranscoder.js.map +1 -1
  40. package/dist/controls/MapControls.d.ts +1 -1
  41. package/dist/controls/MapControls.d.ts.map +1 -1
  42. package/dist/controls/MapControls.js +3 -3
  43. package/dist/controls/MapControls.js.map +1 -1
  44. package/dist/controls/OrbitControls.d.ts +17 -3
  45. package/dist/controls/OrbitControls.d.ts.map +1 -1
  46. package/dist/controls/OrbitControls.js +24 -7
  47. package/dist/controls/OrbitControls.js.map +1 -1
  48. package/dist/controls/TrackballControls.d.ts.map +1 -1
  49. package/dist/controls/TrackballControls.js +2 -0
  50. package/dist/controls/TrackballControls.js.map +1 -1
  51. package/dist/create-aura3d/index.js +1 -1
  52. package/dist/engine/agent-api/AnimationController.d.ts +5 -1
  53. package/dist/engine/agent-api/AnimationController.d.ts.map +1 -1
  54. package/dist/engine/agent-api/AnimationController.js +5 -0
  55. package/dist/engine/agent-api/AnimationController.js.map +1 -1
  56. package/dist/engine/agent-api/CameraProjection.d.ts +2 -0
  57. package/dist/engine/agent-api/CameraProjection.d.ts.map +1 -0
  58. package/dist/engine/agent-api/CameraProjection.js +2 -0
  59. package/dist/engine/agent-api/CameraProjection.js.map +1 -0
  60. package/dist/engine/agent-api/DeferredFrameResources.d.ts +2 -0
  61. package/dist/engine/agent-api/DeferredFrameResources.d.ts.map +1 -0
  62. package/dist/engine/agent-api/DeferredFrameResources.js +2 -0
  63. package/dist/engine/agent-api/DeferredFrameResources.js.map +1 -0
  64. package/dist/engine/agent-api/FootPlanting.d.ts +6 -0
  65. package/dist/engine/agent-api/FootPlanting.d.ts.map +1 -1
  66. package/dist/engine/agent-api/FootPlanting.js +2 -1
  67. package/dist/engine/agent-api/FootPlanting.js.map +1 -1
  68. package/dist/engine/agent-api/GameCameraRigs.d.ts.map +1 -1
  69. package/dist/engine/agent-api/GameCameraRigs.js +7 -2
  70. package/dist/engine/agent-api/GameCameraRigs.js.map +1 -1
  71. package/dist/engine/agent-api/LabelTelemetry.d.ts +8 -2
  72. package/dist/engine/agent-api/LabelTelemetry.d.ts.map +1 -1
  73. package/dist/engine/agent-api/LabelTelemetry.js +18 -4
  74. package/dist/engine/agent-api/LabelTelemetry.js.map +1 -1
  75. package/dist/engine/agent-api/ModelInstanceMatrices.d.ts +2 -0
  76. package/dist/engine/agent-api/ModelInstanceMatrices.d.ts.map +1 -0
  77. package/dist/engine/agent-api/ModelInstanceMatrices.js +2 -0
  78. package/dist/engine/agent-api/ModelInstanceMatrices.js.map +1 -0
  79. package/dist/engine/agent-api/NavigationCrowds.d.ts +28 -0
  80. package/dist/engine/agent-api/NavigationCrowds.d.ts.map +1 -1
  81. package/dist/engine/agent-api/NavigationCrowds.js +73 -1
  82. package/dist/engine/agent-api/NavigationCrowds.js.map +1 -1
  83. package/dist/engine/agent-api/PngTexturePixels.d.ts +9 -0
  84. package/dist/engine/agent-api/PngTexturePixels.d.ts.map +1 -0
  85. package/dist/engine/agent-api/PngTexturePixels.js +53 -0
  86. package/dist/engine/agent-api/PngTexturePixels.js.map +1 -0
  87. package/dist/engine/agent-api/RootDiagnosticSnapshot.d.ts +2 -0
  88. package/dist/engine/agent-api/RootDiagnosticSnapshot.d.ts.map +1 -0
  89. package/dist/engine/agent-api/RootDiagnosticSnapshot.js +2 -0
  90. package/dist/engine/agent-api/RootDiagnosticSnapshot.js.map +1 -0
  91. package/dist/engine/agent-api/RootFrameClock.d.ts +2 -0
  92. package/dist/engine/agent-api/RootFrameClock.d.ts.map +1 -0
  93. package/dist/engine/agent-api/RootFrameClock.js +2 -0
  94. package/dist/engine/agent-api/RootFrameClock.js.map +1 -0
  95. package/dist/engine/agent-api/RootPerformanceQuality.d.ts +2 -0
  96. package/dist/engine/agent-api/RootPerformanceQuality.d.ts.map +1 -0
  97. package/dist/engine/agent-api/RootPerformanceQuality.js +2 -0
  98. package/dist/engine/agent-api/RootPerformanceQuality.js.map +1 -0
  99. package/dist/engine/agent-api/RootRuntimeSupport.d.ts +61 -0
  100. package/dist/engine/agent-api/RootRuntimeSupport.d.ts.map +1 -0
  101. package/dist/engine/agent-api/RootRuntimeSupport.js +127 -0
  102. package/dist/engine/agent-api/RootRuntimeSupport.js.map +1 -0
  103. package/dist/engine/agent-api/RuntimeNodeHandle.d.ts +22 -0
  104. package/dist/engine/agent-api/RuntimeNodeHandle.d.ts.map +1 -1
  105. package/dist/engine/agent-api/RuntimeNodeHandle.js.map +1 -1
  106. package/dist/engine/agent-api/WorldLabelRenderer.d.ts +13 -2
  107. package/dist/engine/agent-api/WorldLabelRenderer.d.ts.map +1 -1
  108. package/dist/engine/agent-api/WorldLabelRenderer.js +58 -26
  109. package/dist/engine/agent-api/WorldLabelRenderer.js.map +1 -1
  110. package/dist/engine/agent-api/index.d.ts +103 -8
  111. package/dist/engine/agent-api/index.d.ts.map +1 -1
  112. package/dist/engine/agent-api/index.js +750 -242
  113. package/dist/engine/agent-api/index.js.map +1 -1
  114. package/dist/engine/agent-api/product-viewer-runtime.d.ts.map +1 -1
  115. package/dist/engine/production-runtime/RootGpuParticleWorkload.d.ts +34 -0
  116. package/dist/engine/production-runtime/RootGpuParticleWorkload.d.ts.map +1 -0
  117. package/dist/engine/production-runtime/RootGpuParticleWorkload.js +117 -0
  118. package/dist/engine/production-runtime/RootGpuParticleWorkload.js.map +1 -0
  119. package/dist/engine/production-runtime/RootRenderSourceBridge.d.ts +2 -0
  120. package/dist/engine/production-runtime/RootRenderSourceBridge.d.ts.map +1 -0
  121. package/dist/engine/production-runtime/RootRenderSourceBridge.js +2 -0
  122. package/dist/engine/production-runtime/RootRenderSourceBridge.js.map +1 -0
  123. package/dist/engine/production-runtime/TypedGLBActor.d.ts +6 -0
  124. package/dist/engine/production-runtime/TypedGLBActor.d.ts.map +1 -1
  125. package/dist/engine/production-runtime/TypedGLBActor.js +13 -0
  126. package/dist/engine/production-runtime/TypedGLBActor.js.map +1 -1
  127. package/dist/engine/production-runtime/index.d.ts +2 -0
  128. package/dist/engine/production-runtime/index.d.ts.map +1 -1
  129. package/dist/engine/production-runtime/index.js +2 -0
  130. package/dist/engine/production-runtime/index.js.map +1 -1
  131. package/dist/input/Haptics.d.ts +1 -1
  132. package/dist/input/Haptics.js +1 -1
  133. package/dist/input/InputSystem.d.ts.map +1 -1
  134. package/dist/input/InputSystem.js +1 -0
  135. package/dist/input/InputSystem.js.map +1 -1
  136. package/dist/input/PointerDevice.d.ts +1 -0
  137. package/dist/input/PointerDevice.d.ts.map +1 -1
  138. package/dist/input/PointerDevice.js +1 -1
  139. package/dist/input/PointerDevice.js.map +1 -1
  140. package/dist/input/controls/OrbitControls.d.ts +44 -1
  141. package/dist/input/controls/OrbitControls.d.ts.map +1 -1
  142. package/dist/input/controls/OrbitControls.js +151 -12
  143. package/dist/input/controls/OrbitControls.js.map +1 -1
  144. package/dist/lean/base.d.ts +1 -1
  145. package/dist/lean/base.d.ts.map +1 -1
  146. package/dist/lean/base.js +1 -1
  147. package/dist/lean/base.js.map +1 -1
  148. package/dist/lean/index.js +1 -1
  149. package/dist/lean/index.js.map +1 -1
  150. package/dist/navigation-recast/index.d.ts +116 -0
  151. package/dist/navigation-recast/index.d.ts.map +1 -0
  152. package/dist/navigation-recast/index.js +247 -0
  153. package/dist/navigation-recast/index.js.map +1 -0
  154. package/dist/physics-rapier/HeightfieldLayout.d.ts +12 -0
  155. package/dist/physics-rapier/HeightfieldLayout.d.ts.map +1 -0
  156. package/dist/physics-rapier/HeightfieldLayout.js +19 -0
  157. package/dist/physics-rapier/HeightfieldLayout.js.map +1 -0
  158. package/dist/physics-rapier/index.d.ts.map +1 -1
  159. package/dist/physics-rapier/index.js +2 -1
  160. package/dist/physics-rapier/index.js.map +1 -1
  161. package/dist/rendering/CascadedShadowMaps.d.ts.map +1 -1
  162. package/dist/rendering/CascadedShadowMaps.js +3 -1
  163. package/dist/rendering/CascadedShadowMaps.js.map +1 -1
  164. package/dist/rendering/ExtensionScalarAtlas.d.ts +18 -0
  165. package/dist/rendering/ExtensionScalarAtlas.d.ts.map +1 -0
  166. package/dist/rendering/ExtensionScalarAtlas.js +107 -0
  167. package/dist/rendering/ExtensionScalarAtlas.js.map +1 -0
  168. package/dist/rendering/LeanWebGL2Device.d.ts +265 -0
  169. package/dist/rendering/LeanWebGL2Device.d.ts.map +1 -0
  170. package/dist/rendering/LeanWebGL2Device.js +4001 -0
  171. package/dist/rendering/LeanWebGL2Device.js.map +1 -0
  172. package/dist/rendering/MaterialBinding.js +1 -1
  173. package/dist/rendering/MaterialBinding.js.map +1 -1
  174. package/dist/rendering/PlanarReflection.d.ts +2 -2
  175. package/dist/rendering/PlanarReflection.js +3 -3
  176. package/dist/rendering/PlanarReflection.js.map +1 -1
  177. package/dist/rendering/PostProcessPass.d.ts +19 -0
  178. package/dist/rendering/PostProcessPass.d.ts.map +1 -1
  179. package/dist/rendering/PostProcessPass.js +64 -32
  180. package/dist/rendering/PostProcessPass.js.map +1 -1
  181. package/dist/rendering/ProjectionMath.d.ts +3 -0
  182. package/dist/rendering/ProjectionMath.d.ts.map +1 -0
  183. package/dist/rendering/ProjectionMath.js +26 -0
  184. package/dist/rendering/ProjectionMath.js.map +1 -0
  185. package/dist/rendering/ReflectionSurfaces.d.ts +4 -1
  186. package/dist/rendering/ReflectionSurfaces.d.ts.map +1 -1
  187. package/dist/rendering/ReflectionSurfaces.js +10 -1
  188. package/dist/rendering/ReflectionSurfaces.js.map +1 -1
  189. package/dist/rendering/RenderDevice.d.ts +5 -0
  190. package/dist/rendering/RenderDevice.d.ts.map +1 -1
  191. package/dist/rendering/RenderDevice.js.map +1 -1
  192. package/dist/rendering/Renderer.d.ts +9 -0
  193. package/dist/rendering/Renderer.d.ts.map +1 -1
  194. package/dist/rendering/Renderer.js +94 -8
  195. package/dist/rendering/Renderer.js.map +1 -1
  196. package/dist/rendering/RendererPostprocessPlan.d.ts +16 -3
  197. package/dist/rendering/RendererPostprocessPlan.d.ts.map +1 -1
  198. package/dist/rendering/RendererPostprocessPlan.js +16 -2
  199. package/dist/rendering/RendererPostprocessPlan.js.map +1 -1
  200. package/dist/rendering/ScreenSpaceReflectionPass.d.ts +43 -0
  201. package/dist/rendering/ScreenSpaceReflectionPass.d.ts.map +1 -0
  202. package/dist/rendering/ScreenSpaceReflectionPass.js +58 -0
  203. package/dist/rendering/ScreenSpaceReflectionPass.js.map +1 -0
  204. package/dist/rendering/ShaderLibrary.d.ts.map +1 -1
  205. package/dist/rendering/ShaderLibrary.js +168 -197
  206. package/dist/rendering/ShaderLibrary.js.map +1 -1
  207. package/dist/rendering/ShaderLibraryCore.d.ts +3 -0
  208. package/dist/rendering/ShaderLibraryCore.d.ts.map +1 -1
  209. package/dist/rendering/ShaderLibraryCore.js +3 -0
  210. package/dist/rendering/ShaderLibraryCore.js.map +1 -1
  211. package/dist/rendering/TemporalHistory.d.ts +37 -0
  212. package/dist/rendering/TemporalHistory.d.ts.map +1 -0
  213. package/dist/rendering/TemporalHistory.js +135 -0
  214. package/dist/rendering/TemporalHistory.js.map +1 -0
  215. package/dist/rendering/TemporalMath.d.ts +3 -0
  216. package/dist/rendering/TemporalMath.d.ts.map +1 -0
  217. package/dist/rendering/TemporalMath.js +9 -0
  218. package/dist/rendering/TemporalMath.js.map +1 -0
  219. package/dist/rendering/TexturedPBRMaterial.d.ts +3 -0
  220. package/dist/rendering/TexturedPBRMaterial.d.ts.map +1 -1
  221. package/dist/rendering/TexturedPBRMaterial.js +65 -3
  222. package/dist/rendering/TexturedPBRMaterial.js.map +1 -1
  223. package/dist/rendering/WebGL2Device.d.ts +15 -0
  224. package/dist/rendering/WebGL2Device.d.ts.map +1 -1
  225. package/dist/rendering/WebGL2Device.js +328 -90
  226. package/dist/rendering/WebGL2Device.js.map +1 -1
  227. package/dist/rendering/WebGPUDevice.d.ts +16 -5
  228. package/dist/rendering/WebGPUDevice.d.ts.map +1 -1
  229. package/dist/rendering/WebGPUDevice.js +357 -58
  230. package/dist/rendering/WebGPUDevice.js.map +1 -1
  231. package/dist/rendering/WebGPUExtensionAtlas.d.ts +6 -0
  232. package/dist/rendering/WebGPUExtensionAtlas.d.ts.map +1 -0
  233. package/dist/rendering/WebGPUExtensionAtlas.js +225 -0
  234. package/dist/rendering/WebGPUExtensionAtlas.js.map +1 -0
  235. package/dist/rendering/effects/GPUParticleBackend.d.ts +4 -2
  236. package/dist/rendering/effects/GPUParticleBackend.d.ts.map +1 -1
  237. package/dist/rendering/effects/GPUParticleBackend.js +20 -2
  238. package/dist/rendering/effects/GPUParticleBackend.js.map +1 -1
  239. package/dist/rendering/effects/ResidentGPUParticleRenderer.d.ts +158 -0
  240. package/dist/rendering/effects/ResidentGPUParticleRenderer.d.ts.map +1 -0
  241. package/dist/rendering/effects/ResidentGPUParticleRenderer.js +576 -0
  242. package/dist/rendering/effects/ResidentGPUParticleRenderer.js.map +1 -0
  243. package/dist/rendering/extension-scalar-atlas.d.ts +3 -0
  244. package/dist/rendering/extension-scalar-atlas.d.ts.map +1 -0
  245. package/dist/rendering/extension-scalar-atlas.js +2 -0
  246. package/dist/rendering/extension-scalar-atlas.js.map +1 -0
  247. package/dist/rendering/index.d.ts +1 -0
  248. package/dist/rendering/index.d.ts.map +1 -1
  249. package/dist/rendering/index.js +1 -0
  250. package/dist/rendering/index.js.map +1 -1
  251. package/dist/rendering/lean/LeanProductionRenderer.d.ts.map +1 -1
  252. package/dist/rendering/lean/LeanProductionRenderer.js +2 -2
  253. package/dist/rendering/lean/LeanProductionRenderer.js.map +1 -1
  254. package/dist/rendering/lean-core-runtime.d.ts +13 -0
  255. package/dist/rendering/lean-core-runtime.d.ts.map +1 -0
  256. package/dist/rendering/lean-core-runtime.js +10 -0
  257. package/dist/rendering/lean-core-runtime.js.map +1 -0
  258. package/dist/rendering/postprocess/NativeBloomPyramid.d.ts +17 -1
  259. package/dist/rendering/postprocess/NativeBloomPyramid.d.ts.map +1 -1
  260. package/dist/rendering/postprocess/NativeBloomPyramid.js +31 -1
  261. package/dist/rendering/postprocess/NativeBloomPyramid.js.map +1 -1
  262. package/dist/rendering/production-runtime/ProductionRendererTypes.d.ts +1 -0
  263. package/dist/rendering/production-runtime/ProductionRendererTypes.d.ts.map +1 -1
  264. package/dist/rendering/production-runtime/ProductionRendererTypes.js.map +1 -1
  265. package/dist/rendering/production-runtime/ProductionRuntimeRenderer.d.ts +3 -0
  266. package/dist/rendering/production-runtime/ProductionRuntimeRenderer.d.ts.map +1 -1
  267. package/dist/rendering/production-runtime/ProductionRuntimeRenderer.js +8 -1
  268. package/dist/rendering/production-runtime/ProductionRuntimeRenderer.js.map +1 -1
  269. package/dist/rendering/production-runtime/ProductionWebGL2Renderer.d.ts +4 -0
  270. package/dist/rendering/production-runtime/ProductionWebGL2Renderer.d.ts.map +1 -1
  271. package/dist/rendering/production-runtime/ProductionWebGL2Renderer.js +15 -0
  272. package/dist/rendering/production-runtime/ProductionWebGL2Renderer.js.map +1 -1
  273. package/dist/rendering/production-runtime/ProductionWebGPURenderer.d.ts +7 -1
  274. package/dist/rendering/production-runtime/ProductionWebGPURenderer.d.ts.map +1 -1
  275. package/dist/rendering/production-runtime/ProductionWebGPURenderer.js +28 -2
  276. package/dist/rendering/production-runtime/ProductionWebGPURenderer.js.map +1 -1
  277. package/dist/rendering/production-runtime/framegraph/FrameGraph.d.ts +4 -2
  278. package/dist/rendering/production-runtime/framegraph/FrameGraph.d.ts.map +1 -1
  279. package/dist/rendering/production-runtime/framegraph/FrameGraph.js +62 -4
  280. package/dist/rendering/production-runtime/framegraph/FrameGraph.js.map +1 -1
  281. package/dist/rendering/production-runtime/framegraph/NativeFrameGraphBindings.d.ts +32 -0
  282. package/dist/rendering/production-runtime/framegraph/NativeFrameGraphBindings.d.ts.map +1 -0
  283. package/dist/rendering/production-runtime/framegraph/NativeFrameGraphBindings.js +111 -0
  284. package/dist/rendering/production-runtime/framegraph/NativeFrameGraphBindings.js.map +1 -0
  285. package/dist/rendering/production-runtime/framegraph/RenderPass.d.ts +23 -0
  286. package/dist/rendering/production-runtime/framegraph/RenderPass.d.ts.map +1 -1
  287. package/dist/rendering/production-runtime/framegraph/RenderPass.js +87 -1
  288. package/dist/rendering/production-runtime/framegraph/RenderPass.js.map +1 -1
  289. package/dist/rendering/production-runtime/index.d.ts +2 -0
  290. package/dist/rendering/production-runtime/index.d.ts.map +1 -1
  291. package/dist/rendering/production-runtime/index.js +1 -0
  292. package/dist/rendering/production-runtime/index.js.map +1 -1
  293. package/dist/rendering/production-runtime/passes/DepthPrepass.d.ts +4 -12
  294. package/dist/rendering/production-runtime/passes/DepthPrepass.d.ts.map +1 -1
  295. package/dist/rendering/production-runtime/passes/DepthPrepass.js +3 -11
  296. package/dist/rendering/production-runtime/passes/DepthPrepass.js.map +1 -1
  297. package/dist/rendering/production-runtime/passes/FramegraphTopology.js +1 -1
  298. package/dist/rendering/production-runtime/passes/FramegraphTopology.js.map +1 -1
  299. package/dist/rendering/production-runtime/passes/OpaquePass.d.ts +3 -8
  300. package/dist/rendering/production-runtime/passes/OpaquePass.d.ts.map +1 -1
  301. package/dist/rendering/production-runtime/passes/OpaquePass.js +3 -1
  302. package/dist/rendering/production-runtime/passes/OpaquePass.js.map +1 -1
  303. package/dist/rendering/production-runtime/passes/ShadowPass.d.ts +3 -8
  304. package/dist/rendering/production-runtime/passes/ShadowPass.d.ts.map +1 -1
  305. package/dist/rendering/production-runtime/passes/ShadowPass.js +2 -0
  306. package/dist/rendering/production-runtime/passes/ShadowPass.js.map +1 -1
  307. package/dist/rendering/production-runtime/passes/SkyboxPass.d.ts +3 -7
  308. package/dist/rendering/production-runtime/passes/SkyboxPass.d.ts.map +1 -1
  309. package/dist/rendering/production-runtime/passes/SkyboxPass.js +2 -0
  310. package/dist/rendering/production-runtime/passes/SkyboxPass.js.map +1 -1
  311. package/dist/rendering/production-runtime/passes/ToneMappingPass.d.ts +7 -10
  312. package/dist/rendering/production-runtime/passes/ToneMappingPass.d.ts.map +1 -1
  313. package/dist/rendering/production-runtime/passes/ToneMappingPass.js +9 -0
  314. package/dist/rendering/production-runtime/passes/ToneMappingPass.js.map +1 -1
  315. package/dist/rendering/production-runtime/passes/TransparentPass.d.ts +4 -9
  316. package/dist/rendering/production-runtime/passes/TransparentPass.d.ts.map +1 -1
  317. package/dist/rendering/production-runtime/passes/TransparentPass.js +6 -1
  318. package/dist/rendering/production-runtime/passes/TransparentPass.js.map +1 -1
  319. package/dist/rendering/reflection-surfaces.d.ts +5 -0
  320. package/dist/rendering/reflection-surfaces.d.ts.map +1 -0
  321. package/dist/rendering/reflection-surfaces.js +3 -0
  322. package/dist/rendering/reflection-surfaces.js.map +1 -0
  323. package/dist/rendering/webgpu/WebGPUPostShaders.d.ts +2 -3
  324. package/dist/rendering/webgpu/WebGPUPostShaders.d.ts.map +1 -1
  325. package/dist/rendering/webgpu/WebGPUPostShaders.js +2 -3
  326. package/dist/rendering/webgpu/WebGPUPostShaders.js.map +1 -1
  327. package/dist/rendering/webgpu/WebGPUTemporal.d.ts +17 -0
  328. package/dist/rendering/webgpu/WebGPUTemporal.d.ts.map +1 -0
  329. package/dist/rendering/webgpu/WebGPUTemporal.js +64 -0
  330. package/dist/rendering/webgpu/WebGPUTemporal.js.map +1 -0
  331. package/dist/rendering/webgpu.d.ts +6 -0
  332. package/dist/rendering/webgpu.d.ts.map +1 -1
  333. package/dist/rendering/webgpu.js +4 -0
  334. package/dist/rendering/webgpu.js.map +1 -1
  335. package/package.json +37 -24
  336. package/templates/cinematic-scene/package.json +2 -2
  337. package/templates/cinematic-scene/src/main.ts +14 -2
  338. package/templates/mini-game/package.json +2 -2
  339. package/templates/product-viewer/package.json +2 -2
  340. package/templates/production-asset-inspector/package.json +1 -1
  341. package/templates/production-material-studio/package.json +1 -1
  342. package/templates/production-product-configurator/package.json +1 -1
  343. package/templates/production-product-viewer/package.json +1 -1
  344. package/templates/cinematic-scene/node_modules/.vite/deps/_metadata.json +0 -8
  345. package/templates/cinematic-scene/node_modules/.vite/deps/package.json +0 -3
  346. package/templates/cinematic-scene/test-results/.last-run.json +0 -6
  347. package/templates/cinematic-scene/test-results/route-health-Aura3D-cinematic-scene-reaches-ready-state/error-context.md +0 -64
  348. package/templates/product-viewer/node_modules/.vite/deps/_metadata.json +0 -8
  349. package/templates/product-viewer/node_modules/.vite/deps/package.json +0 -3
@@ -1,5 +1,7 @@
1
+ import { WEBGPU_EXTENSION_ATLAS_WGSL, webgpuExtensionAtlasUniforms } from "./WebGPUExtensionAtlas.js";
1
2
  import { RenderDeviceError, viewBytes, resolveGpuTargetOwner, spreadGpuTargetInventory } from "./RenderDevice.js";
2
3
  import { MAX_WEBGPU_SKINNING_JOINTS } from "./WebGPUSkinningLimits.js";
4
+ import { normalizeWebGPUTemporalOptions, WEBGPU_TAA_FRAGMENT, WEBGPU_TAA_OUTPUT_FRAGMENT } from "./webgpu/WebGPUTemporal.js";
3
5
  import { defaultWebGPUBloomWeights, normalizeWebGPUColorGradeOptions, normalizeWebGPUBloomOptions, webgpuBlurFragment, webgpuBloomCompositeFragment, webgpuBrightExtractFragment, webgpuColorGradeFragment, webgpuFxaaFragment, WEBGPU_POST_VERTEX_WGSL } from "./webgpu/WebGPUPostShaders.js";
4
6
  import { DEFAULT_DEPTH_SHADER_MARKER } from "./ShaderLibraryCore.js";
5
7
  import { reflectShaderSources } from "./ShaderReflection.js";
@@ -24,6 +26,26 @@ const TEXTURE_USAGE = {
24
26
  TEXTURE_BINDING: 0x0004
25
27
  };
26
28
  const DEPTH_TEXTURE_FORMAT = "depth24plus";
29
+ const WEBGPU_NATIVE_COPY_FRAGMENT = `
30
+ @group(0) @binding(0) var<uniform> params: vec4<f32>;
31
+ @group(0) @binding(1) var linearSampler: sampler;
32
+ @group(0) @binding(2) var source: texture_2d<f32>;
33
+ @fragment fn fs_copy(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
34
+ return textureSample(source, linearSampler, clamp(uv, params.xy * 0.5, vec2<f32>(1.0) - params.xy * 0.5));
35
+ }`;
36
+ const WEBGPU_NATIVE_TONE_FRAGMENT = `
37
+ @group(0) @binding(0) var<uniform> params: vec4<f32>;
38
+ @group(0) @binding(1) var linearSampler: sampler;
39
+ @group(0) @binding(2) var source: texture_2d<f32>;
40
+ @fragment fn fs_tone(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
41
+ let sample = textureSample(source, linearSampler, uv);
42
+ var c = max(sample.rgb * params.x / params.w, vec3<f32>(0.0));
43
+ if (params.y > 2.5) { let toe=max(c-vec3<f32>(0.004),vec3<f32>(0.0)); c=min((toe*(6.2*toe+vec3<f32>(0.5)))/(toe*(6.2*toe+vec3<f32>(1.7))+vec3<f32>(0.06)),c*1.08); }
44
+ else if (params.y > 1.5) { c = clamp((c * (2.51 * c + vec3<f32>(0.03))) / (c * (2.43 * c + vec3<f32>(0.59)) + vec3<f32>(0.14)), vec3<f32>(0.0), vec3<f32>(1.0)); }
45
+ else if (params.y > 0.5) { c = c / (vec3<f32>(1.0) + c); }
46
+ if (params.z > 0.5) { c = select(1.055 * pow(c, vec3<f32>(1.0 / 2.4)) - vec3<f32>(0.055), c * 12.92, c <= vec3<f32>(0.0031308)); }
47
+ return vec4<f32>(c, sample.a);
48
+ }`;
27
49
  class WebGPURenderBuffer {
28
50
  id;
29
51
  usage;
@@ -58,9 +80,13 @@ class WebGPUShaderProgram {
58
80
  entryPoints;
59
81
  nativeUniformLayout;
60
82
  portableBindings;
83
+ nativeColorDefaultVertex;
84
+ nativeAtlasVertex;
61
85
  disposed = false;
62
86
  renderPipelines = new Map();
63
- constructor(id, label, marker, reflection, modules, entryPoints, nativeUniformLayout, portableBindings) {
87
+ nativeColorDefaultModule;
88
+ atlasVertexModules = new Map();
89
+ constructor(id, label, marker, reflection, modules, entryPoints, nativeUniformLayout, portableBindings, nativeColorDefaultVertex, nativeAtlasVertex) {
64
90
  this.id = id;
65
91
  this.label = label;
66
92
  this.marker = marker;
@@ -69,6 +95,8 @@ class WebGPUShaderProgram {
69
95
  this.entryPoints = entryPoints;
70
96
  this.nativeUniformLayout = nativeUniformLayout;
71
97
  this.portableBindings = portableBindings;
98
+ this.nativeColorDefaultVertex = nativeColorDefaultVertex;
99
+ this.nativeAtlasVertex = nativeAtlasVertex;
72
100
  }
73
101
  dispose() {
74
102
  this.disposed = true;
@@ -162,6 +190,8 @@ export class WebGPUDevice {
162
190
  nativeBloomPasses = 0;
163
191
  nativeColorGradePasses = 0;
164
192
  nativeFxaaPasses = 0;
193
+ nativeTaaPasses = 0;
194
+ nativeTemporalBindings = 0;
165
195
  webgpuPostPipelines = new Map();
166
196
  webgpuPostLinearSampler = null;
167
197
  lastWebGPUBloom = null;
@@ -312,7 +342,7 @@ export class WebGPUDevice {
312
342
  this.device.createShaderModule({ label: `${sources.label}-fragment`, code: nativeSources.fragment })
313
343
  ]
314
344
  : [];
315
- const shader = new WebGPUShaderProgram(this.nextId++, sources.label, sources.marker, reflectShaderSources(sources), modules, nativeSources.entryPoints, nativeSources.uniformLayout, nativeSources.portableBindings ?? []);
345
+ const shader = new WebGPUShaderProgram(this.nextId++, sources.label, sources.marker, reflectShaderSources(sources), modules, nativeSources.entryPoints, nativeSources.uniformLayout, nativeSources.portableBindings ?? [], nativeSources.colorDefaultVertex, nativeSources.atlasVertex);
316
346
  this.shaders.add(shader);
317
347
  return shader;
318
348
  }
@@ -787,6 +817,13 @@ export class WebGPUDevice {
787
817
  this.rasterizeDraw(command, vertexBuffer);
788
818
  this.drawCalls += 1;
789
819
  }
820
+ async waitForSubmittedWork() {
821
+ this.assertAlive();
822
+ if (!this.device.queue.onSubmittedWorkDone) {
823
+ throw new RenderDeviceError("Native GPU queue completion is unavailable", "GPU_COMPLETION_UNAVAILABLE");
824
+ }
825
+ await this.device.queue.onSubmittedWorkDone();
826
+ }
790
827
  endFrame() {
791
828
  this.assertFrame();
792
829
  this.frameActive = false;
@@ -821,6 +858,8 @@ export class WebGPUDevice {
821
858
  ["nativeBloomPasses", this.nativeBloomPasses],
822
859
  ["nativeColorGradePasses", this.nativeColorGradePasses],
823
860
  ["nativeFxaaPasses", this.nativeFxaaPasses],
861
+ ["nativeTaaPasses", this.nativeTaaPasses],
862
+ ["nativeTemporalBindings", this.nativeTemporalBindings],
824
863
  ["canvasSubmissions", this.canvasSubmissions]
825
864
  ]);
826
865
  }
@@ -879,7 +918,7 @@ export class WebGPUDevice {
879
918
  *
880
919
  * Renders bright-extract -> separable-blur mip pyramid -> composite over
881
920
  * the source, all as real WGSL fullscreen passes on this device. Returns a
882
- * fresh rgba8 target owned by the caller (dispose it); every intermediate
921
+ * fresh rgba16f target owned by the caller (dispose it); every intermediate
883
922
  * is disposed before return. Throws fail-closed when the native device
884
923
  * cannot run fullscreen passes.
885
924
  */
@@ -893,7 +932,10 @@ export class WebGPUDevice {
893
932
  }
894
933
  const width = src.width;
895
934
  const height = src.height;
896
- const mipFormat = normalized.halfFloat ? "rgba16f" : "rgba8";
935
+ // Preserve HDR source energy even at performance quality. Bloom adds to
936
+ // scene radiance before tone mapping, so its composite is always floating point.
937
+ const halfFloat = normalized.halfFloat || src.colorTexture.format === "rgba16f" || src.colorTexture.format === "rgba32f";
938
+ const mipFormat = halfFloat ? "rgba16f" : "rgba8";
897
939
  const owned = [];
898
940
  const track = (target) => {
899
941
  owned.push(target);
@@ -943,7 +985,7 @@ export class WebGPUDevice {
943
985
  level = mipTarget;
944
986
  }
945
987
  const weights = defaultWebGPUBloomWeights(normalized.mipCount);
946
- const composite = track(this.createRenderTarget({ width, height, format: "rgba8", label: "a3d-webgpu-bloom-composite" }));
988
+ const composite = track(this.createRenderTarget({ width, height, format: "rgba16f", label: "a3d-webgpu-bloom-composite" }));
947
989
  const mipViews = mips.map((mipTarget) => {
948
990
  const view = this.requireRenderTarget(mipTarget).nativeView;
949
991
  if (!view)
@@ -958,13 +1000,13 @@ export class WebGPUDevice {
958
1000
  views: [srcView, ...mipViews],
959
1001
  target: composite
960
1002
  });
961
- const bytesPerPixel = normalized.halfFloat ? 8 : 4;
1003
+ const bytesPerPixel = halfFloat ? 8 : 4;
962
1004
  const targetBytes = width * height * bytesPerPixel
963
- + mips.reduce((total, mipTarget) => total + mipTarget.width * mipTarget.height * bytesPerPixel, 0)
964
- + width * height * 4;
1005
+ + mips.reduce((total, mipTarget) => total + mipTarget.width * mipTarget.height * bytesPerPixel * 2, 0)
1006
+ + width * height * 8;
965
1007
  this.lastWebGPUBloom = {
966
1008
  mipCount: normalized.mipCount,
967
- halfFloat: normalized.halfFloat,
1009
+ halfFloat,
968
1010
  targetBytes,
969
1011
  compositeGain: normalized.strength,
970
1012
  passes: 1 + normalized.mipCount * 2 + 1,
@@ -1033,9 +1075,8 @@ export class WebGPUDevice {
1033
1075
  /**
1034
1076
  * muse3jsparity-PRD J2 — native WebGPU FXAA.
1035
1077
  * Compact FXAA 3.11 console core (luma neighborhood, one perpendicular
1036
- * blend tap pair; flat neighborhoods pass through untouched). TAA is
1037
- * intentionally out of scope: no velocity/history inputs exist on the
1038
- * WebGPU path (same withheld doctrine as the root A3 nodes).
1078
+ * blend tap pair; flat neighborhoods pass through untouched). Temporal
1079
+ * antialiasing has a separate GPU history contract in executeWebGPUTaa.
1039
1080
  */
1040
1081
  executeWebGPUFxaa(source) {
1041
1082
  this.reclaimWebGPUPostUniformBuffers();
@@ -1062,16 +1103,108 @@ export class WebGPUDevice {
1062
1103
  throw error;
1063
1104
  }
1064
1105
  }
1106
+ /** Native target-to-target chain. Unsupported passes fail before any submission. */
1107
+ presentLdrPostprocess(source, options) {
1108
+ this.assertAlive();
1109
+ const supported = new Set(["bloom", "tone-mapping", "color-grade", "fxaa", "taa"]);
1110
+ for (const pass of options.passes) {
1111
+ if (!supported.has(pass.name))
1112
+ throw new RenderDeviceError(`Native WebGPU postprocess does not support ${pass.name}`, "WEBGPU_POSTPROCESS_UNSUPPORTED", { pass: pass.name });
1113
+ }
1114
+ const owned = [];
1115
+ let current = source;
1116
+ const track = (target) => { owned.push(target); current = target; };
1117
+ try {
1118
+ for (const pass of options.passes) {
1119
+ if (pass.name === "bloom")
1120
+ track(this.executeWebGPUBloom(current, pass.options));
1121
+ else if (pass.name === "color-grade")
1122
+ track(this.executeWebGPUColorGrade(current, pass.options));
1123
+ else if (pass.name === "fxaa")
1124
+ track(this.executeWebGPUFxaa(current));
1125
+ else if (pass.name === "taa") {
1126
+ const temporal = pass.options.temporal;
1127
+ if (!temporal)
1128
+ throw new RenderDeviceError("Native WebGPU TAA requires GPU temporal bindings", "TEMPORAL_NATIVE_REQUIRED");
1129
+ track(this.executeWebGPUTaa(current, { ...temporal, ...(typeof pass.options.blend === "number" ? { blend: pass.options.blend } : {}) }));
1130
+ }
1131
+ else if (pass.name === "tone-mapping") {
1132
+ const tone = { ...options.toneMappingDefaults, ...pass.options };
1133
+ const operator = tone.operator ?? "reinhard";
1134
+ const operatorId = operator === "linear" || operator === "none" ? 0 : operator === "reinhard" ? 1 : operator === "aces" || operator === "aces-filmic" ? 2 : operator === "filmic" ? 3 : -1;
1135
+ if (operatorId < 0)
1136
+ throw new RenderDeviceError(`Unsupported native WebGPU tone operator ${String(operator)}`, "WEBGPU_POSTPROCESS_UNSUPPORTED");
1137
+ const exposure = typeof tone.exposure === "number" ? tone.exposure : 1;
1138
+ const whitePoint = typeof tone.whitePoint === "number" ? tone.whitePoint : 1;
1139
+ if (!Number.isFinite(whitePoint) || whitePoint <= 0)
1140
+ throw new RenderDeviceError("Invalid tone white point", "WEBGPU_POSTPROCESS_UNSUPPORTED");
1141
+ if (!Number.isFinite(exposure) || exposure < 0)
1142
+ throw new RenderDeviceError("Invalid tone exposure", "WEBGPU_POSTPROCESS_UNSUPPORTED");
1143
+ const target = this.createRenderTarget({ width: source.width, height: source.height, format: "rgba8", label: "a3d-webgpu-tone" });
1144
+ owned.push(target);
1145
+ this.runWebGPUFullscreenPass({ label: "webgpu-tone", fragment: WEBGPU_NATIVE_TONE_FRAGMENT, entryPoint: "fs_tone",
1146
+ uniforms: new Float32Array([exposure, operatorId, tone.outputColorSpace === "srgb" ? 1 : 0, whitePoint]),
1147
+ views: [this.requireRenderTarget(current).nativeView], target });
1148
+ current = target;
1149
+ }
1150
+ }
1151
+ const target = options.outputTarget ?? null;
1152
+ if (target === current)
1153
+ throw new RenderDeviceError("Native WebGPU presentation source/output alias", "INVALID_RESOURCE");
1154
+ this.runWebGPUFullscreenPass({ label: "webgpu-post-present", fragment: WEBGPU_NATIVE_COPY_FRAGMENT, entryPoint: "fs_copy",
1155
+ uniforms: new Float32Array([1 / source.width, 1 / source.height, 0, 0]),
1156
+ views: [this.requireRenderTarget(current).nativeView], target });
1157
+ if (!target) {
1158
+ this.activeRenderTarget = null;
1159
+ this.canvasSubmissions += 1;
1160
+ }
1161
+ }
1162
+ finally {
1163
+ for (const target of owned)
1164
+ target.dispose();
1165
+ }
1166
+ }
1167
+ /** Resolve GPU velocity/history into caller-owned historyOutput, returning scene-alpha color. */
1168
+ executeWebGPUTaa(source, options) {
1169
+ this.assertAlive();
1170
+ const [blend, depthThreshold] = normalizeWebGPUTemporalOptions(options);
1171
+ const targets = [source, options.velocity, options.history, options.historyOutput];
1172
+ if (new Set(targets).size !== targets.length)
1173
+ throw new RenderDeviceError("TAA targets must not alias", "NATIVE_POST_UNSUPPORTED");
1174
+ const native = targets.map((target) => this.requireRenderTarget(target));
1175
+ if (native.some((target) => target.width !== source.width || target.height !== source.height || !target.nativeView)) {
1176
+ throw new RenderDeviceError("TAA requires same-size native source, velocity and history targets", "NATIVE_POST_UNSUPPORTED");
1177
+ }
1178
+ if (targets.slice(1).some((target) => target.colorTexture.format !== "rgba16f")) {
1179
+ throw new RenderDeviceError("TAA velocity/history targets require rgba16f", "NATIVE_POST_UNSUPPORTED");
1180
+ }
1181
+ const out = this.createRenderTarget({ width: source.width, height: source.height, format: "rgba16f", label: "a3d-webgpu-taa-color" });
1182
+ try {
1183
+ this.runWebGPUFullscreenPass({ label: "webgpu-taa", fragment: WEBGPU_TAA_FRAGMENT, entryPoint: "fs_taa",
1184
+ uniforms: new Float32Array([1 / source.width, 1 / source.height, blend, options.historyValid ? 1 : 0, depthThreshold, 0, 0, 0]),
1185
+ views: native.slice(0, 3).map((target) => target.nativeView), target: options.historyOutput });
1186
+ this.runWebGPUFullscreenPass({ label: "webgpu-taa-output", fragment: WEBGPU_TAA_OUTPUT_FRAGMENT, entryPoint: "fs_taa_output",
1187
+ uniforms: new Float32Array([1 / source.width, 1 / source.height, 0, 0]),
1188
+ views: [native[3].nativeView, native[0].nativeView], target: out });
1189
+ this.nativeTaaPasses += 2;
1190
+ this.nativeTemporalBindings += 3;
1191
+ return out;
1192
+ }
1193
+ catch (error) {
1194
+ out.dispose();
1195
+ throw error;
1196
+ }
1197
+ }
1065
1198
  runWebGPUFullscreenPass(args) {
1066
1199
  // No assertFrame here on purpose: these are target-to-target passes with
1067
1200
  // their own encoders, so they run both inside a Renderer frame (scene ->
1068
1201
  // post chaining) and after it closed (proof harnesses that render, then
1069
1202
  // post, then read back).
1070
1203
  this.assertAlive();
1071
- const target = this.requireRenderTarget(args.target);
1072
- const targetView = target.nativeView;
1204
+ const target = args.target ? this.requireRenderTarget(args.target) : null;
1205
+ const targetView = target ? target.nativeView : this.canvasContext?.getCurrentTexture().createView();
1073
1206
  if (!targetView) {
1074
- throw new RenderDeviceError("WebGPU post target has no native view", "NATIVE_POST_UNSUPPORTED", { label: target.label });
1207
+ throw new RenderDeviceError("WebGPU post target has no native view", "NATIVE_POST_UNSUPPORTED", { label: target?.label ?? "canvas" });
1075
1208
  }
1076
1209
  if (!this.device.createShaderModule || !this.device.createRenderPipeline || !this.device.createCommandEncoder
1077
1210
  || !this.device.createSampler || !this.device.createBuffer || !this.device.createBindGroup) {
@@ -1084,8 +1217,10 @@ export class WebGPUDevice {
1084
1217
  // the submit) so per-pass validation failures land here labeled.
1085
1218
  scopeDevice.pushErrorScope?.("validation");
1086
1219
  }
1087
- const format = webgpuTextureFormat(target.colorTexture.format);
1088
- const cacheKey = `${args.label}:${args.entryPoint}:${format}`;
1220
+ const format = target ? webgpuTextureFormat(target.colorTexture.format) : this.presentationFormat;
1221
+ // Bloom quality changes the fragment binding layout (one texture per mip).
1222
+ // A label/format-only key can reuse the wrong native pipeline after a quality change.
1223
+ const cacheKey = `${args.label}:${args.entryPoint}:${format}:${args.fragment}`;
1089
1224
  let pipeline = this.webgpuPostPipelines.get(cacheKey);
1090
1225
  if (!pipeline) {
1091
1226
  const vertexModule = this.device.createShaderModule({ label: `${args.label}-vs`, code: WEBGPU_POST_VERTEX_WGSL });
@@ -1141,7 +1276,8 @@ export class WebGPUDevice {
1141
1276
  submitted = true;
1142
1277
  // The pass owns every pixel (fullscreen triangle), so a later
1143
1278
  // readPixelsAsync must NOT flush a stale pending clear over it.
1144
- target.nativeNeedsClear = false;
1279
+ if (target)
1280
+ target.nativeNeedsClear = false;
1145
1281
  this.nativeRenderPasses += 1;
1146
1282
  // Retired, not destroyed: the submit only enqueues; the pass reads
1147
1283
  // these uniforms when the GPU gets to it.
@@ -1202,6 +1338,8 @@ export class WebGPUDevice {
1202
1338
  throw new RenderDeviceError("No active frame", "NO_ACTIVE_FRAME");
1203
1339
  }
1204
1340
  }
1341
+ temporalLossListeners = new Set();
1342
+ onDeviceLost(listener) { this.temporalLossListeners.add(listener); return () => this.temporalLossListeners.delete(listener); }
1205
1343
  observeDeviceLost() {
1206
1344
  const lost = this.device.lost;
1207
1345
  if (!lost || typeof lost.then !== "function")
@@ -1210,12 +1348,24 @@ export class WebGPUDevice {
1210
1348
  if (this.disposed)
1211
1349
  return;
1212
1350
  this.contextLost = true;
1351
+ for (const listener of this.temporalLossListeners) {
1352
+ try {
1353
+ listener();
1354
+ }
1355
+ catch { /* Continue notifying owners. */ }
1356
+ }
1213
1357
  const detail = [info.reason, info.message].filter(Boolean).join(": ");
1214
1358
  this.lastError = detail ? `WebGPU device lost: ${detail}` : "WebGPU device lost";
1215
1359
  }).catch((error) => {
1216
1360
  if (this.disposed)
1217
1361
  return;
1218
1362
  this.contextLost = true;
1363
+ for (const listener of this.temporalLossListeners) {
1364
+ try {
1365
+ listener();
1366
+ }
1367
+ catch { /* Continue notifying owners. */ }
1368
+ }
1219
1369
  this.lastError = `WebGPU device lost promise rejected: ${error instanceof Error ? error.message : String(error)}`;
1220
1370
  });
1221
1371
  }
@@ -1289,9 +1439,9 @@ export class WebGPUDevice {
1289
1439
  const worldA = transformPosition(localA, modelMatrix);
1290
1440
  const worldB = transformPosition(localB, modelMatrix);
1291
1441
  const worldC = transformPosition(localC, modelMatrix);
1292
- const colorA = readVertexColor(vertexBuffer.bytes, command.vertexFormat.stride, colorAttribute?.offset, indexA);
1293
- const colorB = readVertexColor(vertexBuffer.bytes, command.vertexFormat.stride, colorAttribute?.offset, indexB);
1294
- const colorC = readVertexColor(vertexBuffer.bytes, command.vertexFormat.stride, colorAttribute?.offset, indexC);
1442
+ const colorA = readVertexColor(vertexBuffer.bytes, command.vertexFormat.stride, colorAttribute?.offset, indexA, colorAttribute?.components);
1443
+ const colorB = readVertexColor(vertexBuffer.bytes, command.vertexFormat.stride, colorAttribute?.offset, indexB, colorAttribute?.components);
1444
+ const colorC = readVertexColor(vertexBuffer.bytes, command.vertexFormat.stride, colorAttribute?.offset, indexC, colorAttribute?.components);
1295
1445
  const uvA = readVertexUv(vertexBuffer.bytes, command.vertexFormat.stride, uvAttribute?.offset, indexA);
1296
1446
  const uvB = readVertexUv(vertexBuffer.bytes, command.vertexFormat.stride, uvAttribute?.offset, indexB);
1297
1447
  const uvC = readVertexUv(vertexBuffer.bytes, command.vertexFormat.stride, uvAttribute?.offset, indexC);
@@ -1304,15 +1454,15 @@ export class WebGPUDevice {
1304
1454
  const indexB = indices[offset + 1];
1305
1455
  const a = transformPosition(readLocal(indexA), matrix);
1306
1456
  const b = transformPosition(readLocal(indexB), matrix);
1307
- const colorA = readVertexColor(vertexBuffer.bytes, command.vertexFormat.stride, colorAttribute?.offset, indexA);
1308
- const colorB = readVertexColor(vertexBuffer.bytes, command.vertexFormat.stride, colorAttribute?.offset, indexB);
1457
+ const colorA = readVertexColor(vertexBuffer.bytes, command.vertexFormat.stride, colorAttribute?.offset, indexA, colorAttribute?.components);
1458
+ const colorB = readVertexColor(vertexBuffer.bytes, command.vertexFormat.stride, colorAttribute?.offset, indexB, colorAttribute?.components);
1309
1459
  rasterizeLine(this.activeRenderTarget, a, b, color, colorA, colorB, depthTest, depthWrite);
1310
1460
  }
1311
1461
  }
1312
1462
  else {
1313
1463
  for (const index of indices) {
1314
1464
  const point = transformPosition(readLocal(index), matrix);
1315
- const vertexColor = readVertexColor(vertexBuffer.bytes, command.vertexFormat.stride, colorAttribute?.offset, index);
1465
+ const vertexColor = readVertexColor(vertexBuffer.bytes, command.vertexFormat.stride, colorAttribute?.offset, index, colorAttribute?.components);
1316
1466
  rasterizePoint(this.activeRenderTarget, point, multiplyColor(color, vertexColor), depthTest, depthWrite);
1317
1467
  }
1318
1468
  }
@@ -1326,7 +1476,27 @@ export class WebGPUDevice {
1326
1476
  if (!hasNativeRenderPipeline(this.device))
1327
1477
  return;
1328
1478
  const shader = this.requireShader(command.shader);
1329
- const vertexModule = shader.modules[0];
1479
+ // GL supplies an opaque white default for an absent vertex color. Native
1480
+ // WGSL inputs must instead match the actual vertex buffer layout.
1481
+ const useDefaultColor = shader.nativeColorDefaultVertex && !command.vertexFormat?.attributes.some(attribute => attribute.semantic === "color");
1482
+ if (useDefaultColor && !shader.nativeColorDefaultModule) {
1483
+ shader.nativeColorDefaultModule = this.device.createShaderModule?.({ label: `${shader.label}-vertex-default-color`, code: shader.nativeColorDefaultVertex });
1484
+ }
1485
+ let vertexModule = useDefaultColor ? shader.nativeColorDefaultModule : shader.modules[0];
1486
+ if (shader.nativeAtlasVertex) {
1487
+ const hasColor = command.vertexFormat?.attributes.some(attribute => attribute.shaderLocation === 4) ?? false;
1488
+ const hasUv1 = command.vertexFormat?.attributes.some(attribute => attribute.shaderLocation === 7) ?? false;
1489
+ const key = `${hasColor}:${hasUv1}`;
1490
+ if (!shader.atlasVertexModules.has(key)) {
1491
+ let source = shader.nativeAtlasVertex;
1492
+ if (!hasColor)
1493
+ source = source.replace(", @location(4) color: vec4<f32>", "").replace("output.color = color;", "output.color = vec4<f32>(1.0);");
1494
+ if (!hasUv1)
1495
+ source = source.replace(", @location(7) uv1: vec2<f32>", "").replace("output.uv1 = uv1;", "output.uv1 = vec2<f32>(0.0);");
1496
+ shader.atlasVertexModules.set(key, this.device.createShaderModule?.({ label: `${shader.label}-atlas-vertex-${key}`, code: source }));
1497
+ }
1498
+ vertexModule = shader.atlasVertexModules.get(key);
1499
+ }
1330
1500
  const fragmentModule = shader.modules[1] ?? vertexModule;
1331
1501
  if (!vertexModule || !fragmentModule)
1332
1502
  return;
@@ -1386,12 +1556,19 @@ export class WebGPUDevice {
1386
1556
  const uniformBuffer = usesNativeDrawUniforms(shader.nativeUniformLayout) ? this.createNativeDrawUniformBuffer(command) : null;
1387
1557
  const sampledTexture = shader.nativeUniformLayout === "generated-texture" ? this.createNativeSampledTextureBinding(uniformBaseColorTextureBinding(command.uniforms)) : null;
1388
1558
  const materialTextures = usesNativePbrTextureBindings(shader.nativeUniformLayout) ? this.createNativePbrTextureBindings(command) : null;
1559
+ const atlasResources = shader.nativeUniformLayout === "generated-atlas-pbr" ? this.createNativeAtlasBindings(command) : null;
1560
+ if (shader.nativeUniformLayout === "generated-atlas-pbr" && !atlasResources) {
1561
+ uniformBuffer?.destroy();
1562
+ return;
1563
+ }
1389
1564
  if (shader.nativeUniformLayout === "generated-texture" && !sampledTexture) {
1390
1565
  uniformBuffer?.destroy();
1566
+ atlasResources?.buffer.destroy();
1391
1567
  return;
1392
1568
  }
1393
1569
  if (usesNativePbrTextureBindings(shader.nativeUniformLayout) && !materialTextures) {
1394
1570
  uniformBuffer?.destroy();
1571
+ atlasResources?.buffer.destroy();
1395
1572
  return;
1396
1573
  }
1397
1574
  if (shader.nativeUniformLayout === "portable" && !portableResources)
@@ -1402,6 +1579,7 @@ export class WebGPUDevice {
1402
1579
  layout: pipeline.getBindGroupLayout(0),
1403
1580
  entries: portableResources?.entries ?? [
1404
1581
  { binding: 0, resource: { buffer: uniformBuffer } },
1582
+ ...(atlasResources?.entries ?? []),
1405
1583
  ...(sampledTexture ? [
1406
1584
  { binding: 1, resource: sampledTexture.sampler },
1407
1585
  { binding: 2, resource: sampledTexture.view }
@@ -1458,6 +1636,7 @@ export class WebGPUDevice {
1458
1636
  if (!pass.setIndexBuffer || !pass.drawIndexed) {
1459
1637
  pass.end();
1460
1638
  uniformBuffer?.destroy();
1639
+ atlasResources?.buffer.destroy();
1461
1640
  portableResources?.buffer.destroy();
1462
1641
  this.lastError = "Native WebGPU indexed draw skipped because the render-pass encoder does not expose indexed draw APIs.";
1463
1642
  return;
@@ -1474,6 +1653,7 @@ export class WebGPUDevice {
1474
1653
  if (this.activeRenderTarget)
1475
1654
  this.activeRenderTarget.nativeNeedsClear = false;
1476
1655
  uniformBuffer?.destroy();
1656
+ atlasResources?.buffer.destroy();
1477
1657
  portableResources?.buffer.destroy();
1478
1658
  this.nativeSubmissions += 1;
1479
1659
  if (sampledTexture)
@@ -1488,7 +1668,7 @@ export class WebGPUDevice {
1488
1668
  this.nativePassthroughSubmissions += 1;
1489
1669
  this.nativeTextureBindings += portableResources?.actualTextureCount ?? 0;
1490
1670
  }
1491
- if (shader.nativeUniformLayout === "generated-pbr" || shader.nativeUniformLayout === "generated-textured-pbr" || shader.nativeUniformLayout === "generated-instanced-pbr")
1671
+ if (shader.nativeUniformLayout === "generated-pbr" || shader.nativeUniformLayout === "generated-atlas-pbr" || shader.nativeUniformLayout === "generated-textured-pbr" || shader.nativeUniformLayout === "generated-instanced-pbr")
1492
1672
  this.nativePbrSubmissions += 1;
1493
1673
  if (shader.nativeUniformLayout === "generated-instanced-pbr")
1494
1674
  this.nativeInstancedSubmissions += 1;
@@ -1496,6 +1676,7 @@ export class WebGPUDevice {
1496
1676
  this.nativeSkinnedSubmissions += 1;
1497
1677
  if (shader.nativeUniformLayout === "generated-morph-unlit")
1498
1678
  this.nativeMorphSubmissions += 1;
1679
+ this.nativeTextureBindings += atlasResources?.actualTextureCount ?? 0;
1499
1680
  if (materialTextures?.actualBaseColor)
1500
1681
  this.nativeTextureBindings += 1;
1501
1682
  if (materialTextures?.actualNormal)
@@ -1799,6 +1980,36 @@ export class WebGPUDevice {
1799
1980
  this.device.queue.writeBuffer(buffer, 0, data);
1800
1981
  return buffer;
1801
1982
  }
1983
+ createNativeAtlasBindings(command) {
1984
+ const definitions = [
1985
+ ["u_extensionScalarAtlas", 20, 0, "linear"],
1986
+ ["u_clearcoatNormalTexture", 22, 21, "linear"],
1987
+ ["u_sheenColorTexture", 24, 23, "srgb"],
1988
+ ["u_anisotropyTexture", 26, 25, "linear"],
1989
+ ["u_emissiveTexture", 28, 27, "srgb"]
1990
+ ];
1991
+ const entries = [];
1992
+ let actualTextureCount = 0;
1993
+ for (const [name, textureIndex, samplerIndex, colorSpace] of definitions) {
1994
+ const actualTexture = this.createNativeSampledTextureBinding(uniformTextureBinding(command.uniforms, name));
1995
+ if (textureIndex === 20 && !actualTexture)
1996
+ throw new RenderDeviceError("Native extension atlas requires a resolved scalar atlas texture", "TEXTURE_BINDING_MISSING");
1997
+ const texture = actualTexture
1998
+ ?? this.createNativeFallbackSampledTextureBinding(`atlas-${name}`, name.includes("Normal") ? [128, 128, 255, 255] : [255, 255, 255, 255], colorSpace);
1999
+ if (!texture)
2000
+ return null;
2001
+ if (texture.actual)
2002
+ actualTextureCount += 1;
2003
+ entries.push({ binding: textureIndex, resource: texture.view });
2004
+ if (samplerIndex)
2005
+ entries.push({ binding: samplerIndex, resource: texture.sampler });
2006
+ }
2007
+ const data = webgpuExtensionAtlasUniforms(command.uniforms);
2008
+ const buffer = this.device.createBuffer({ label: "extension-atlas-uniforms", size: data.byteLength, usage: BUFFER_USAGE.UNIFORM | BUFFER_USAGE.COPY_DST });
2009
+ this.device.queue.writeBuffer(buffer, 0, data);
2010
+ entries.push({ binding: 19, resource: { buffer } });
2011
+ return { buffer, entries, actualTextureCount };
2012
+ }
1802
2013
  createNativePortableBindings(command, bindings) {
1803
2014
  if (!this.device.createBindGroup)
1804
2015
  return null;
@@ -2105,6 +2316,7 @@ function usesNativeDrawUniforms(layout) {
2105
2316
  }
2106
2317
  function usesNativePbrTextureBindings(layout) {
2107
2318
  return layout === "generated-pbr" ||
2319
+ layout === "generated-atlas-pbr" ||
2108
2320
  layout === "generated-textured-pbr" ||
2109
2321
  layout === "generated-instanced-pbr";
2110
2322
  }
@@ -2360,7 +2572,7 @@ function jointPaletteFor(uniforms) {
2360
2572
  const count = Math.min(MAX_WEBGPU_SKINNING_JOINTS, Math.floor(length / 16));
2361
2573
  return count > 0 ? uniformMat4Array(value, count) : [];
2362
2574
  }
2363
- function readVertexColor(bytes, stride, offset, vertexIndex) {
2575
+ function readVertexColor(bytes, stride, offset, vertexIndex, components = 4) {
2364
2576
  if (offset === undefined)
2365
2577
  return [1, 1, 1, 1];
2366
2578
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
@@ -2369,7 +2581,7 @@ function readVertexColor(bytes, stride, offset, vertexIndex) {
2369
2581
  view.getFloat32(base, true),
2370
2582
  view.getFloat32(base + 4, true),
2371
2583
  view.getFloat32(base + 8, true),
2372
- view.getFloat32(base + 12, true)
2584
+ components >= 4 ? view.getFloat32(base + 12, true) : 1
2373
2585
  ];
2374
2586
  }
2375
2587
  function readVertexUv(bytes, stride, offset, vertexIndex) {
@@ -2554,7 +2766,9 @@ function createNativeShaderSources(sources) {
2554
2766
  return { ...nativeInstancedPbrShader(vertexEntry, fragmentEntry, sources.marker), entryPoints: [vertexEntry, fragmentEntry], uniformLayout: "generated-instanced-pbr" };
2555
2767
  }
2556
2768
  if (sources.marker.includes("pbr-textured")) {
2557
- return { ...nativeTexturedPbrShader(vertexEntry, fragmentEntry, sources.marker, nativeShaderUsesTangent(sources.vertex)), entryPoints: [vertexEntry, fragmentEntry], uniformLayout: "generated-textured-pbr" };
2769
+ const atlas = /uniform\s+sampler2D\s+u_extensionScalarAtlas/.test(sources.fragment);
2770
+ const native = nativeTexturedPbrShader(vertexEntry, fragmentEntry, sources.marker, nativeShaderUsesTangent(sources.vertex), atlas);
2771
+ return { ...native, ...(atlas ? { atlasVertex: native.vertex } : {}), entryPoints: [vertexEntry, fragmentEntry], uniformLayout: atlas ? "generated-atlas-pbr" : "generated-textured-pbr" };
2558
2772
  }
2559
2773
  if (sources.marker.includes("pbr-direct")) {
2560
2774
  return sources.fragment.includes("u_baseColorTexture")
@@ -2568,20 +2782,89 @@ function createNativeShaderSources(sources) {
2568
2782
  return { ...nativeMorphUnlitShader(vertexEntry, fragmentEntry, sources.marker), entryPoints: [vertexEntry, fragmentEntry], uniformLayout: "generated-morph-unlit" };
2569
2783
  }
2570
2784
  if (/sampler2D/.test(sources.fragment) && /layout\s*\(\s*location\s*=\s*2\s*\)\s*in\s+vec2/.test(sources.vertex)) {
2785
+ const colorInput = /layout\s*\(\s*location\s*=\s*(\d+)\s*\)\s*in\s+(?:lowp\s+|mediump\s+|highp\s+)?vec([34])\s+(?:a_color|color)\s*;/.exec(sources.vertex);
2786
+ const colorParameter = colorInput ? `, @location(${colorInput[1]}) vertexColor: vec${colorInput[2]}<f32>` : "";
2787
+ const drawUniforms = `struct DrawUniforms {
2788
+ modelViewProjection: mat4x4<f32>,
2789
+ color: vec4<f32>,
2790
+ };
2791
+ @group(0) @binding(0) var<uniform> u_draw: DrawUniforms;`;
2792
+ const vertexOutput = `struct VertexOutput {
2793
+ @builtin(position) position: vec4<f32>,
2794
+ @location(0) uv: vec2<f32>,
2795
+ ${colorInput ? "@location(1) color: vec4<f32>," : ""}
2796
+ };`;
2797
+ const vertex = `// ${sources.marker}
2798
+ ${drawUniforms}
2799
+ ${vertexOutput}
2800
+ @vertex
2801
+ fn ${vertexEntry}(@location(0) position: vec3<f32>, @location(2) uv: vec2<f32>${colorParameter}) -> VertexOutput {
2802
+ var output: VertexOutput;
2803
+ let clipPosition = u_draw.modelViewProjection * vec4<f32>(position, 1.0);
2804
+ output.position = vec4<f32>(clipPosition.x, clipPosition.y, clipPosition.z * 0.5 + clipPosition.w * 0.5, clipPosition.w);
2805
+ output.uv = uv;
2806
+ ${colorInput ? `output.color = ${colorInput[2] === "3" ? "vec4<f32>(vertexColor, 1.0)" : "vertexColor"};` : ""}
2807
+ return output;
2808
+ }
2809
+ `;
2571
2810
  return {
2572
- vertex: `// ${sources.marker}\nstruct DrawUniforms {\n modelViewProjection: mat4x4<f32>,\n color: vec4<f32>,\n};\n\nstruct VertexOutput {\n @builtin(position) position: vec4<f32>,\n @location(0) uv: vec2<f32>,\n};\n\n@group(0) @binding(0) var<uniform> u_draw: DrawUniforms;\n\n@vertex\nfn ${vertexEntry}(@location(0) position: vec3<f32>, @location(2) uv: vec2<f32>) -> VertexOutput {\n var output: VertexOutput;\n let clipPosition = u_draw.modelViewProjection * vec4<f32>(position, 1.0);
2573
- output.position = vec4<f32>(clipPosition.x, clipPosition.y, clipPosition.z * 0.5 + clipPosition.w * 0.5, clipPosition.w);\n output.uv = uv;\n return output;\n}\n`,
2574
- fragment: `// ${sources.marker}\nstruct DrawUniforms {\n modelViewProjection: mat4x4<f32>,\n color: vec4<f32>,\n};\n\nstruct VertexOutput {\n @builtin(position) position: vec4<f32>,\n @location(0) uv: vec2<f32>,\n};\n\n@group(0) @binding(0) var<uniform> u_draw: DrawUniforms;\n@group(0) @binding(1) var u_textureSampler: sampler;\n@group(0) @binding(2) var u_texture: texture_2d<f32>;\n\n@fragment\nfn ${fragmentEntry}(input: VertexOutput) -> @location(0) vec4<f32> {\n return u_draw.color * textureSample(u_texture, u_textureSampler, input.uv);\n}\n`,
2811
+ vertex,
2812
+ colorDefaultVertex: colorInput ? vertex.replace(colorParameter, "").replace(/output\.color = [^;]+;/, "output.color = vec4<f32>(1.0);") : undefined,
2813
+ fragment: `// ${sources.marker}
2814
+ ${drawUniforms}
2815
+ ${vertexOutput}
2816
+ @group(0) @binding(1) var u_textureSampler: sampler;
2817
+ @group(0) @binding(2) var u_texture: texture_2d<f32>;
2818
+ @fragment
2819
+ fn ${fragmentEntry}(input: VertexOutput) -> @location(0) vec4<f32> {
2820
+ return u_draw.color * textureSample(u_texture, u_textureSampler, input.uv)${colorInput ? " * input.color" : ""};
2821
+ }
2822
+ `,
2575
2823
  entryPoints: [vertexEntry, fragmentEntry],
2576
2824
  uniformLayout: "generated-texture"
2577
2825
  };
2578
2826
  }
2827
+ // Basic GLSL programs (including unlit primitives and particle sprites)
2828
+ // declare their color input explicitly. Preserve that location and component
2829
+ // count; RGB inputs use opaque alpha, while RGBA alpha modulates the material.
2830
+ // Depth programs intentionally do not consume material/vertex color.
2831
+ const colorInput = sources.marker.includes(DEFAULT_DEPTH_SHADER_MARKER) ? null
2832
+ : /layout\s*\(\s*location\s*=\s*(\d+)\s*\)\s*in\s+(?:lowp\s+|mediump\s+|highp\s+)?vec([34])\s+(?:a_color|color)\s*;/.exec(sources.vertex);
2833
+ const colorParameter = colorInput ? `, @location(${colorInput[1]}) vertexColor: vec${colorInput[2]}<f32>` : "";
2834
+ const vertexOutput = `struct VertexOutput {
2835
+ @builtin(position) position: vec4<f32>,
2836
+ ${colorInput ? "@location(0) color: vec4<f32>," : ""}
2837
+ };`;
2838
+ const drawUniforms = `struct DrawUniforms {
2839
+ modelViewProjection: mat4x4<f32>,
2840
+ color: vec4<f32>,
2841
+ };
2842
+ @group(0) @binding(0) var<uniform> u_draw: DrawUniforms;`;
2843
+ const vertex = `// ${sources.marker}
2844
+ ${drawUniforms}
2845
+ ${vertexOutput}
2846
+ @vertex
2847
+ fn ${vertexEntry}(@location(0) position: vec3<f32>${colorParameter}) -> VertexOutput {
2848
+ var output: VertexOutput;
2849
+ let clipPosition = u_draw.modelViewProjection * vec4<f32>(position, 1.0);
2850
+ output.position = vec4<f32>(clipPosition.x, clipPosition.y, clipPosition.z * 0.5 + clipPosition.w * 0.5, clipPosition.w);
2851
+ ${colorInput ? `output.color = ${colorInput[2] === "3" ? "vec4<f32>(vertexColor, 1.0)" : "vertexColor"};` : ""}
2852
+ return output;
2853
+ }
2854
+ `;
2579
2855
  return {
2580
- vertex: `// ${sources.marker}\nstruct DrawUniforms {\n modelViewProjection: mat4x4<f32>,\n color: vec4<f32>,\n};\n\nstruct VertexOutput {\n @builtin(position) position: vec4<f32>,\n};\n\n@group(0) @binding(0) var<uniform> u_draw: DrawUniforms;\n\n@vertex\nfn ${vertexEntry}(@location(0) position: vec3<f32>) -> VertexOutput {\n var output: VertexOutput;\n let clipPosition = u_draw.modelViewProjection * vec4<f32>(position, 1.0);
2581
- output.position = vec4<f32>(clipPosition.x, clipPosition.y, clipPosition.z * 0.5 + clipPosition.w * 0.5, clipPosition.w);\n return output;\n}\n`,
2856
+ vertex,
2857
+ colorDefaultVertex: colorInput ? vertex.replace(colorParameter, "").replace(/output\.color = [^;]+;/, "output.color = vec4<f32>(1.0);") : undefined,
2582
2858
  fragment: sources.marker.includes(DEFAULT_DEPTH_SHADER_MARKER)
2583
2859
  ? nativeDepthFragment(sources.marker, fragmentEntry)
2584
- : `// ${sources.marker}\nstruct DrawUniforms {\n modelViewProjection: mat4x4<f32>,\n color: vec4<f32>,\n};\n\n@group(0) @binding(0) var<uniform> u_draw: DrawUniforms;\n\n@fragment\nfn ${fragmentEntry}() -> @location(0) vec4<f32> {\n return u_draw.color;\n}\n`,
2860
+ : `// ${sources.marker}
2861
+ ${drawUniforms}
2862
+ ${vertexOutput}
2863
+ @fragment
2864
+ fn ${fragmentEntry}(input: VertexOutput) -> @location(0) vec4<f32> {
2865
+ return u_draw.color${colorInput ? " * input.color" : ""};
2866
+ }
2867
+ `,
2585
2868
  entryPoints: [vertexEntry, fragmentEntry],
2586
2869
  uniformLayout: "generated-basic"
2587
2870
  };
@@ -2725,7 +3008,7 @@ fn a3dWebGPUClipPosition(clipPosition: vec4<f32>) -> vec4<f32> {
2725
3008
  function nativeShaderUsesTangent(vertexSource) {
2726
3009
  return /\ba_tangent\b/.test(vertexSource) || /layout\s*\(\s*location\s*=\s*3\s*\)\s*in\s+vec4/.test(vertexSource);
2727
3010
  }
2728
- function nativePbrFragmentPrelude(marker) {
3011
+ function nativePbrFragmentPrelude(marker, atlas = false) {
2729
3012
  return `// ${marker}
2730
3013
  ${nativeUniformStruct()}
2731
3014
  @group(0) @binding(1) var u_baseSampler: sampler;
@@ -2747,12 +3030,15 @@ ${nativeUniformStruct()}
2747
3030
  @group(0) @binding(17) var u_spotShadowSampler: sampler;
2748
3031
  @group(0) @binding(18) var u_spotShadowTexture: texture_2d<f32>;
2749
3032
 
3033
+ ${atlas ? WEBGPU_EXTENSION_ATLAS_WGSL : ""}
3034
+
2750
3035
  struct VertexOutput {
2751
3036
  @builtin(position) position: vec4<f32>,
2752
3037
  @location(0) normal: vec3<f32>,
2753
3038
  @location(1) uv: vec2<f32>,
2754
3039
  @location(2) worldPosition: vec3<f32>,
2755
3040
  @location(3) tangent: vec4<f32>,
3041
+ ${atlas ? "@location(4) uv1: vec2<f32>, @location(5) color: vec4<f32>," : ""}
2756
3042
  };
2757
3043
 
2758
3044
  fn fresnelSchlick(cosTheta: f32, f0: vec3<f32>) -> vec3<f32> {
@@ -2765,14 +3051,16 @@ fn fresnelSchlickRoughness(cosTheta: f32, f0: vec3<f32>, roughness: f32) -> vec3
2765
3051
  }
2766
3052
 
2767
3053
  fn ggxDistribution(nDotH: f32, roughness: f32) -> f32 {
2768
- let a = max(roughness * roughness, 0.045);
3054
+ let a = ${atlas ? "max(roughness, 0.045) * max(roughness, 0.045)" : "max(roughness * roughness, 0.045)"};
2769
3055
  let a2 = a * a;
2770
- let denom = max((nDotH * nDotH) * (a2 - 1.0) + 1.0, 0.001);
2771
- return a2 / max(3.14159265 * denom * denom, 0.001);
3056
+ // At roughness .045 the true denominator before squaring is ~4.1e-6.
3057
+ // A .001/.00001 floor destroys the normalized low-roughness GGX lobe.
3058
+ let denom = max((nDotH * nDotH) * (a2 - 1.0) + 1.0, ${atlas ? "0.00000001" : "0.001"});
3059
+ return a2 / max(3.14159265 * denom * denom, ${atlas ? "0.0000000000000001" : "0.001"});
2772
3060
  }
2773
3061
 
2774
3062
  fn ggxVisibilitySmithCorrelated(nDotV: f32, nDotL: f32, roughness: f32) -> f32 {
2775
- let a = max(roughness * roughness, 0.045);
3063
+ let a = ${atlas ? "max(roughness, 0.045) * max(roughness, 0.045)" : "max(roughness * roughness, 0.045)"};
2776
3064
  let a2 = a * a;
2777
3065
  let lambdaV = nDotL * sqrt(max((nDotV - a2 * nDotV) * nDotV + a2, 0.00001));
2778
3066
  let lambdaL = nDotV * sqrt(max((nDotL - a2 * nDotL) * nDotL + a2, 0.00001));
@@ -2898,18 +3186,18 @@ fn a3dNativeShadowFactor(worldPosition: vec3<f32>, normal: vec3<f32>, lightDirec
2898
3186
  return mix(1.0, 1.0 - occlusion, clamp(strength, 0.0, 1.0));
2899
3187
  }
2900
3188
 
2901
- fn shadePbr(normalInput: vec3<f32>, tangentFrame: vec4<f32>, uv: vec2<f32>, worldPosition: vec3<f32>, fragmentPosition: vec2<f32>) -> vec4<f32> {
3189
+ fn shadePbr(normalInput: vec3<f32>, tangentFrame: vec4<f32>, uv: vec2<f32>, worldPosition: vec3<f32>, fragmentPosition: vec2<f32>${atlas ? ", uv1: vec2<f32>, vertexColor: vec4<f32>" : ""}) -> vec4<f32> {
2902
3190
  var normal = normalize(normalInput);
2903
3191
  if (u_draw.morph0.x > 0.5) {
2904
- normal = perturbNormal(normal, tangentFrame, textureSample(u_normalTexture, u_normalSampler, uv).rgb, u_draw.morph0.y);
3192
+ normal = perturbNormal(normal, tangentFrame, textureSample(u_normalTexture, u_normalSampler, ${atlas ? "atlasUv(1u, uv, uv1)" : "uv"}).rgb, u_draw.morph0.y);
2905
3193
  }
2906
3194
  let viewDirection = normalize(u_draw.camera.xyz - worldPosition);
2907
3195
  let lightDirection = normalize(vec3<f32>(0.36, 0.52, 0.78));
2908
3196
  let halfVector = normalize(lightDirection + viewDirection);
2909
- var baseColor = u_draw.color.rgb;
2910
- var materialAlpha = u_draw.color.a;
3197
+ var baseColor = u_draw.color.rgb${atlas ? " * vertexColor.rgb" : ""};
3198
+ var materialAlpha = u_draw.color.a${atlas ? " * vertexColor.a" : ""};
2911
3199
  if (u_draw.flags.x > 0.5) {
2912
- let baseSample = textureSample(u_baseTexture, u_baseSampler, uv);
3200
+ let baseSample = textureSample(u_baseTexture, u_baseSampler, ${atlas ? "atlasUv(0u, uv, uv1)" : "uv"});
2913
3201
  baseColor = baseColor * baseSample.rgb;
2914
3202
  materialAlpha = materialAlpha * baseSample.a;
2915
3203
  }
@@ -2918,16 +3206,14 @@ fn shadePbr(normalInput: vec3<f32>, tangentFrame: vec4<f32>, uv: vec2<f32>, worl
2918
3206
  let productBodyGate = productPropBodyGate(sourceProductBaseColor, u_draw.materialFlags.z) * (1.0 - productOrangeGate * 0.92);
2919
3207
  let productSurfaceGate = clamp(productBodyGate + productOrangeGate * 0.72, 0.0, 1.0);
2920
3208
  baseColor = productPropAlbedo(sourceProductBaseColor, u_draw.materialFlags.z);
2921
- if (materialAlpha < u_draw.material.x) {
2922
- discard;
2923
- }
3209
+ ${atlas ? "" : "if (materialAlpha < u_draw.material.x) { discard; }"}
2924
3210
  let transmission = clamp(max(u_draw.material.y, u_draw.material.z), 0.0, 1.0);
2925
- let metallicRoughnessSample = textureSample(u_metallicRoughnessTexture, u_metallicRoughnessSampler, uv);
3211
+ let metallicRoughnessSample = textureSample(u_metallicRoughnessTexture, u_metallicRoughnessSampler, ${atlas ? "atlasUv(2u, uv, uv1)" : "uv"});
2926
3212
  let metallicRoughnessEnabled = step(0.5, u_draw.materialFlags.x);
2927
3213
  let occlusionEnabled = step(0.5, u_draw.materialFlags.y);
2928
3214
  let sampledMetallic = clamp(u_draw.params.x * metallicRoughnessSample.b, 0.0, 1.0);
2929
3215
  let sampledRoughness = clamp(u_draw.params.y * metallicRoughnessSample.g, 0.045, 1.0);
2930
- let sampledOcclusion = mix(1.0, textureSample(u_occlusionTexture, u_occlusionSampler, uv).r, clamp(u_draw.morph0.z, 0.0, 1.0));
3216
+ let sampledOcclusion = mix(1.0, textureSample(u_occlusionTexture, u_occlusionSampler, ${atlas ? "atlasUv(3u, uv, uv1)" : "uv"}).r, clamp(u_draw.morph0.z, 0.0, 1.0));
2931
3217
  let metallic = mix(clamp(u_draw.params.x, 0.0, 1.0), sampledMetallic, metallicRoughnessEnabled);
2932
3218
  let roughness = mix(clamp(u_draw.params.y, 0.045, 1.0), sampledRoughness, metallicRoughnessEnabled);
2933
3219
  let occlusion = mix(1.0, sampledOcclusion, occlusionEnabled);
@@ -2937,16 +3223,23 @@ fn shadePbr(normalInput: vec3<f32>, tangentFrame: vec4<f32>, uv: vec2<f32>, worl
2937
3223
  let nDotV = max(dot(normal, viewDirection), 0.001);
2938
3224
  let nDotH = max(dot(normal, halfVector), 0.001);
2939
3225
  let vDotH = max(dot(viewDirection, halfVector), 0.001);
2940
- let f0 = mix(vec3<f32>(0.04, 0.04, 0.04), baseColor, metallic);
3226
+ let dielectricF0 = ${atlas ? "vec3<f32>(pow((u_atlas.substrate.x-1.0)/(u_atlas.substrate.x+1.0),2.0)) * clamp(u_atlas.specularColor.rgb,vec3<f32>(0.0),vec3<f32>(1.0)) * clamp(u_atlas.substrate.y,0.0,1.0)" : "vec3<f32>(0.04, 0.04, 0.04)"};
3227
+ let f0 = mix(dielectricF0, baseColor, metallic);
2941
3228
  let fresnel = fresnelSchlick(vDotH, f0);
2942
3229
  let distribution = ggxDistribution(nDotH, roughness);
2943
3230
  let visibility = ggxVisibilitySmithCorrelated(nDotV, nDotL, roughness);
2944
3231
  let lDotH = max(dot(lightDirection, halfVector), 0.0);
2945
- let kd = (vec3<f32>(1.0, 1.0, 1.0) - fresnel) * (1.0 - metallic);
3232
+ ${atlas ? `// The film replaces the substrate Fresnel in the dielectric energy split.
3233
+ // Retaining the old kd while adding film specular creates or loses energy.
3234
+ let filmFresnel = atlasIridescenceFresnel(f0, vDotH, uv, uv1);
3235
+ let filmMaximum = max(max(filmFresnel.r, filmFresnel.g), filmFresnel.b);
3236
+ let diffuseFresnel = select(fresnel, vec3<f32>(filmMaximum), u_atlas.iridescence.y > 0.0);
3237
+ let kd = (vec3<f32>(1.0) - diffuseFresnel) * (1.0 - metallic);` : "let kd = (vec3<f32>(1.0, 1.0, 1.0) - fresnel) * (1.0 - metallic);"}
2946
3238
  let specular = fresnel * distribution * visibility;
2947
3239
  let diffuse = kd * baseColor * diffuseBurley(nDotV, nDotL, lDotH, roughness) / 3.14159265;
2948
3240
  var environment = baseColor * u_draw.params.z * (0.28 + 0.72 * clamp(normal.y * 0.5 + 0.5, 0.0, 1.0)) * occlusion;
2949
3241
  var environmentSpecularContribution = vec3<f32>(0.0, 0.0, 0.0);
3242
+ var environmentExtensionRadiance = vec3<f32>(0.0);
2950
3243
  if (u_draw.flags.w > 0.5) {
2951
3244
  let reflectionDirection = reflect(-viewDirection, normal);
2952
3245
  let diffuseUv = vec2<f32>(fract(atan2(normal.z, normal.x) / 6.2831853 + 0.5), acos(clamp(normal.y, -1.0, 1.0)) / 3.14159265);
@@ -2957,8 +3250,10 @@ fn shadePbr(normalInput: vec3<f32>, tangentFrame: vec4<f32>, uv: vec2<f32>, worl
2957
3250
  let brdf = textureSampleLevel(u_brdfTexture, u_brdfSampler, vec2<f32>(nDotV, roughness), 0.0).rg;
2958
3251
  let environmentFresnel = fresnelSchlickRoughness(nDotV, f0, roughness);
2959
3252
  let environmentDiffuse = (vec3<f32>(1.0, 1.0, 1.0) - environmentFresnel) * (1.0 - metallic) * diffuseEnv * baseColor * u_draw.params.w * occlusion;
2960
- let environmentSpecular = specularEnv * (f0 * brdf.x + vec3<f32>(brdf.y, brdf.y, brdf.y)) * max(u_draw.reserved0.w, 0.0);
3253
+ ${atlas ? "let environmentF0 = atlasIridescenceEnvironmentF0(f0, nDotV, uv, uv1);" : "let environmentF0 = f0;"}
3254
+ let environmentSpecular = specularEnv * (environmentF0 * brdf.x + vec3<f32>(brdf.y, brdf.y, brdf.y)) * max(u_draw.reserved0.w, 0.0);
2961
3255
  environmentSpecularContribution = environmentSpecular;
3256
+ environmentExtensionRadiance = specularEnv * max(u_draw.reserved0.w, 0.0);
2962
3257
  environment = environment + environmentDiffuse + environmentSpecular;
2963
3258
  }
2964
3259
  // J2 native shadow projections: directional PCF mirrors GLSL
@@ -2998,7 +3293,8 @@ fn shadePbr(normalInput: vec3<f32>, tangentFrame: vec4<f32>, uv: vec2<f32>, worl
2998
3293
  }
2999
3294
  }
3000
3295
  let legacyDirect = (diffuse + specular) * nDotL * 2.25 * shadow;
3001
- let litOpaqueLinearColor = environment + select(legacyDirect, clusteredDirect, u_draw.materialFlags.w > 0.5);
3296
+ let litOpaqueLinearColor = environment + select(legacyDirect, clusteredDirect, u_draw.materialFlags.w > 0.5)
3297
+ ${atlas ? "+ atlasExtensionLighting(normal, tangentFrame, viewDirection, lightDirection, uv, uv1, shadow, environmentExtensionRadiance, f0, roughness) + u_atlas.emissive.rgb * u_atlas.factors.y * mix(vec3<f32>(1.0), textureSample(u_emissiveTexture, u_emissiveSampler, atlasUv(4u, uv, uv1)).rgb, u_atlas.maps[4].control.z)" : ""};
3002
3298
  let smoothedBodyColor = mix(baseColor, vec3<f32>(1.0, 0.88, 0.012), productBodyGate * 0.32);
3003
3299
  let smoothedBeakColor = mix(baseColor, vec3<f32>(1.0, 0.24, 0.018), productOrangeGate * 0.82);
3004
3300
  let softBodyProduct = smoothedBodyColor * (1.5 + 0.06 * nDotL) + specular * nDotL * 0.018;
@@ -3008,6 +3304,7 @@ fn shadePbr(normalInput: vec3<f32>, tangentFrame: vec4<f32>, uv: vec2<f32>, worl
3008
3304
  let transmittedTint = baseColor * u_draw.params.w * (0.18 + 0.42 * clamp(1.0 - roughness, 0.0, 1.0)) + environmentSpecularContribution * 1.35;
3009
3305
  let linearColor = mix(opaqueLinearColor, opaqueLinearColor * 0.22 + transmittedTint, transmission);
3010
3306
  let outputAlpha = mix(materialAlpha, min(materialAlpha, 0.22), transmission);
3307
+ ${atlas ? "if (materialAlpha < u_draw.material.x) { discard; }" : ""}
3011
3308
  return vec4<f32>(encodePbrOutput(linearColor), outputAlpha);
3012
3309
  }
3013
3310
  `;
@@ -3044,7 +3341,7 @@ fn ${fragmentEntry}(input: VertexOutput) -> @location(0) vec4<f32> {
3044
3341
  `
3045
3342
  };
3046
3343
  }
3047
- function nativeTexturedPbrShader(vertexEntry, fragmentEntry, marker, usesTangent) {
3344
+ function nativeTexturedPbrShader(vertexEntry, fragmentEntry, marker, usesTangent, atlas = false) {
3048
3345
  return {
3049
3346
  vertex: `// ${marker}
3050
3347
  ${nativeUniformStruct()}
@@ -3054,24 +3351,26 @@ struct VertexOutput {
3054
3351
  @location(1) uv: vec2<f32>,
3055
3352
  @location(2) worldPosition: vec3<f32>,
3056
3353
  @location(3) tangent: vec4<f32>,
3354
+ ${atlas ? "@location(4) uv1: vec2<f32>, @location(5) color: vec4<f32>," : ""}
3057
3355
  };
3058
3356
 
3059
3357
  @vertex
3060
- fn ${vertexEntry}(@location(0) position: vec3<f32>, @location(1) normal: vec3<f32>, @location(2) uv: vec2<f32>${usesTangent ? ", @location(3) tangent: vec4<f32>" : ""}) -> VertexOutput {
3358
+ fn ${vertexEntry}(@location(0) position: vec3<f32>, @location(1) normal: vec3<f32>, @location(2) uv: vec2<f32>${usesTangent ? ", @location(3) tangent: vec4<f32>" : ""}${atlas ? ", @location(4) color: vec4<f32>, @location(7) uv1: vec2<f32>" : ""}) -> VertexOutput {
3061
3359
  var output: VertexOutput;
3062
3360
  let worldPosition = (u_draw.instance0 * vec4<f32>(position, 1.0)).xyz;
3063
3361
  output.position = a3dWebGPUClipPosition(u_draw.modelViewProjection * vec4<f32>(position, 1.0));
3064
3362
  output.normal = normalize((u_draw.normalMatrix * vec4<f32>(normal, 0.0)).xyz);
3065
3363
  output.uv = uv;
3364
+ ${atlas ? "output.uv1 = uv1; output.color = color;" : ""}
3066
3365
  output.worldPosition = worldPosition;
3067
3366
  ${usesTangent ? "output.tangent = vec4<f32>(normalize((u_draw.normalMatrix * vec4<f32>(tangent.xyz, 0.0)).xyz), tangent.w);" : "output.tangent = vec4<f32>(1.0, 0.0, 0.0, 1.0);"}
3068
3367
  return output;
3069
3368
  }
3070
3369
  `,
3071
- fragment: `${nativePbrFragmentPrelude(marker)}
3370
+ fragment: `${nativePbrFragmentPrelude(marker, atlas)}
3072
3371
  @fragment
3073
3372
  fn ${fragmentEntry}(input: VertexOutput) -> @location(0) vec4<f32> {
3074
- return shadePbr(input.normal, input.tangent, input.uv, input.worldPosition, input.position.xy);
3373
+ return shadePbr(input.normal, input.tangent, input.uv, input.worldPosition, input.position.xy${atlas ? ", input.uv1, input.color" : ""});
3075
3374
  }
3076
3375
  `
3077
3376
  };