@aura3d/rendering 2.0.4 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) 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/ClusteredForwardLighting.d.ts +25 -1
  6. package/dist/ClusteredForwardLighting.d.ts.map +1 -1
  7. package/dist/ClusteredForwardLighting.js +80 -3
  8. package/dist/ClusteredForwardLighting.js.map +1 -1
  9. package/dist/DayNightSky.d.ts +63 -0
  10. package/dist/DayNightSky.d.ts.map +1 -0
  11. package/dist/DayNightSky.js +156 -0
  12. package/dist/DayNightSky.js.map +1 -0
  13. package/dist/EnvironmentPresetPack.d.ts +30 -0
  14. package/dist/EnvironmentPresetPack.d.ts.map +1 -0
  15. package/dist/EnvironmentPresetPack.js +49 -0
  16. package/dist/EnvironmentPresetPack.js.map +1 -0
  17. package/dist/ForwardPass.d.ts +65 -0
  18. package/dist/ForwardPass.d.ts.map +1 -1
  19. package/dist/ForwardPass.js +196 -2
  20. package/dist/ForwardPass.js.map +1 -1
  21. package/dist/InstancedPBRMaterial.d.ts +1 -0
  22. package/dist/InstancedPBRMaterial.d.ts.map +1 -1
  23. package/dist/InstancedPBRMaterial.js +3 -0
  24. package/dist/InstancedPBRMaterial.js.map +1 -1
  25. package/dist/InstancingDiagnostics.d.ts +69 -0
  26. package/dist/InstancingDiagnostics.d.ts.map +1 -0
  27. package/dist/InstancingDiagnostics.js +98 -0
  28. package/dist/InstancingDiagnostics.js.map +1 -0
  29. package/dist/LightingRig.d.ts +19 -1
  30. package/dist/LightingRig.d.ts.map +1 -1
  31. package/dist/LightingRig.js +61 -1
  32. package/dist/LightingRig.js.map +1 -1
  33. package/dist/MeshConsolidation.d.ts +12 -0
  34. package/dist/MeshConsolidation.d.ts.map +1 -1
  35. package/dist/MeshConsolidation.js +28 -0
  36. package/dist/MeshConsolidation.js.map +1 -1
  37. package/dist/MorphTargetPlan.d.ts +29 -2
  38. package/dist/MorphTargetPlan.d.ts.map +1 -1
  39. package/dist/MorphTargetPlan.js +23 -4
  40. package/dist/MorphTargetPlan.js.map +1 -1
  41. package/dist/NormalMappedPBRMaterial.d.ts.map +1 -1
  42. package/dist/NormalMappedPBRMaterial.js +22 -2
  43. package/dist/NormalMappedPBRMaterial.js.map +1 -1
  44. package/dist/OceanSurface.d.ts +75 -0
  45. package/dist/OceanSurface.d.ts.map +1 -1
  46. package/dist/OceanSurface.js +211 -0
  47. package/dist/OceanSurface.js.map +1 -1
  48. package/dist/PBRMaterial.d.ts +1 -0
  49. package/dist/PBRMaterial.d.ts.map +1 -1
  50. package/dist/PBRMaterial.js +23 -2
  51. package/dist/PBRMaterial.js.map +1 -1
  52. package/dist/PbrReference.d.ts +48 -0
  53. package/dist/PbrReference.d.ts.map +1 -1
  54. package/dist/PbrReference.js +126 -0
  55. package/dist/PbrReference.js.map +1 -1
  56. package/dist/PlanarReflection.d.ts +221 -0
  57. package/dist/PlanarReflection.d.ts.map +1 -0
  58. package/dist/PlanarReflection.js +541 -0
  59. package/dist/PlanarReflection.js.map +1 -0
  60. package/dist/PostProcessPass.d.ts +13 -0
  61. package/dist/PostProcessPass.d.ts.map +1 -1
  62. package/dist/PostProcessPass.js +15 -4
  63. package/dist/PostProcessPass.js.map +1 -1
  64. package/dist/ReflectionSurfaces.d.ts +14 -0
  65. package/dist/ReflectionSurfaces.d.ts.map +1 -1
  66. package/dist/ReflectionSurfaces.js +93 -0
  67. package/dist/ReflectionSurfaces.js.map +1 -1
  68. package/dist/RenderDevice.d.ts +87 -0
  69. package/dist/RenderDevice.d.ts.map +1 -1
  70. package/dist/RenderDevice.js +85 -0
  71. package/dist/RenderDevice.js.map +1 -1
  72. package/dist/Renderer.js +3 -1
  73. package/dist/Renderer.js.map +1 -1
  74. package/dist/RendererPostprocessPlan.d.ts +48 -0
  75. package/dist/RendererPostprocessPlan.d.ts.map +1 -1
  76. package/dist/RendererPostprocessPlan.js +101 -11
  77. package/dist/RendererPostprocessPlan.js.map +1 -1
  78. package/dist/RendererVisualPipelineReport.d.ts.map +1 -1
  79. package/dist/RendererVisualPipelineReport.js +3 -1
  80. package/dist/RendererVisualPipelineReport.js.map +1 -1
  81. package/dist/Sampler.d.ts +29 -0
  82. package/dist/Sampler.d.ts.map +1 -1
  83. package/dist/Sampler.js +47 -0
  84. package/dist/Sampler.js.map +1 -1
  85. package/dist/SdfText.d.ts +250 -0
  86. package/dist/SdfText.d.ts.map +1 -0
  87. package/dist/SdfText.js +551 -0
  88. package/dist/SdfText.js.map +1 -0
  89. package/dist/ShaderChunks.d.ts.map +1 -1
  90. package/dist/ShaderChunks.js +30 -12
  91. package/dist/ShaderChunks.js.map +1 -1
  92. package/dist/ShaderLibrary.d.ts.map +1 -1
  93. package/dist/ShaderLibrary.js +478 -34
  94. package/dist/ShaderLibrary.js.map +1 -1
  95. package/dist/ShaderLibraryCore.d.ts.map +1 -1
  96. package/dist/ShaderLibraryCore.js +70 -12
  97. package/dist/ShaderLibraryCore.js.map +1 -1
  98. package/dist/ShadowMap.d.ts +24 -0
  99. package/dist/ShadowMap.d.ts.map +1 -1
  100. package/dist/ShadowMap.js +43 -0
  101. package/dist/ShadowMap.js.map +1 -1
  102. package/dist/SkinnedLitMaterial.d.ts.map +1 -1
  103. package/dist/SkinnedLitMaterial.js +2 -0
  104. package/dist/SkinnedLitMaterial.js.map +1 -1
  105. package/dist/SkinningBounds.d.ts +10 -0
  106. package/dist/SkinningBounds.d.ts.map +1 -1
  107. package/dist/SkinningBounds.js +27 -0
  108. package/dist/SkinningBounds.js.map +1 -1
  109. package/dist/SpriteFlipbook.d.ts +60 -0
  110. package/dist/SpriteFlipbook.d.ts.map +1 -0
  111. package/dist/SpriteFlipbook.js +128 -0
  112. package/dist/SpriteFlipbook.js.map +1 -0
  113. package/dist/TerrainTiles.d.ts +120 -0
  114. package/dist/TerrainTiles.d.ts.map +1 -0
  115. package/dist/TerrainTiles.js +221 -0
  116. package/dist/TerrainTiles.js.map +1 -0
  117. package/dist/TexturedPBRMaterial.d.ts +1 -0
  118. package/dist/TexturedPBRMaterial.d.ts.map +1 -1
  119. package/dist/TexturedPBRMaterial.js +23 -2
  120. package/dist/TexturedPBRMaterial.js.map +1 -1
  121. package/dist/TexturedUnlitMaterial.d.ts.map +1 -1
  122. package/dist/TexturedUnlitMaterial.js +20 -2
  123. package/dist/TexturedUnlitMaterial.js.map +1 -1
  124. package/dist/UnlitMaterial.d.ts.map +1 -1
  125. package/dist/UnlitMaterial.js +20 -2
  126. package/dist/UnlitMaterial.js.map +1 -1
  127. package/dist/VertexFormat.d.ts +6 -0
  128. package/dist/VertexFormat.d.ts.map +1 -1
  129. package/dist/VertexFormat.js +12 -0
  130. package/dist/VertexFormat.js.map +1 -1
  131. package/dist/VolumetricFog.d.ts +55 -0
  132. package/dist/VolumetricFog.d.ts.map +1 -0
  133. package/dist/VolumetricFog.js +91 -0
  134. package/dist/VolumetricFog.js.map +1 -0
  135. package/dist/WaterSurface.d.ts +77 -0
  136. package/dist/WaterSurface.d.ts.map +1 -0
  137. package/dist/WaterSurface.js +133 -0
  138. package/dist/WaterSurface.js.map +1 -0
  139. package/dist/WebGL2Device.d.ts +58 -0
  140. package/dist/WebGL2Device.d.ts.map +1 -1
  141. package/dist/WebGL2Device.js +536 -17
  142. package/dist/WebGL2Device.js.map +1 -1
  143. package/dist/WebGPUDevice.d.ts +50 -0
  144. package/dist/WebGPUDevice.d.ts.map +1 -1
  145. package/dist/WebGPUDevice.js +504 -10
  146. package/dist/WebGPUDevice.js.map +1 -1
  147. package/dist/WebGPUSkinningLimits.d.ts +40 -0
  148. package/dist/WebGPUSkinningLimits.d.ts.map +1 -1
  149. package/dist/WebGPUSkinningLimits.js +32 -0
  150. package/dist/WebGPUSkinningLimits.js.map +1 -1
  151. package/dist/effects/CollisionModule.d.ts +9 -0
  152. package/dist/effects/CollisionModule.d.ts.map +1 -1
  153. package/dist/effects/CollisionModule.js +10 -0
  154. package/dist/effects/CollisionModule.js.map +1 -1
  155. package/dist/effects/ColorModule.d.ts +7 -0
  156. package/dist/effects/ColorModule.d.ts.map +1 -1
  157. package/dist/effects/ColorModule.js +23 -0
  158. package/dist/effects/ColorModule.js.map +1 -1
  159. package/dist/effects/ForceModule.d.ts +35 -0
  160. package/dist/effects/ForceModule.d.ts.map +1 -1
  161. package/dist/effects/ForceModule.js +49 -0
  162. package/dist/effects/ForceModule.js.map +1 -1
  163. package/dist/effects/GPUParticleBackend.d.ts +186 -0
  164. package/dist/effects/GPUParticleBackend.d.ts.map +1 -1
  165. package/dist/effects/GPUParticleBackend.js +692 -1
  166. package/dist/effects/GPUParticleBackend.js.map +1 -1
  167. package/dist/effects/HeightfieldModule.d.ts +58 -0
  168. package/dist/effects/HeightfieldModule.d.ts.map +1 -0
  169. package/dist/effects/HeightfieldModule.js +129 -0
  170. package/dist/effects/HeightfieldModule.js.map +1 -0
  171. package/dist/effects/LightingModule.d.ts +34 -0
  172. package/dist/effects/LightingModule.d.ts.map +1 -0
  173. package/dist/effects/LightingModule.js +54 -0
  174. package/dist/effects/LightingModule.js.map +1 -0
  175. package/dist/effects/ParticleDiagnostics.d.ts +8 -0
  176. package/dist/effects/ParticleDiagnostics.d.ts.map +1 -1
  177. package/dist/effects/ParticleDiagnostics.js +3 -0
  178. package/dist/effects/ParticleDiagnostics.js.map +1 -1
  179. package/dist/effects/ParticleModule.d.ts +8 -0
  180. package/dist/effects/ParticleModule.d.ts.map +1 -1
  181. package/dist/effects/ParticleModule.js.map +1 -1
  182. package/dist/effects/ParticleRenderer.d.ts +20 -0
  183. package/dist/effects/ParticleRenderer.d.ts.map +1 -1
  184. package/dist/effects/ParticleRenderer.js +57 -7
  185. package/dist/effects/ParticleRenderer.js.map +1 -1
  186. package/dist/effects/ParticleSystem.d.ts +45 -1
  187. package/dist/effects/ParticleSystem.d.ts.map +1 -1
  188. package/dist/effects/ParticleSystem.js +373 -4
  189. package/dist/effects/ParticleSystem.js.map +1 -1
  190. package/dist/effects/SizeModule.d.ts +4 -0
  191. package/dist/effects/SizeModule.d.ts.map +1 -1
  192. package/dist/effects/SizeModule.js +16 -0
  193. package/dist/effects/SizeModule.js.map +1 -1
  194. package/dist/effects/SubEmitterModule.d.ts +61 -0
  195. package/dist/effects/SubEmitterModule.d.ts.map +1 -0
  196. package/dist/effects/SubEmitterModule.js +119 -0
  197. package/dist/effects/SubEmitterModule.js.map +1 -0
  198. package/dist/effects/TrailModule.d.ts +38 -0
  199. package/dist/effects/TrailModule.d.ts.map +1 -1
  200. package/dist/effects/TrailModule.js +95 -2
  201. package/dist/effects/TrailModule.js.map +1 -1
  202. package/dist/effects/TurbulenceModule.d.ts +43 -0
  203. package/dist/effects/TurbulenceModule.d.ts.map +1 -0
  204. package/dist/effects/TurbulenceModule.js +130 -0
  205. package/dist/effects/TurbulenceModule.js.map +1 -0
  206. package/dist/effects/index.d.ts +4 -0
  207. package/dist/effects/index.d.ts.map +1 -1
  208. package/dist/effects/index.js +4 -0
  209. package/dist/effects/index.js.map +1 -1
  210. package/dist/index.d.ts +57 -19
  211. package/dist/index.d.ts.map +1 -1
  212. package/dist/index.js +31 -10
  213. package/dist/index.js.map +1 -1
  214. package/dist/materials/MaterialExtensions.js +3 -3
  215. package/dist/materials/MaterialExtensions.js.map +1 -1
  216. package/dist/postprocess/NativeBloomPyramid.d.ts +28 -0
  217. package/dist/postprocess/NativeBloomPyramid.d.ts.map +1 -0
  218. package/dist/postprocess/NativeBloomPyramid.js +54 -0
  219. package/dist/postprocess/NativeBloomPyramid.js.map +1 -0
  220. package/dist/postprocess/NativeLdrEffectLuts.d.ts +10 -1
  221. package/dist/postprocess/NativeLdrEffectLuts.d.ts.map +1 -1
  222. package/dist/postprocess/NativeLdrEffectLuts.js +19 -2
  223. package/dist/postprocess/NativeLdrEffectLuts.js.map +1 -1
  224. package/dist/postprocess/index.d.ts +2 -0
  225. package/dist/postprocess/index.d.ts.map +1 -1
  226. package/dist/postprocess/index.js +1 -0
  227. package/dist/postprocess/index.js.map +1 -1
  228. package/dist/production-runtime/PBRHDRPipeline.d.ts +22 -0
  229. package/dist/production-runtime/PBRHDRPipeline.d.ts.map +1 -1
  230. package/dist/production-runtime/PBRHDRPipeline.js +125 -0
  231. package/dist/production-runtime/PBRHDRPipeline.js.map +1 -1
  232. package/dist/production-runtime/ProductionWebGPURenderer.d.ts +52 -0
  233. package/dist/production-runtime/ProductionWebGPURenderer.d.ts.map +1 -1
  234. package/dist/production-runtime/ProductionWebGPURenderer.js +101 -0
  235. package/dist/production-runtime/ProductionWebGPURenderer.js.map +1 -1
  236. package/dist/production-runtime/index.d.ts +5 -4
  237. package/dist/production-runtime/index.d.ts.map +1 -1
  238. package/dist/production-runtime/index.js +3 -2
  239. package/dist/production-runtime/index.js.map +1 -1
  240. package/dist/production-runtime/passes/DepthPrepass.d.ts +26 -1
  241. package/dist/production-runtime/passes/DepthPrepass.d.ts.map +1 -1
  242. package/dist/production-runtime/passes/DepthPrepass.js +49 -2
  243. package/dist/production-runtime/passes/DepthPrepass.js.map +1 -1
  244. package/dist/production-runtime/passes/FramegraphTopology.d.ts +13 -0
  245. package/dist/production-runtime/passes/FramegraphTopology.d.ts.map +1 -0
  246. package/dist/production-runtime/passes/FramegraphTopology.js +59 -0
  247. package/dist/production-runtime/passes/FramegraphTopology.js.map +1 -0
  248. package/dist/production-runtime/passes/OpaquePass.d.ts +24 -1
  249. package/dist/production-runtime/passes/OpaquePass.d.ts.map +1 -1
  250. package/dist/production-runtime/passes/OpaquePass.js +41 -2
  251. package/dist/production-runtime/passes/OpaquePass.js.map +1 -1
  252. package/dist/production-runtime/passes/ShadowPass.d.ts +24 -1
  253. package/dist/production-runtime/passes/ShadowPass.d.ts.map +1 -1
  254. package/dist/production-runtime/passes/ShadowPass.js +43 -2
  255. package/dist/production-runtime/passes/ShadowPass.js.map +1 -1
  256. package/dist/production-runtime/passes/SkyboxPass.d.ts +21 -1
  257. package/dist/production-runtime/passes/SkyboxPass.d.ts.map +1 -1
  258. package/dist/production-runtime/passes/SkyboxPass.js +39 -2
  259. package/dist/production-runtime/passes/SkyboxPass.js.map +1 -1
  260. package/dist/production-runtime/passes/ToneMappingPass.d.ts +29 -1
  261. package/dist/production-runtime/passes/ToneMappingPass.d.ts.map +1 -1
  262. package/dist/production-runtime/passes/ToneMappingPass.js +53 -2
  263. package/dist/production-runtime/passes/ToneMappingPass.js.map +1 -1
  264. package/dist/production-runtime/passes/TransparentPass.d.ts +24 -1
  265. package/dist/production-runtime/passes/TransparentPass.d.ts.map +1 -1
  266. package/dist/production-runtime/passes/TransparentPass.js +42 -2
  267. package/dist/production-runtime/passes/TransparentPass.js.map +1 -1
  268. package/dist/shadows/CascadeHysteresis.d.ts +67 -0
  269. package/dist/shadows/CascadeHysteresis.d.ts.map +1 -0
  270. package/dist/shadows/CascadeHysteresis.js +104 -0
  271. package/dist/shadows/CascadeHysteresis.js.map +1 -0
  272. package/dist/shadows/ContactShadows.d.ts +52 -0
  273. package/dist/shadows/ContactShadows.d.ts.map +1 -1
  274. package/dist/shadows/ContactShadows.js +122 -0
  275. package/dist/shadows/ContactShadows.js.map +1 -1
  276. package/dist/shadows/SpotShadowMaps.d.ts +77 -0
  277. package/dist/shadows/SpotShadowMaps.d.ts.map +1 -0
  278. package/dist/shadows/SpotShadowMaps.js +106 -0
  279. package/dist/shadows/SpotShadowMaps.js.map +1 -0
  280. package/dist/webgpu/WebGPUPostProcess.d.ts +2 -0
  281. package/dist/webgpu/WebGPUPostProcess.d.ts.map +1 -1
  282. package/dist/webgpu/WebGPUPostProcess.js +1 -0
  283. package/dist/webgpu/WebGPUPostProcess.js.map +1 -1
  284. package/dist/webgpu/WebGPUPostShaders.d.ts +78 -0
  285. package/dist/webgpu/WebGPUPostShaders.d.ts.map +1 -0
  286. package/dist/webgpu/WebGPUPostShaders.js +262 -0
  287. package/dist/webgpu/WebGPUPostShaders.js.map +1 -0
  288. package/package.json +3 -3
@@ -1,5 +1,7 @@
1
- import { RenderDeviceError, viewBytes } from "./RenderDevice.js";
1
+ import { RenderDeviceError, viewBytes, resolveGpuTargetOwner, spreadGpuTargetInventory } from "./RenderDevice.js";
2
2
  import { MAX_WEBGPU_SKINNING_JOINTS } from "./WebGPUSkinningLimits.js";
3
+ import { defaultWebGPUBloomWeights, normalizeWebGPUColorGradeOptions, normalizeWebGPUBloomOptions, webgpuBlurFragment, webgpuBloomCompositeFragment, webgpuBrightExtractFragment, webgpuColorGradeFragment, webgpuFxaaFragment, WEBGPU_POST_VERTEX_WGSL } from "./webgpu/WebGPUPostShaders.js";
4
+ import { DEFAULT_DEPTH_SHADER_MARKER } from "./ShaderLibraryCore.js";
3
5
  import { reflectShaderSources } from "./ShaderReflection.js";
4
6
  import { Texture, bytesPerPixel, isCompressedTextureFormat } from "./Texture.js";
5
7
  import { isTextureBinding, TextureBinding } from "./TextureBinding.js";
@@ -157,6 +159,22 @@ export class WebGPUDevice {
157
159
  nativeMorphSubmissions = 0;
158
160
  nativeEnvironmentBindings = 0;
159
161
  nativeShadowMapBindings = 0;
162
+ nativeBloomPasses = 0;
163
+ nativeColorGradePasses = 0;
164
+ nativeFxaaPasses = 0;
165
+ webgpuPostPipelines = new Map();
166
+ webgpuPostLinearSampler = null;
167
+ lastWebGPUBloom = null;
168
+ /**
169
+ * Uniform buffers referenced by submitted-but-maybe-unexecuted post
170
+ * passes. Destroying them synchronously after submit races the GPU (the
171
+ * reads happen later), so they retire here and are reclaimed at the start
172
+ * of the next post run — after any intervening readback fence — or on
173
+ * dispose.
174
+ */
175
+ webgpuPostRetiredUniformBuffers = [];
176
+ webgpuPostErrorScopePending = [];
177
+ webgpuPostErrors = [];
160
178
  canvasSubmissions = 0;
161
179
  buffers = new Set();
162
180
  shaders = new Set();
@@ -800,6 +818,9 @@ export class WebGPUDevice {
800
818
  ["nativeMorphSubmissions", this.nativeMorphSubmissions],
801
819
  ["nativeEnvironmentBindings", this.nativeEnvironmentBindings],
802
820
  ["nativeShadowMapBindings", this.nativeShadowMapBindings],
821
+ ["nativeBloomPasses", this.nativeBloomPasses],
822
+ ["nativeColorGradePasses", this.nativeColorGradePasses],
823
+ ["nativeFxaaPasses", this.nativeFxaaPasses],
803
824
  ["canvasSubmissions", this.canvasSubmissions]
804
825
  ]);
805
826
  }
@@ -825,6 +846,12 @@ export class WebGPUDevice {
825
846
  disposedShaders: [...this.shaders].filter((shader) => shader.disposed).length,
826
847
  disposedRenderTargets: [...this.renderTargets].filter((target) => target.disposed).length,
827
848
  disposedTextures: [...this.renderTargets].reduce((total, target) => total + (target.colorTexture.disposed ? 1 : 0) + (target.depthTexture?.disposed ? 1 : 0), 0),
849
+ ...spreadGpuTargetInventory(liveRenderTargets.map((target) => ({
850
+ label: target.label,
851
+ kind: "render-target",
852
+ bytes: target.colorTexture.byteLength + (target.depthTexture?.byteLength ?? 0),
853
+ owner: resolveGpuTargetOwner(target.label)
854
+ }))),
828
855
  nativeSubmissions: this.nativeSubmissions,
829
856
  nativeRenderPipelinesCreated: this.nativeRenderPipelinesCreated,
830
857
  nativeRenderPasses: this.nativeRenderPasses,
@@ -840,13 +867,310 @@ export class WebGPUDevice {
840
867
  nativeMorphSubmissions: this.nativeMorphSubmissions,
841
868
  nativeEnvironmentBindings: this.nativeEnvironmentBindings,
842
869
  nativeShadowMapBindings: this.nativeShadowMapBindings,
870
+ nativeBloomPasses: this.nativeBloomPasses,
871
+ nativeColorGradePasses: this.nativeColorGradePasses,
872
+ nativeFxaaPasses: this.nativeFxaaPasses,
843
873
  lastError: this.lastError,
844
874
  contextLost: this.contextLost
845
875
  };
846
876
  }
877
+ /**
878
+ * muse3jsparity-PRD J2 — native WebGPU bloom pyramid.
879
+ *
880
+ * Renders bright-extract -> separable-blur mip pyramid -> composite over
881
+ * 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
883
+ * is disposed before return. Throws fail-closed when the native device
884
+ * cannot run fullscreen passes.
885
+ */
886
+ executeWebGPUBloom(source, options = {}) {
887
+ this.reclaimWebGPUPostUniformBuffers();
888
+ const normalized = normalizeWebGPUBloomOptions(options);
889
+ const src = this.requireRenderTarget(source);
890
+ const srcView = src.nativeView;
891
+ if (!srcView) {
892
+ throw new RenderDeviceError("WebGPU bloom source target has no native view", "NATIVE_POST_UNSUPPORTED", { label: src.label });
893
+ }
894
+ const width = src.width;
895
+ const height = src.height;
896
+ const mipFormat = normalized.halfFloat ? "rgba16f" : "rgba8";
897
+ const owned = [];
898
+ const track = (target) => {
899
+ owned.push(target);
900
+ return target;
901
+ };
902
+ try {
903
+ const bright = track(this.createRenderTarget({ width, height, format: mipFormat, label: "a3d-webgpu-bloom-bright" }));
904
+ this.runWebGPUFullscreenPass({
905
+ label: "webgpu-bloom-bright",
906
+ fragment: webgpuBrightExtractFragment(),
907
+ entryPoint: "fs_bright",
908
+ uniforms: new Float32Array([normalized.threshold, normalized.knee, normalized.strength, 0]),
909
+ views: [srcView],
910
+ target: bright
911
+ });
912
+ const mips = [];
913
+ let level = bright;
914
+ let levelWidth = width;
915
+ let levelHeight = height;
916
+ for (let mip = 0; mip < normalized.mipCount; mip += 1) {
917
+ levelWidth = Math.max(1, levelWidth >> 1);
918
+ levelHeight = Math.max(1, levelHeight >> 1);
919
+ const scratch = track(this.createRenderTarget({ width: levelWidth, height: levelHeight, format: mipFormat, label: `a3d-webgpu-bloom-mip${mip}-scratch` }));
920
+ const mipTarget = track(this.createRenderTarget({ width: levelWidth, height: levelHeight, format: mipFormat, label: `a3d-webgpu-bloom-mip${mip}` }));
921
+ const levelView = this.requireRenderTarget(level).nativeView;
922
+ const scratchView = this.requireRenderTarget(scratch).nativeView;
923
+ if (!levelView || !scratchView) {
924
+ throw new RenderDeviceError("WebGPU bloom pyramid target has no native view", "NATIVE_POST_UNSUPPORTED", { mip });
925
+ }
926
+ this.runWebGPUFullscreenPass({
927
+ label: `webgpu-bloom-mip${mip}-h`,
928
+ fragment: webgpuBlurFragment(),
929
+ entryPoint: "fs_blur",
930
+ uniforms: new Float32Array([1, 0, 1 / levelWidth, 1 / levelHeight]),
931
+ views: [levelView],
932
+ target: scratch
933
+ });
934
+ this.runWebGPUFullscreenPass({
935
+ label: `webgpu-bloom-mip${mip}-v`,
936
+ fragment: webgpuBlurFragment(),
937
+ entryPoint: "fs_blur",
938
+ uniforms: new Float32Array([0, 1, 1 / levelWidth, 1 / levelHeight]),
939
+ views: [scratchView],
940
+ target: mipTarget
941
+ });
942
+ mips.push(mipTarget);
943
+ level = mipTarget;
944
+ }
945
+ const weights = defaultWebGPUBloomWeights(normalized.mipCount);
946
+ const composite = track(this.createRenderTarget({ width, height, format: "rgba8", label: "a3d-webgpu-bloom-composite" }));
947
+ const mipViews = mips.map((mipTarget) => {
948
+ const view = this.requireRenderTarget(mipTarget).nativeView;
949
+ if (!view)
950
+ throw new RenderDeviceError("WebGPU bloom mip has no native view", "NATIVE_POST_UNSUPPORTED", {});
951
+ return view;
952
+ });
953
+ this.runWebGPUFullscreenPass({
954
+ label: "webgpu-bloom-composite",
955
+ fragment: webgpuBloomCompositeFragment({ weights, strength: normalized.strength }),
956
+ entryPoint: "fs_composite",
957
+ uniforms: new Float32Array([normalized.strength, 0, 0, 0]),
958
+ views: [srcView, ...mipViews],
959
+ target: composite
960
+ });
961
+ const bytesPerPixel = normalized.halfFloat ? 8 : 4;
962
+ const targetBytes = width * height * bytesPerPixel
963
+ + mips.reduce((total, mipTarget) => total + mipTarget.width * mipTarget.height * bytesPerPixel, 0)
964
+ + width * height * 4;
965
+ this.lastWebGPUBloom = {
966
+ mipCount: normalized.mipCount,
967
+ halfFloat: normalized.halfFloat,
968
+ targetBytes,
969
+ compositeGain: normalized.strength,
970
+ passes: 1 + normalized.mipCount * 2 + 1,
971
+ executionMode: "webgpu-native-post"
972
+ };
973
+ this.nativeBloomPasses += this.lastWebGPUBloom.passes;
974
+ for (const target of owned) {
975
+ if (target !== composite)
976
+ target.dispose();
977
+ }
978
+ return composite;
979
+ }
980
+ catch (error) {
981
+ for (const target of owned)
982
+ target.dispose();
983
+ throw error;
984
+ }
985
+ }
986
+ /** Last native bloom run on this device (null before the first run). */
987
+ getWebGPUBloomDiagnostics() {
988
+ return this.lastWebGPUBloom;
989
+ }
990
+ /**
991
+ * Drains GPU validation errors observed by the native post passes.
992
+ * Empty means every submitted post pass validated clean.
993
+ */
994
+ async drainWebGPUPostErrors() {
995
+ const pending = this.webgpuPostErrorScopePending;
996
+ this.webgpuPostErrorScopePending = [];
997
+ await Promise.all(pending);
998
+ const errors = [...this.webgpuPostErrors];
999
+ this.webgpuPostErrors = [];
1000
+ return errors;
1001
+ }
1002
+ /**
1003
+ * muse3jsparity-PRD J2 — native WebGPU color grade.
1004
+ * Exposure (stops) -> saturation around luma -> contrast around middle
1005
+ * gray, one WGSL fullscreen pass into a fresh caller-owned rgba8 target.
1006
+ */
1007
+ executeWebGPUColorGrade(source, options = {}) {
1008
+ this.reclaimWebGPUPostUniformBuffers();
1009
+ const normalized = normalizeWebGPUColorGradeOptions(options);
1010
+ const src = this.requireRenderTarget(source);
1011
+ const srcView = src.nativeView;
1012
+ if (!srcView) {
1013
+ throw new RenderDeviceError("WebGPU color-grade source target has no native view", "NATIVE_POST_UNSUPPORTED", { label: src.label });
1014
+ }
1015
+ const out = this.createRenderTarget({ width: src.width, height: src.height, format: "rgba8", label: "a3d-webgpu-color-grade" });
1016
+ try {
1017
+ this.runWebGPUFullscreenPass({
1018
+ label: "webgpu-color-grade",
1019
+ fragment: webgpuColorGradeFragment(),
1020
+ entryPoint: "fs_grade",
1021
+ uniforms: new Float32Array([normalized.exposure, normalized.contrast, normalized.saturation, 0]),
1022
+ views: [srcView],
1023
+ target: out
1024
+ });
1025
+ this.nativeColorGradePasses += 1;
1026
+ return out;
1027
+ }
1028
+ catch (error) {
1029
+ out.dispose();
1030
+ throw error;
1031
+ }
1032
+ }
1033
+ /**
1034
+ * muse3jsparity-PRD J2 — native WebGPU FXAA.
1035
+ * 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).
1039
+ */
1040
+ executeWebGPUFxaa(source) {
1041
+ this.reclaimWebGPUPostUniformBuffers();
1042
+ const src = this.requireRenderTarget(source);
1043
+ const srcView = src.nativeView;
1044
+ if (!srcView) {
1045
+ throw new RenderDeviceError("WebGPU FXAA source target has no native view", "NATIVE_POST_UNSUPPORTED", { label: src.label });
1046
+ }
1047
+ const out = this.createRenderTarget({ width: src.width, height: src.height, format: "rgba8", label: "a3d-webgpu-fxaa" });
1048
+ try {
1049
+ this.runWebGPUFullscreenPass({
1050
+ label: "webgpu-fxaa",
1051
+ fragment: webgpuFxaaFragment(),
1052
+ entryPoint: "fs_fxaa",
1053
+ uniforms: new Float32Array([1 / src.width, 1 / src.height, 0, 0]),
1054
+ views: [srcView],
1055
+ target: out
1056
+ });
1057
+ this.nativeFxaaPasses += 1;
1058
+ return out;
1059
+ }
1060
+ catch (error) {
1061
+ out.dispose();
1062
+ throw error;
1063
+ }
1064
+ }
1065
+ runWebGPUFullscreenPass(args) {
1066
+ // No assertFrame here on purpose: these are target-to-target passes with
1067
+ // their own encoders, so they run both inside a Renderer frame (scene ->
1068
+ // post chaining) and after it closed (proof harnesses that render, then
1069
+ // post, then read back).
1070
+ this.assertAlive();
1071
+ const target = this.requireRenderTarget(args.target);
1072
+ const targetView = target.nativeView;
1073
+ if (!targetView) {
1074
+ throw new RenderDeviceError("WebGPU post target has no native view", "NATIVE_POST_UNSUPPORTED", { label: target.label });
1075
+ }
1076
+ if (!this.device.createShaderModule || !this.device.createRenderPipeline || !this.device.createCommandEncoder
1077
+ || !this.device.createSampler || !this.device.createBuffer || !this.device.createBindGroup) {
1078
+ throw new RenderDeviceError("WebGPU fullscreen post requires shader/pipeline/sampler/buffer natives", "NATIVE_POST_UNSUPPORTED", { label: args.label });
1079
+ }
1080
+ const scopeDevice = this.device;
1081
+ const scopedErrors = Boolean(scopeDevice.pushErrorScope && scopeDevice.popErrorScope);
1082
+ if (scopedErrors) {
1083
+ // Bracket everything below (module/pipeline/bind-group creation plus
1084
+ // the submit) so per-pass validation failures land here labeled.
1085
+ scopeDevice.pushErrorScope?.("validation");
1086
+ }
1087
+ const format = webgpuTextureFormat(target.colorTexture.format);
1088
+ const cacheKey = `${args.label}:${args.entryPoint}:${format}`;
1089
+ let pipeline = this.webgpuPostPipelines.get(cacheKey);
1090
+ if (!pipeline) {
1091
+ const vertexModule = this.device.createShaderModule({ label: `${args.label}-vs`, code: WEBGPU_POST_VERTEX_WGSL });
1092
+ const fragmentModule = this.device.createShaderModule({ label: `${args.label}-fs`, code: args.fragment });
1093
+ pipeline = this.device.createRenderPipeline({
1094
+ label: `${args.label}-pipeline`,
1095
+ layout: "auto",
1096
+ vertex: { module: vertexModule, entryPoint: "vs_post" },
1097
+ fragment: { module: fragmentModule, entryPoint: args.entryPoint, targets: [{ format }] },
1098
+ primitive: { topology: "triangle-list" }
1099
+ });
1100
+ this.nativeRenderPipelinesCreated += 1;
1101
+ this.webgpuPostPipelines.set(cacheKey, pipeline);
1102
+ }
1103
+ if (!this.webgpuPostLinearSampler) {
1104
+ this.webgpuPostLinearSampler = this.device.createSampler({
1105
+ minFilter: "linear",
1106
+ magFilter: "linear",
1107
+ addressModeU: "clamp-to-edge",
1108
+ addressModeV: "clamp-to-edge"
1109
+ });
1110
+ }
1111
+ const uniformBuffer = this.device.createBuffer({
1112
+ label: `${args.label}-uniforms`,
1113
+ size: args.uniforms.byteLength,
1114
+ usage: BUFFER_USAGE.UNIFORM | BUFFER_USAGE.COPY_DST
1115
+ });
1116
+ let submitted = false;
1117
+ try {
1118
+ this.device.queue.writeBuffer(uniformBuffer, 0, args.uniforms);
1119
+ const layout = pipeline.getBindGroupLayout?.(0);
1120
+ if (!layout)
1121
+ throw new RenderDeviceError("WebGPU post pipeline has no bind-group layout", "NATIVE_POST_UNSUPPORTED", { label: args.label });
1122
+ const group = this.device.createBindGroup({
1123
+ label: `${args.label}-bind`,
1124
+ layout,
1125
+ entries: [
1126
+ { binding: 0, resource: { buffer: uniformBuffer } },
1127
+ { binding: 1, resource: this.webgpuPostLinearSampler },
1128
+ ...args.views.map((view, index) => ({ binding: 2 + index, resource: view }))
1129
+ ]
1130
+ });
1131
+ const encoder = this.device.createCommandEncoder({ label: `${args.label}-encoder` });
1132
+ const pass = encoder.beginRenderPass({
1133
+ label: `${args.label}-pass`,
1134
+ colorAttachments: [{ view: targetView, loadOp: "load", storeOp: "store" }]
1135
+ });
1136
+ pass.setPipeline(pipeline);
1137
+ pass.setBindGroup?.(0, group);
1138
+ pass.draw(3, 1, 0);
1139
+ pass.end();
1140
+ this.device.queue.submit([encoder.finish()]);
1141
+ submitted = true;
1142
+ // The pass owns every pixel (fullscreen triangle), so a later
1143
+ // readPixelsAsync must NOT flush a stale pending clear over it.
1144
+ target.nativeNeedsClear = false;
1145
+ this.nativeRenderPasses += 1;
1146
+ // Retired, not destroyed: the submit only enqueues; the pass reads
1147
+ // these uniforms when the GPU gets to it.
1148
+ this.webgpuPostRetiredUniformBuffers.push(uniformBuffer);
1149
+ }
1150
+ catch (error) {
1151
+ if (!submitted)
1152
+ uniformBuffer.destroy();
1153
+ throw error;
1154
+ }
1155
+ finally {
1156
+ if (scopedErrors && scopeDevice.popErrorScope) {
1157
+ const pending = scopeDevice.popErrorScope().then((info) => {
1158
+ if (info?.message)
1159
+ this.webgpuPostErrors.push(`[${args.label}] ${info.message}`);
1160
+ }, () => undefined);
1161
+ this.webgpuPostErrorScopePending.push(pending);
1162
+ }
1163
+ }
1164
+ }
1165
+ reclaimWebGPUPostUniformBuffers() {
1166
+ for (const buffer of this.webgpuPostRetiredUniformBuffers)
1167
+ buffer.destroy();
1168
+ this.webgpuPostRetiredUniformBuffers = [];
1169
+ }
847
1170
  dispose() {
848
1171
  if (this.disposed)
849
1172
  return;
1173
+ this.reclaimWebGPUPostUniformBuffers();
850
1174
  for (const buffer of this.buffers)
851
1175
  buffer.dispose();
852
1176
  for (const shader of this.shaders)
@@ -1098,7 +1422,9 @@ export class WebGPUDevice {
1098
1422
  { binding: 13, resource: materialTextures.occlusion.sampler },
1099
1423
  { binding: 14, resource: materialTextures.occlusion.view },
1100
1424
  { binding: 15, resource: materialTextures.clusterLightData.view },
1101
- { binding: 16, resource: materialTextures.clusterLightIndices.view }
1425
+ { binding: 16, resource: materialTextures.clusterLightIndices.view },
1426
+ { binding: 17, resource: materialTextures.spotShadow.sampler },
1427
+ { binding: 18, resource: materialTextures.spotShadow.view }
1102
1428
  ] : [])
1103
1429
  ]
1104
1430
  })
@@ -1184,6 +1510,8 @@ export class WebGPUDevice {
1184
1510
  this.nativeEnvironmentBindings += 1;
1185
1511
  if (materialTextures?.actualShadow)
1186
1512
  this.nativeShadowMapBindings += 1;
1513
+ if (materialTextures?.actualSpotShadow)
1514
+ this.nativeShadowMapBindings += 1;
1187
1515
  if (!this.activeRenderTarget)
1188
1516
  this.canvasSubmissions += 1;
1189
1517
  }
@@ -1221,6 +1549,20 @@ export class WebGPUDevice {
1221
1549
  if (!binding?.texture || binding.texture.disposed || !binding.validate().ok || !hasNativeSampledTextureBinding(this.device))
1222
1550
  return null;
1223
1551
  const texture = binding.texture;
1552
+ // J2 zero-copy shadow path: a render target's native color texture is
1553
+ // created with TEXTURE_BINDING usage, so a shadow map rendered natively
1554
+ // (ShadowPass "encoded-color-depth") samples directly — no CPU readback
1555
+ // round-trip. Same-queue submission order guarantees the depth writes
1556
+ // are visible to the later lit pass.
1557
+ for (const target of this.renderTargets) {
1558
+ if (target.disposed || target.sampleCount !== 1 || target.colorTexture !== texture || !target.nativeView)
1559
+ continue;
1560
+ return {
1561
+ view: target.nativeView,
1562
+ sampler: this.device.createSampler(webgpuSamplerDescriptor(binding.sampler)),
1563
+ actual: true
1564
+ };
1565
+ }
1224
1566
  let resource = this.nativeSampledTextures.get(texture);
1225
1567
  if (!resource) {
1226
1568
  if (texture.source) {
@@ -1290,6 +1632,7 @@ export class WebGPUDevice {
1290
1632
  const environmentBinding = uniformTextureBinding(command.uniforms, "u_environmentMapTexture");
1291
1633
  const brdfBinding = uniformTextureBinding(command.uniforms, "u_environmentBrdfLutTexture");
1292
1634
  const shadowBinding = uniformTextureBinding(command.uniforms, "u_shadowMapTexture");
1635
+ const spotShadowBinding = uniformTextureBinding(command.uniforms, "u_spotShadowMapTexture");
1293
1636
  const normalBinding = uniformTextureBinding(command.uniforms, "u_normalTexture");
1294
1637
  const metallicRoughnessBinding = uniformTextureBinding(command.uniforms, "u_metallicRoughnessTexture");
1295
1638
  const occlusionBinding = uniformTextureBinding(command.uniforms, "u_occlusionTexture");
@@ -1299,18 +1642,20 @@ export class WebGPUDevice {
1299
1642
  const environment = this.createNativeSampledTextureBinding(environmentBinding) ?? this.createNativeFallbackSampledTextureBinding("environment-srgb", [92, 116, 156, 255], "srgb");
1300
1643
  const brdf = this.createNativeSampledTextureBinding(brdfBinding) ?? this.createNativeFallbackSampledTextureBinding("brdf-linear", [180, 180, 255, 255], "linear");
1301
1644
  const shadow = this.createNativeSampledTextureBinding(shadowBinding) ?? this.createNativeFallbackSampledTextureBinding("shadow-lit-linear", [255, 255, 255, 255], "linear");
1645
+ const spotShadow = this.createNativeSampledTextureBinding(spotShadowBinding) ?? this.createNativeFallbackSampledTextureBinding("spot-shadow-lit-linear", [255, 255, 255, 255], "linear");
1302
1646
  const normal = this.createNativeSampledTextureBinding(normalBinding) ?? this.createNativeFallbackSampledTextureBinding("flat-normal-linear", [128, 128, 255, 255], "linear");
1303
1647
  const metallicRoughness = this.createNativeSampledTextureBinding(metallicRoughnessBinding) ?? this.createNativeFallbackSampledTextureBinding("metallic-roughness-linear", [255, 255, 255, 255], "linear");
1304
1648
  const occlusion = this.createNativeSampledTextureBinding(occlusionBinding) ?? this.createNativeFallbackSampledTextureBinding("occlusion-linear", [255, 255, 255, 255], "linear");
1305
1649
  const clusterLightData = this.createNativeSampledTextureBinding(clusterLightDataBinding) ?? this.createNativeFallbackSampledTextureBinding("cluster-light-data-linear", [0, 0, 0, 0], "linear");
1306
1650
  const clusterLightIndices = this.createNativeSampledTextureBinding(clusterLightIndicesBinding) ?? this.createNativeFallbackSampledTextureBinding("cluster-light-indices-linear", [0, 0, 0, 0], "linear");
1307
- if (!base || !environment || !brdf || !shadow || !normal || !metallicRoughness || !occlusion || !clusterLightData || !clusterLightIndices)
1651
+ if (!base || !environment || !brdf || !shadow || !spotShadow || !normal || !metallicRoughness || !occlusion || !clusterLightData || !clusterLightIndices)
1308
1652
  return null;
1309
1653
  return {
1310
1654
  base,
1311
1655
  environment,
1312
1656
  brdf,
1313
1657
  shadow,
1658
+ spotShadow,
1314
1659
  normal,
1315
1660
  metallicRoughness,
1316
1661
  occlusion,
@@ -1320,6 +1665,7 @@ export class WebGPUDevice {
1320
1665
  actualEnvironment: environmentBinding !== null && environment.actual,
1321
1666
  actualBrdf: brdfBinding !== null && brdf.actual,
1322
1667
  actualShadow: shadowBinding !== null && shadow.actual,
1668
+ actualSpotShadow: spotShadowBinding !== null && spotShadow.actual,
1323
1669
  actualNormal: normalBinding !== null && normal.actual,
1324
1670
  actualMetallicRoughness: metallicRoughnessBinding !== null && metallicRoughness.actual,
1325
1671
  actualOcclusion: occlusionBinding !== null && occlusion.actual
@@ -1355,8 +1701,10 @@ export class WebGPUDevice {
1355
1701
  createNativeDrawUniformBuffer(command) {
1356
1702
  if (!this.device.createBindGroup)
1357
1703
  return null;
1358
- // 188 floats for the scalar/matrix fields + 96 joint matrices (16 floats each) appended at 188.
1359
- const data = new Float32Array(188 + MAX_WEBGPU_SKINNING_JOINTS * 16);
1704
+ // 188 floats for the scalar/matrix fields + 96 joint matrices (16 floats each) appended at 188
1705
+ // + J2 native shadow projections (312 floats at 1724: directional matrix/params/PCF, then spot)
1706
+ // + model matrix (16 floats at 2036) for instanced world composition.
1707
+ const data = new Float32Array(2052);
1360
1708
  const baseColorBinding = uniformBaseColorTextureBinding(command.uniforms);
1361
1709
  const environmentBinding = uniformTextureBinding(command.uniforms, "u_environmentMapTexture");
1362
1710
  const normalBinding = uniformTextureBinding(command.uniforms, "u_normalTexture");
@@ -1408,6 +1756,37 @@ export class WebGPUDevice {
1408
1756
  for (let index = 0; index < MAX_WEBGPU_SKINNING_JOINTS; index += 1) {
1409
1757
  data.set(jointMatrices[index] ?? identityMatrix(), 188 + index * 16);
1410
1758
  }
1759
+ // J2 native shadow projections (WGSL parity with a3dForwardShadowFactor /
1760
+ // a3dSpotShadowFactor). Identity matrices + zero enabled flags keep every
1761
+ // shadow factor at exactly 1.0 (lit) when the ForwardPass gates stay shut.
1762
+ data.set(uniformMat4(command.uniforms?.get("u_shadowMapMatrix")) ?? identityMatrix(), 1724);
1763
+ const shadowTexel = uniformVec2(command.uniforms?.get("u_shadowMapTexelSize")) ?? [1, 1];
1764
+ data.set([
1765
+ uniformNumber(command.uniforms?.get("u_shadowMapBias"), 0.001),
1766
+ uniformNumber(command.uniforms?.get("u_shadowMapSlopeBias"), 1),
1767
+ shadowTexel[0],
1768
+ shadowTexel[1]
1769
+ ], 1740);
1770
+ data.set([uniformNumber(command.uniforms?.get("u_shadowPcfSampleCount"), 1), 0, 0, 0], 1744);
1771
+ setNativeShadowPcfSamples(data, command.uniforms?.get("u_shadowPcfSamples"), 1748);
1772
+ data.set(uniformMat4(command.uniforms?.get("u_spotShadowMatrix")) ?? identityMatrix(), 1876);
1773
+ const spotLightPosition = uniformVec3(command.uniforms?.get("u_spotShadowLightPosition")) ?? [0, 0, 0];
1774
+ data.set([spotLightPosition[0], spotLightPosition[1], spotLightPosition[2], uniformNumber(command.uniforms?.get("u_spotShadowRange"), 1)], 1892);
1775
+ const spotLightDirection = uniformVec3(command.uniforms?.get("u_spotShadowLightDirection")) ?? [0, -1, 0];
1776
+ data.set([spotLightDirection[0], spotLightDirection[1], spotLightDirection[2], 0], 1896);
1777
+ data.set([
1778
+ uniformNumber(command.uniforms?.get("u_spotShadowMapEnabled"), 0),
1779
+ uniformNumber(command.uniforms?.get("u_spotShadowStrength"), 0.65),
1780
+ uniformNumber(command.uniforms?.get("u_spotShadowBias"), 0.001),
1781
+ uniformNumber(command.uniforms?.get("u_spotShadowSlopeBias"), 1)
1782
+ ], 1900);
1783
+ const spotTexel = uniformVec2(command.uniforms?.get("u_spotShadowTexelSize")) ?? [1, 1];
1784
+ const spotCone = uniformVec2(command.uniforms?.get("u_spotShadowCone")) ?? [Math.PI / 4, 0];
1785
+ data.set([spotTexel[0], spotTexel[1], uniformNumber(command.uniforms?.get("u_spotShadowPcfSampleCount"), 1), spotCone[0]], 1904);
1786
+ setNativeShadowPcfSamples(data, command.uniforms?.get("u_spotShadowPcfSamples"), 1908);
1787
+ // Model matrix for the instanced vertex world composition
1788
+ // (u_modelMatrix * instanceMatrix * position, GLSL parity).
1789
+ data.set(modelMatrix, 2036);
1411
1790
  const morphDeltas = command.uniforms?.get("u_morphPositionDeltas");
1412
1791
  const morphNumbers = morphDeltas instanceof Float32Array || Array.isArray(morphDeltas) ? Array.from(morphDeltas).slice(0, 32) : [];
1413
1792
  if (morphNumbers.length > 0)
@@ -1806,6 +2185,15 @@ function uniformVec2(value) {
1806
2185
  ? [components[0], components[1]]
1807
2186
  : null;
1808
2187
  }
2188
+ function setNativeShadowPcfSamples(data, value, offset) {
2189
+ const numbers = value instanceof Float32Array || Array.isArray(value) ? Array.from(value) : [];
2190
+ for (let index = 0; index < 32; index += 1) {
2191
+ const quad = numbers.slice(index * 4, index * 4 + 4);
2192
+ // Default single center tap (weight 1) so an enabled shadow map without
2193
+ // an explicit kernel still occludes instead of dividing by zero weight.
2194
+ data.set(quad.length === 4 && quad.every(Number.isFinite) ? quad : [0, 0, 1, 0], offset + index * 4);
2195
+ }
2196
+ }
1809
2197
  function uniformMat4Array(value, count) {
1810
2198
  const numbers = value instanceof Float32Array || Array.isArray(value) ? Array.from(value) : [];
1811
2199
  return Array.from({ length: count }, (_, index) => {
@@ -2191,11 +2579,20 @@ function createNativeShaderSources(sources) {
2191
2579
  return {
2192
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);
2193
2581
  output.position = vec4<f32>(clipPosition.x, clipPosition.y, clipPosition.z * 0.5 + clipPosition.w * 0.5, clipPosition.w);\n return output;\n}\n`,
2194
- fragment: `// ${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`,
2582
+ fragment: sources.marker.includes(DEFAULT_DEPTH_SHADER_MARKER)
2583
+ ? 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`,
2195
2585
  entryPoints: [vertexEntry, fragmentEntry],
2196
2586
  uniformLayout: "generated-basic"
2197
2587
  };
2198
2588
  }
2589
+ function nativeDepthFragment(marker, fragmentEntry) {
2590
+ // J2 native depth encoding: the GLSL depth program writes gl_FragCoord.z
2591
+ // into an rgba8 shadow target ("encoded-color-depth"). The framebuffer
2592
+ // depth @builtin(position).z is already 0..1 in WebGPU, so packing it
2593
+ // samples identically to the GL path through the projective PCF reader.
2594
+ return `// ${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@fragment\nfn ${fragmentEntry}(input: VertexOutput) -> @location(0) vec4<f32> {\n return vec4<f32>(input.position.z, input.position.z, input.position.z, 1.0);\n}\n`;
2595
+ }
2199
2596
  const PORTABLE_BINDINGS_MARKER = "/* @aura3d-bindings */";
2200
2597
  function injectPortableBindings(source, bindings) {
2201
2598
  if (!source.includes(PORTABLE_BINDINGS_MARKER)) {
@@ -2299,6 +2696,23 @@ function nativeUniformStruct() {
2299
2696
  // 96-joint skinning palette (WebGL2 \`u_jointMatrices[96]\` parity). Appended at the struct end so
2300
2697
  // the existing field offsets are unchanged.
2301
2698
  joints: array<mat4x4<f32>, 96>,
2699
+ // J2 native shadow projections (float offsets 1724+, after the 96-joint
2700
+ // palette). Directional block mirrors GLSL \`a3dForwardShadowFactor\`;
2701
+ // spot block mirrors \`a3dSpotShadowFactor\` (cone/range pre-check inside
2702
+ // the helper). Appended here so no existing field offset moves.
2703
+ shadowMapMatrix: mat4x4<f32>,
2704
+ shadowParams0: vec4<f32>,
2705
+ shadowParams1: vec4<f32>,
2706
+ shadowPcf: array<vec4<f32>, 32>,
2707
+ spotShadowMatrix: mat4x4<f32>,
2708
+ spotShadowParams0: vec4<f32>,
2709
+ spotShadowParams1: vec4<f32>,
2710
+ spotShadowParams2: vec4<f32>,
2711
+ spotShadowParams3: vec4<f32>,
2712
+ spotShadowPcf: array<vec4<f32>, 32>,
2713
+ // Model matrix for GLSL-parity world composition on the instanced path
2714
+ // (\`u_modelMatrix * instanceMatrix * position\`; float offset 2036).
2715
+ modelMatrix: mat4x4<f32>,
2302
2716
  };
2303
2717
 
2304
2718
  @group(0) @binding(0) var<uniform> u_draw: DrawUniforms;
@@ -2330,6 +2744,8 @@ ${nativeUniformStruct()}
2330
2744
  @group(0) @binding(14) var u_occlusionTexture: texture_2d<f32>;
2331
2745
  @group(0) @binding(15) var u_clusterLightData: texture_2d<f32>;
2332
2746
  @group(0) @binding(16) var u_clusterLightIndices: texture_2d<f32>;
2747
+ @group(0) @binding(17) var u_spotShadowSampler: sampler;
2748
+ @group(0) @binding(18) var u_spotShadowTexture: texture_2d<f32>;
2333
2749
 
2334
2750
  struct VertexOutput {
2335
2751
  @builtin(position) position: vec4<f32>,
@@ -2421,6 +2837,67 @@ fn perturbNormal(normalInput: vec3<f32>, tangentFrame: vec4<f32>, normalSample:
2421
2837
  return normalize(tangent * mapped.x + bitangent * mapped.y + n * max(mapped.z, 0.001));
2422
2838
  }
2423
2839
 
2840
+ fn a3dNativeShadowFactor(worldPosition: vec3<f32>, normal: vec3<f32>, lightDirection: vec3<f32>, useSpot: bool, shadowSampler: sampler, shadowTexture: texture_2d<f32>) -> f32 {
2841
+ var shadowMatrix = u_draw.shadowMapMatrix;
2842
+ var bias = u_draw.shadowParams0.x;
2843
+ var slopeBias = u_draw.shadowParams0.y;
2844
+ var texelSize = u_draw.shadowParams0.zw;
2845
+ var strength = u_draw.flags.z;
2846
+ var sampleCount = i32(u_draw.shadowParams1.x + 0.5);
2847
+ var receiverLightDirection = lightDirection;
2848
+ if (useSpot) {
2849
+ if (u_draw.spotShadowParams2.x < 0.5) { return 1.0; }
2850
+ let toFragment = worldPosition - u_draw.spotShadowParams0.xyz;
2851
+ let distanceToLight = max(length(toFragment), 0.0001);
2852
+ if (distanceToLight > u_draw.spotShadowParams0.w) { return 1.0; }
2853
+ let coneAxis = u_draw.spotShadowParams1.xyz / max(length(u_draw.spotShadowParams1.xyz), 0.0001);
2854
+ if (dot(toFragment / distanceToLight, coneAxis) < cos(u_draw.spotShadowParams3.w)) { return 1.0; }
2855
+ receiverLightDirection = coneAxis * -1.0;
2856
+ shadowMatrix = u_draw.spotShadowMatrix;
2857
+ bias = u_draw.spotShadowParams2.z;
2858
+ slopeBias = u_draw.spotShadowParams2.w;
2859
+ texelSize = u_draw.spotShadowParams3.xy;
2860
+ strength = u_draw.spotShadowParams2.y;
2861
+ sampleCount = i32(u_draw.spotShadowParams3.z + 0.5);
2862
+ }
2863
+ let lightPosition = shadowMatrix * vec4<f32>(worldPosition, 1.0);
2864
+ let projected = lightPosition.xyz / max(lightPosition.w, 0.0001);
2865
+ let uv = projected.xy * 0.5 + vec2<f32>(0.5, 0.5);
2866
+ if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) { return 1.0; }
2867
+ // Native render targets store framebuffer row 0 at the top while the CPU
2868
+ // projection math addresses texels GL-style (row 0 at the bottom), so the
2869
+ // sample V must be mirrored to land on the texel the projection means.
2870
+ // Display and readback paths already account for the row order, which is
2871
+ // why screenshots and readPixels match GL while direct sampling does not.
2872
+ let sampleUv = vec2<f32>(uv.x, 1.0 - uv.y);
2873
+ let receiverNormal = normalize(normal);
2874
+ let shadowLightDirection = receiverLightDirection / max(length(receiverLightDirection), 0.0001);
2875
+ let normalDotLight = clamp(abs(dot(receiverNormal, shadowLightDirection)), 0.0, 1.0);
2876
+ // Shared bias-table policy (GLSL parity): per-sample tangent-scaled slope
2877
+ // bias — a centre-only bias under-compensates outer PCF taps on sloped
2878
+ // receivers, so each tap scales by its own texel distance.
2879
+ let slopeTangent = min(sqrt(max(1.0 - normalDotLight * normalDotLight, 0.0)) / max(normalDotLight, 0.05), 8.0);
2880
+ let slopeTexelBias = slopeTangent * slopeBias * max(texelSize.x, texelSize.y);
2881
+ let projectedDepth = projected.z * 0.5 + 0.5;
2882
+ var shadowed = 0.0;
2883
+ var totalWeight = 0.0;
2884
+ let taps = clamp(sampleCount, 1, 32);
2885
+ for (var i = 0; i < 32; i = i + 1) {
2886
+ if (i >= taps) { break; }
2887
+ var sampleData = u_draw.shadowPcf[i];
2888
+ if (useSpot) { sampleData = u_draw.spotShadowPcf[i]; }
2889
+ let weight = max(sampleData.z, 0.0);
2890
+ let offset = sampleData.xy * texelSize;
2891
+ let storedDepth = textureSampleLevel(shadowTexture, shadowSampler, sampleUv + offset, 0.0).r;
2892
+ let sampleTexelDistance = max(1.0, length(sampleData.xy));
2893
+ let receiverDepth = projectedDepth - bias - slopeTexelBias * sampleTexelDistance;
2894
+ shadowed = shadowed + select(0.0, 1.0, receiverDepth > storedDepth) * weight;
2895
+ totalWeight = totalWeight + weight;
2896
+ }
2897
+ let occlusion = select(0.0, shadowed / totalWeight, totalWeight > 0.0);
2898
+ return mix(1.0, 1.0 - occlusion, clamp(strength, 0.0, 1.0));
2899
+ }
2900
+
2424
2901
  fn shadePbr(normalInput: vec3<f32>, tangentFrame: vec4<f32>, uv: vec2<f32>, worldPosition: vec3<f32>, fragmentPosition: vec2<f32>) -> vec4<f32> {
2425
2902
  var normal = normalize(normalInput);
2426
2903
  if (u_draw.morph0.x > 0.5) {
@@ -2484,11 +2961,14 @@ fn shadePbr(normalInput: vec3<f32>, tangentFrame: vec4<f32>, uv: vec2<f32>, worl
2484
2961
  environmentSpecularContribution = environmentSpecular;
2485
2962
  environment = environment + environmentDiffuse + environmentSpecular;
2486
2963
  }
2964
+ // J2 native shadow projections: directional PCF mirrors GLSL
2965
+ // a3dForwardShadowFactor; spot PCF mirrors a3dSpotShadowFactor. Both
2966
+ // default to 1.0 (lit) when disabled or outside the shadow frustum.
2487
2967
  var shadow = 1.0;
2488
2968
  if (u_draw.flags.y > 0.5) {
2489
- let shadowDepth = textureSampleLevel(u_shadowTexture, u_shadowSampler, vec2<f32>(0.5, 0.5), 0.0).r;
2490
- shadow = mix(1.0, shadowDepth, clamp(u_draw.flags.z, 0.0, 1.0));
2969
+ shadow = a3dNativeShadowFactor(worldPosition, normal, lightDirection, false, u_shadowSampler, u_shadowTexture);
2491
2970
  }
2971
+ shadow = shadow * a3dNativeShadowFactor(worldPosition, normal, lightDirection, true, u_spotShadowSampler, u_spotShadowTexture);
2492
2972
  var clusteredDirect = vec3<f32>(0.0, 0.0, 0.0);
2493
2973
  if (u_draw.materialFlags.w > 0.5) {
2494
2974
  let indexDimensions = textureDimensions(u_clusterLightIndices);
@@ -2615,13 +3095,27 @@ fn instanceMatrix(index: u32) -> mat4x4<f32> {
2615
3095
  return u_draw.instance0;
2616
3096
  }
2617
3097
 
3098
+ fn a3dInstanceNormalMatrix(model: mat4x4<f32>) -> mat3x3<f32> {
3099
+ // GLSL parity: v_normal = u_normalMatrix * transpose(inverse(mat3(instance))) * a_normal.
3100
+ let m = mat3x3<f32>(model[0].xyz, model[1].xyz, model[2].xyz);
3101
+ let adj0 = cross(m[1], m[2]);
3102
+ let adj1 = cross(m[2], m[0]);
3103
+ let adj2 = cross(m[0], m[1]);
3104
+ let det = dot(m[0], adj0);
3105
+ let safeDet = select(det, 1.0, abs(det) < 0.00001);
3106
+ return mat3x3<f32>(
3107
+ vec3<f32>(adj0.x, adj1.x, adj2.x) / safeDet,
3108
+ vec3<f32>(adj0.y, adj1.y, adj2.y) / safeDet,
3109
+ vec3<f32>(adj0.z, adj1.z, adj2.z) / safeDet);
3110
+ }
3111
+
2618
3112
  @vertex
2619
3113
  fn ${vertexEntry}(@location(0) position: vec3<f32>, @location(1) normal: vec3<f32>, @builtin(instance_index) instanceIndex: u32) -> VertexOutput {
2620
3114
  var output: VertexOutput;
2621
3115
  let model = instanceMatrix(instanceIndex);
2622
- let worldPosition = (model * vec4<f32>(position, 1.0)).xyz;
3116
+ let worldPosition = (u_draw.modelMatrix * model * vec4<f32>(position, 1.0)).xyz;
2623
3117
  output.position = a3dWebGPUClipPosition(u_draw.modelViewProjection * model * vec4<f32>(position, 1.0));
2624
- output.normal = normalize((model * vec4<f32>(normal, 0.0)).xyz);
3118
+ output.normal = normalize((u_draw.normalMatrix * vec4<f32>(a3dInstanceNormalMatrix(model) * normal, 0.0)).xyz);
2625
3119
  output.uv = vec2<f32>(0.5, 0.5);
2626
3120
  output.worldPosition = worldPosition;
2627
3121
  output.tangent = vec4<f32>(1.0, 0.0, 0.0, 1.0);