@aura3d/rendering 2.0.4 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AtmosphereWetness.d.ts +61 -0
- package/dist/AtmosphereWetness.d.ts.map +1 -0
- package/dist/AtmosphereWetness.js +99 -0
- package/dist/AtmosphereWetness.js.map +1 -0
- package/dist/CascadedShadowMaps.d.ts.map +1 -1
- package/dist/CascadedShadowMaps.js +3 -1
- package/dist/CascadedShadowMaps.js.map +1 -1
- package/dist/ClusteredForwardLighting.d.ts +25 -1
- package/dist/ClusteredForwardLighting.d.ts.map +1 -1
- package/dist/ClusteredForwardLighting.js +80 -3
- package/dist/ClusteredForwardLighting.js.map +1 -1
- package/dist/DayNightSky.d.ts +63 -0
- package/dist/DayNightSky.d.ts.map +1 -0
- package/dist/DayNightSky.js +156 -0
- package/dist/DayNightSky.js.map +1 -0
- package/dist/EnvironmentPresetPack.d.ts +30 -0
- package/dist/EnvironmentPresetPack.d.ts.map +1 -0
- package/dist/EnvironmentPresetPack.js +49 -0
- package/dist/EnvironmentPresetPack.js.map +1 -0
- package/dist/ExtensionScalarAtlas.d.ts +18 -0
- package/dist/ExtensionScalarAtlas.d.ts.map +1 -0
- package/dist/ExtensionScalarAtlas.js +107 -0
- package/dist/ExtensionScalarAtlas.js.map +1 -0
- package/dist/ForwardPass.d.ts +65 -0
- package/dist/ForwardPass.d.ts.map +1 -1
- package/dist/ForwardPass.js +196 -2
- package/dist/ForwardPass.js.map +1 -1
- package/dist/InstancedPBRMaterial.d.ts +1 -0
- package/dist/InstancedPBRMaterial.d.ts.map +1 -1
- package/dist/InstancedPBRMaterial.js +3 -0
- package/dist/InstancedPBRMaterial.js.map +1 -1
- package/dist/InstancingDiagnostics.d.ts +69 -0
- package/dist/InstancingDiagnostics.d.ts.map +1 -0
- package/dist/InstancingDiagnostics.js +98 -0
- package/dist/InstancingDiagnostics.js.map +1 -0
- package/dist/LeanWebGL2Device.d.ts +265 -0
- package/dist/LeanWebGL2Device.d.ts.map +1 -0
- package/dist/LeanWebGL2Device.js +4001 -0
- package/dist/LeanWebGL2Device.js.map +1 -0
- package/dist/LightingRig.d.ts +19 -1
- package/dist/LightingRig.d.ts.map +1 -1
- package/dist/LightingRig.js +61 -1
- package/dist/LightingRig.js.map +1 -1
- package/dist/MaterialBinding.js +1 -1
- package/dist/MaterialBinding.js.map +1 -1
- package/dist/MeshConsolidation.d.ts +12 -0
- package/dist/MeshConsolidation.d.ts.map +1 -1
- package/dist/MeshConsolidation.js +28 -0
- package/dist/MeshConsolidation.js.map +1 -1
- package/dist/MorphTargetPlan.d.ts +29 -2
- package/dist/MorphTargetPlan.d.ts.map +1 -1
- package/dist/MorphTargetPlan.js +23 -4
- package/dist/MorphTargetPlan.js.map +1 -1
- package/dist/NormalMappedPBRMaterial.d.ts.map +1 -1
- package/dist/NormalMappedPBRMaterial.js +22 -2
- package/dist/NormalMappedPBRMaterial.js.map +1 -1
- package/dist/OceanSurface.d.ts +75 -0
- package/dist/OceanSurface.d.ts.map +1 -1
- package/dist/OceanSurface.js +211 -0
- package/dist/OceanSurface.js.map +1 -1
- package/dist/PBRMaterial.d.ts +1 -0
- package/dist/PBRMaterial.d.ts.map +1 -1
- package/dist/PBRMaterial.js +23 -2
- package/dist/PBRMaterial.js.map +1 -1
- package/dist/PbrReference.d.ts +48 -0
- package/dist/PbrReference.d.ts.map +1 -1
- package/dist/PbrReference.js +126 -0
- package/dist/PbrReference.js.map +1 -1
- package/dist/PlanarReflection.d.ts +221 -0
- package/dist/PlanarReflection.d.ts.map +1 -0
- package/dist/PlanarReflection.js +541 -0
- package/dist/PlanarReflection.js.map +1 -0
- package/dist/PostProcessPass.d.ts +32 -0
- package/dist/PostProcessPass.d.ts.map +1 -1
- package/dist/PostProcessPass.js +79 -36
- package/dist/PostProcessPass.js.map +1 -1
- package/dist/ProjectionMath.d.ts +3 -0
- package/dist/ProjectionMath.d.ts.map +1 -0
- package/dist/ProjectionMath.js +26 -0
- package/dist/ProjectionMath.js.map +1 -0
- package/dist/ReflectionSurfaces.d.ts +17 -0
- package/dist/ReflectionSurfaces.d.ts.map +1 -1
- package/dist/ReflectionSurfaces.js +103 -1
- package/dist/ReflectionSurfaces.js.map +1 -1
- package/dist/RenderDevice.d.ts +92 -0
- package/dist/RenderDevice.d.ts.map +1 -1
- package/dist/RenderDevice.js +85 -0
- package/dist/RenderDevice.js.map +1 -1
- package/dist/Renderer.d.ts +9 -0
- package/dist/Renderer.d.ts.map +1 -1
- package/dist/Renderer.js +97 -9
- package/dist/Renderer.js.map +1 -1
- package/dist/RendererPostprocessPlan.d.ts +64 -3
- package/dist/RendererPostprocessPlan.d.ts.map +1 -1
- package/dist/RendererPostprocessPlan.js +116 -12
- package/dist/RendererPostprocessPlan.js.map +1 -1
- package/dist/RendererVisualPipelineReport.d.ts.map +1 -1
- package/dist/RendererVisualPipelineReport.js +3 -1
- package/dist/RendererVisualPipelineReport.js.map +1 -1
- package/dist/Sampler.d.ts +29 -0
- package/dist/Sampler.d.ts.map +1 -1
- package/dist/Sampler.js +47 -0
- package/dist/Sampler.js.map +1 -1
- package/dist/ScreenSpaceReflectionPass.d.ts +43 -0
- package/dist/ScreenSpaceReflectionPass.d.ts.map +1 -0
- package/dist/ScreenSpaceReflectionPass.js +58 -0
- package/dist/ScreenSpaceReflectionPass.js.map +1 -0
- package/dist/SdfText.d.ts +250 -0
- package/dist/SdfText.d.ts.map +1 -0
- package/dist/SdfText.js +551 -0
- package/dist/SdfText.js.map +1 -0
- package/dist/ShaderChunks.d.ts.map +1 -1
- package/dist/ShaderChunks.js +30 -12
- package/dist/ShaderChunks.js.map +1 -1
- package/dist/ShaderLibrary.d.ts.map +1 -1
- package/dist/ShaderLibrary.js +601 -186
- package/dist/ShaderLibrary.js.map +1 -1
- package/dist/ShaderLibraryCore.d.ts +3 -0
- package/dist/ShaderLibraryCore.d.ts.map +1 -1
- package/dist/ShaderLibraryCore.js +73 -12
- package/dist/ShaderLibraryCore.js.map +1 -1
- package/dist/ShadowMap.d.ts +24 -0
- package/dist/ShadowMap.d.ts.map +1 -1
- package/dist/ShadowMap.js +43 -0
- package/dist/ShadowMap.js.map +1 -1
- package/dist/SkinnedLitMaterial.d.ts.map +1 -1
- package/dist/SkinnedLitMaterial.js +2 -0
- package/dist/SkinnedLitMaterial.js.map +1 -1
- package/dist/SkinningBounds.d.ts +10 -0
- package/dist/SkinningBounds.d.ts.map +1 -1
- package/dist/SkinningBounds.js +27 -0
- package/dist/SkinningBounds.js.map +1 -1
- package/dist/SpriteFlipbook.d.ts +60 -0
- package/dist/SpriteFlipbook.d.ts.map +1 -0
- package/dist/SpriteFlipbook.js +128 -0
- package/dist/SpriteFlipbook.js.map +1 -0
- package/dist/TemporalHistory.d.ts +37 -0
- package/dist/TemporalHistory.d.ts.map +1 -0
- package/dist/TemporalHistory.js +135 -0
- package/dist/TemporalHistory.js.map +1 -0
- package/dist/TemporalMath.d.ts +3 -0
- package/dist/TemporalMath.d.ts.map +1 -0
- package/dist/TemporalMath.js +9 -0
- package/dist/TemporalMath.js.map +1 -0
- package/dist/TerrainTiles.d.ts +120 -0
- package/dist/TerrainTiles.d.ts.map +1 -0
- package/dist/TerrainTiles.js +221 -0
- package/dist/TerrainTiles.js.map +1 -0
- package/dist/TexturedPBRMaterial.d.ts +4 -0
- package/dist/TexturedPBRMaterial.d.ts.map +1 -1
- package/dist/TexturedPBRMaterial.js +88 -5
- package/dist/TexturedPBRMaterial.js.map +1 -1
- package/dist/TexturedUnlitMaterial.d.ts.map +1 -1
- package/dist/TexturedUnlitMaterial.js +20 -2
- package/dist/TexturedUnlitMaterial.js.map +1 -1
- package/dist/UnlitMaterial.d.ts.map +1 -1
- package/dist/UnlitMaterial.js +20 -2
- package/dist/UnlitMaterial.js.map +1 -1
- package/dist/VertexFormat.d.ts +6 -0
- package/dist/VertexFormat.d.ts.map +1 -1
- package/dist/VertexFormat.js +12 -0
- package/dist/VertexFormat.js.map +1 -1
- package/dist/VolumetricFog.d.ts +55 -0
- package/dist/VolumetricFog.d.ts.map +1 -0
- package/dist/VolumetricFog.js +91 -0
- package/dist/VolumetricFog.js.map +1 -0
- package/dist/WaterSurface.d.ts +77 -0
- package/dist/WaterSurface.d.ts.map +1 -0
- package/dist/WaterSurface.js +133 -0
- package/dist/WaterSurface.js.map +1 -0
- package/dist/WebGL2Device.d.ts +73 -0
- package/dist/WebGL2Device.d.ts.map +1 -1
- package/dist/WebGL2Device.js +836 -79
- package/dist/WebGL2Device.js.map +1 -1
- package/dist/WebGPUDevice.d.ts +62 -1
- package/dist/WebGPUDevice.d.ts.map +1 -1
- package/dist/WebGPUDevice.js +844 -51
- package/dist/WebGPUDevice.js.map +1 -1
- package/dist/WebGPUExtensionAtlas.d.ts +6 -0
- package/dist/WebGPUExtensionAtlas.d.ts.map +1 -0
- package/dist/WebGPUExtensionAtlas.js +225 -0
- package/dist/WebGPUExtensionAtlas.js.map +1 -0
- package/dist/WebGPUSkinningLimits.d.ts +40 -0
- package/dist/WebGPUSkinningLimits.d.ts.map +1 -1
- package/dist/WebGPUSkinningLimits.js +32 -0
- package/dist/WebGPUSkinningLimits.js.map +1 -1
- package/dist/effects/CollisionModule.d.ts +9 -0
- package/dist/effects/CollisionModule.d.ts.map +1 -1
- package/dist/effects/CollisionModule.js +10 -0
- package/dist/effects/CollisionModule.js.map +1 -1
- package/dist/effects/ColorModule.d.ts +7 -0
- package/dist/effects/ColorModule.d.ts.map +1 -1
- package/dist/effects/ColorModule.js +23 -0
- package/dist/effects/ColorModule.js.map +1 -1
- package/dist/effects/ForceModule.d.ts +35 -0
- package/dist/effects/ForceModule.d.ts.map +1 -1
- package/dist/effects/ForceModule.js +49 -0
- package/dist/effects/ForceModule.js.map +1 -1
- package/dist/effects/GPUParticleBackend.d.ts +188 -0
- package/dist/effects/GPUParticleBackend.d.ts.map +1 -1
- package/dist/effects/GPUParticleBackend.js +710 -1
- package/dist/effects/GPUParticleBackend.js.map +1 -1
- package/dist/effects/HeightfieldModule.d.ts +58 -0
- package/dist/effects/HeightfieldModule.d.ts.map +1 -0
- package/dist/effects/HeightfieldModule.js +129 -0
- package/dist/effects/HeightfieldModule.js.map +1 -0
- package/dist/effects/LightingModule.d.ts +34 -0
- package/dist/effects/LightingModule.d.ts.map +1 -0
- package/dist/effects/LightingModule.js +54 -0
- package/dist/effects/LightingModule.js.map +1 -0
- package/dist/effects/ParticleDiagnostics.d.ts +8 -0
- package/dist/effects/ParticleDiagnostics.d.ts.map +1 -1
- package/dist/effects/ParticleDiagnostics.js +3 -0
- package/dist/effects/ParticleDiagnostics.js.map +1 -1
- package/dist/effects/ParticleModule.d.ts +8 -0
- package/dist/effects/ParticleModule.d.ts.map +1 -1
- package/dist/effects/ParticleModule.js.map +1 -1
- package/dist/effects/ParticleRenderer.d.ts +20 -0
- package/dist/effects/ParticleRenderer.d.ts.map +1 -1
- package/dist/effects/ParticleRenderer.js +57 -7
- package/dist/effects/ParticleRenderer.js.map +1 -1
- package/dist/effects/ParticleSystem.d.ts +45 -1
- package/dist/effects/ParticleSystem.d.ts.map +1 -1
- package/dist/effects/ParticleSystem.js +373 -4
- package/dist/effects/ParticleSystem.js.map +1 -1
- package/dist/effects/ResidentGPUParticleRenderer.d.ts +158 -0
- package/dist/effects/ResidentGPUParticleRenderer.d.ts.map +1 -0
- package/dist/effects/ResidentGPUParticleRenderer.js +576 -0
- package/dist/effects/ResidentGPUParticleRenderer.js.map +1 -0
- package/dist/effects/SizeModule.d.ts +4 -0
- package/dist/effects/SizeModule.d.ts.map +1 -1
- package/dist/effects/SizeModule.js +16 -0
- package/dist/effects/SizeModule.js.map +1 -1
- package/dist/effects/SubEmitterModule.d.ts +61 -0
- package/dist/effects/SubEmitterModule.d.ts.map +1 -0
- package/dist/effects/SubEmitterModule.js +119 -0
- package/dist/effects/SubEmitterModule.js.map +1 -0
- package/dist/effects/TrailModule.d.ts +38 -0
- package/dist/effects/TrailModule.d.ts.map +1 -1
- package/dist/effects/TrailModule.js +95 -2
- package/dist/effects/TrailModule.js.map +1 -1
- package/dist/effects/TurbulenceModule.d.ts +43 -0
- package/dist/effects/TurbulenceModule.d.ts.map +1 -0
- package/dist/effects/TurbulenceModule.js +130 -0
- package/dist/effects/TurbulenceModule.js.map +1 -0
- package/dist/effects/index.d.ts +4 -0
- package/dist/effects/index.d.ts.map +1 -1
- package/dist/effects/index.js +4 -0
- package/dist/effects/index.js.map +1 -1
- package/dist/extension-scalar-atlas.d.ts +3 -0
- package/dist/extension-scalar-atlas.d.ts.map +1 -0
- package/dist/extension-scalar-atlas.js +2 -0
- package/dist/extension-scalar-atlas.js.map +1 -0
- package/dist/index.d.ts +58 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -10
- package/dist/index.js.map +1 -1
- package/dist/lean/LeanProductionRenderer.d.ts.map +1 -1
- package/dist/lean/LeanProductionRenderer.js +2 -2
- package/dist/lean/LeanProductionRenderer.js.map +1 -1
- package/dist/lean-core-runtime.d.ts +13 -0
- package/dist/lean-core-runtime.d.ts.map +1 -0
- package/dist/lean-core-runtime.js +10 -0
- package/dist/lean-core-runtime.js.map +1 -0
- package/dist/materials/MaterialExtensions.js +3 -3
- package/dist/materials/MaterialExtensions.js.map +1 -1
- package/dist/postprocess/NativeBloomPyramid.d.ts +44 -0
- package/dist/postprocess/NativeBloomPyramid.d.ts.map +1 -0
- package/dist/postprocess/NativeBloomPyramid.js +84 -0
- package/dist/postprocess/NativeBloomPyramid.js.map +1 -0
- package/dist/postprocess/NativeLdrEffectLuts.d.ts +10 -1
- package/dist/postprocess/NativeLdrEffectLuts.d.ts.map +1 -1
- package/dist/postprocess/NativeLdrEffectLuts.js +19 -2
- package/dist/postprocess/NativeLdrEffectLuts.js.map +1 -1
- package/dist/postprocess/index.d.ts +2 -0
- package/dist/postprocess/index.d.ts.map +1 -1
- package/dist/postprocess/index.js +1 -0
- package/dist/postprocess/index.js.map +1 -1
- package/dist/production-runtime/PBRHDRPipeline.d.ts +22 -0
- package/dist/production-runtime/PBRHDRPipeline.d.ts.map +1 -1
- package/dist/production-runtime/PBRHDRPipeline.js +125 -0
- package/dist/production-runtime/PBRHDRPipeline.js.map +1 -1
- package/dist/production-runtime/ProductionRendererTypes.d.ts +1 -0
- package/dist/production-runtime/ProductionRendererTypes.d.ts.map +1 -1
- package/dist/production-runtime/ProductionRendererTypes.js.map +1 -1
- package/dist/production-runtime/ProductionRuntimeRenderer.d.ts +3 -0
- package/dist/production-runtime/ProductionRuntimeRenderer.d.ts.map +1 -1
- package/dist/production-runtime/ProductionRuntimeRenderer.js +8 -1
- package/dist/production-runtime/ProductionRuntimeRenderer.js.map +1 -1
- package/dist/production-runtime/ProductionWebGL2Renderer.d.ts +4 -0
- package/dist/production-runtime/ProductionWebGL2Renderer.d.ts.map +1 -1
- package/dist/production-runtime/ProductionWebGL2Renderer.js +15 -0
- package/dist/production-runtime/ProductionWebGL2Renderer.js.map +1 -1
- package/dist/production-runtime/ProductionWebGPURenderer.d.ts +58 -0
- package/dist/production-runtime/ProductionWebGPURenderer.d.ts.map +1 -1
- package/dist/production-runtime/ProductionWebGPURenderer.js +127 -0
- package/dist/production-runtime/ProductionWebGPURenderer.js.map +1 -1
- package/dist/production-runtime/framegraph/FrameGraph.d.ts +4 -2
- package/dist/production-runtime/framegraph/FrameGraph.d.ts.map +1 -1
- package/dist/production-runtime/framegraph/FrameGraph.js +62 -4
- package/dist/production-runtime/framegraph/FrameGraph.js.map +1 -1
- package/dist/production-runtime/framegraph/NativeFrameGraphBindings.d.ts +32 -0
- package/dist/production-runtime/framegraph/NativeFrameGraphBindings.d.ts.map +1 -0
- package/dist/production-runtime/framegraph/NativeFrameGraphBindings.js +111 -0
- package/dist/production-runtime/framegraph/NativeFrameGraphBindings.js.map +1 -0
- package/dist/production-runtime/framegraph/RenderPass.d.ts +23 -0
- package/dist/production-runtime/framegraph/RenderPass.d.ts.map +1 -1
- package/dist/production-runtime/framegraph/RenderPass.js +87 -1
- package/dist/production-runtime/framegraph/RenderPass.js.map +1 -1
- package/dist/production-runtime/index.d.ts +7 -4
- package/dist/production-runtime/index.d.ts.map +1 -1
- package/dist/production-runtime/index.js +4 -2
- package/dist/production-runtime/index.js.map +1 -1
- package/dist/production-runtime/passes/DepthPrepass.d.ts +18 -1
- package/dist/production-runtime/passes/DepthPrepass.d.ts.map +1 -1
- package/dist/production-runtime/passes/DepthPrepass.js +41 -2
- package/dist/production-runtime/passes/DepthPrepass.js.map +1 -1
- package/dist/production-runtime/passes/FramegraphTopology.d.ts +13 -0
- package/dist/production-runtime/passes/FramegraphTopology.d.ts.map +1 -0
- package/dist/production-runtime/passes/FramegraphTopology.js +59 -0
- package/dist/production-runtime/passes/FramegraphTopology.js.map +1 -0
- package/dist/production-runtime/passes/OpaquePass.d.ts +19 -1
- package/dist/production-runtime/passes/OpaquePass.d.ts.map +1 -1
- package/dist/production-runtime/passes/OpaquePass.js +43 -2
- package/dist/production-runtime/passes/OpaquePass.js.map +1 -1
- package/dist/production-runtime/passes/ShadowPass.d.ts +19 -1
- package/dist/production-runtime/passes/ShadowPass.d.ts.map +1 -1
- package/dist/production-runtime/passes/ShadowPass.js +45 -2
- package/dist/production-runtime/passes/ShadowPass.js.map +1 -1
- package/dist/production-runtime/passes/SkyboxPass.d.ts +17 -1
- package/dist/production-runtime/passes/SkyboxPass.d.ts.map +1 -1
- package/dist/production-runtime/passes/SkyboxPass.js +41 -2
- package/dist/production-runtime/passes/SkyboxPass.js.map +1 -1
- package/dist/production-runtime/passes/ToneMappingPass.d.ts +26 -1
- package/dist/production-runtime/passes/ToneMappingPass.d.ts.map +1 -1
- package/dist/production-runtime/passes/ToneMappingPass.js +62 -2
- package/dist/production-runtime/passes/ToneMappingPass.js.map +1 -1
- package/dist/production-runtime/passes/TransparentPass.d.ts +19 -1
- package/dist/production-runtime/passes/TransparentPass.d.ts.map +1 -1
- package/dist/production-runtime/passes/TransparentPass.js +47 -2
- package/dist/production-runtime/passes/TransparentPass.js.map +1 -1
- package/dist/reflection-surfaces.d.ts +5 -0
- package/dist/reflection-surfaces.d.ts.map +1 -0
- package/dist/reflection-surfaces.js +3 -0
- package/dist/reflection-surfaces.js.map +1 -0
- package/dist/shadows/CascadeHysteresis.d.ts +67 -0
- package/dist/shadows/CascadeHysteresis.d.ts.map +1 -0
- package/dist/shadows/CascadeHysteresis.js +104 -0
- package/dist/shadows/CascadeHysteresis.js.map +1 -0
- package/dist/shadows/ContactShadows.d.ts +52 -0
- package/dist/shadows/ContactShadows.d.ts.map +1 -1
- package/dist/shadows/ContactShadows.js +122 -0
- package/dist/shadows/ContactShadows.js.map +1 -1
- package/dist/shadows/SpotShadowMaps.d.ts +77 -0
- package/dist/shadows/SpotShadowMaps.d.ts.map +1 -0
- package/dist/shadows/SpotShadowMaps.js +106 -0
- package/dist/shadows/SpotShadowMaps.js.map +1 -0
- package/dist/webgpu/WebGPUPostProcess.d.ts +2 -0
- package/dist/webgpu/WebGPUPostProcess.d.ts.map +1 -1
- package/dist/webgpu/WebGPUPostProcess.js +1 -0
- package/dist/webgpu/WebGPUPostProcess.js.map +1 -1
- package/dist/webgpu/WebGPUPostShaders.d.ts +77 -0
- package/dist/webgpu/WebGPUPostShaders.d.ts.map +1 -0
- package/dist/webgpu/WebGPUPostShaders.js +261 -0
- package/dist/webgpu/WebGPUPostShaders.js.map +1 -0
- package/dist/webgpu/WebGPUTemporal.d.ts +17 -0
- package/dist/webgpu/WebGPUTemporal.d.ts.map +1 -0
- package/dist/webgpu/WebGPUTemporal.js +64 -0
- package/dist/webgpu/WebGPUTemporal.js.map +1 -0
- package/dist/webgpu.d.ts +6 -0
- package/dist/webgpu.d.ts.map +1 -1
- package/dist/webgpu.js +4 -0
- package/dist/webgpu.js.map +1 -1
- package/package.json +15 -3
package/dist/WebGPUDevice.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { WEBGPU_EXTENSION_ATLAS_WGSL, webgpuExtensionAtlasUniforms } from "./WebGPUExtensionAtlas.js";
|
|
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";
|
|
5
|
+
import { defaultWebGPUBloomWeights, normalizeWebGPUColorGradeOptions, normalizeWebGPUBloomOptions, webgpuBlurFragment, webgpuBloomCompositeFragment, webgpuBrightExtractFragment, webgpuColorGradeFragment, webgpuFxaaFragment, WEBGPU_POST_VERTEX_WGSL } from "./webgpu/WebGPUPostShaders.js";
|
|
6
|
+
import { DEFAULT_DEPTH_SHADER_MARKER } from "./ShaderLibraryCore.js";
|
|
3
7
|
import { reflectShaderSources } from "./ShaderReflection.js";
|
|
4
8
|
import { Texture, bytesPerPixel, isCompressedTextureFormat } from "./Texture.js";
|
|
5
9
|
import { isTextureBinding, TextureBinding } from "./TextureBinding.js";
|
|
@@ -22,6 +26,26 @@ const TEXTURE_USAGE = {
|
|
|
22
26
|
TEXTURE_BINDING: 0x0004
|
|
23
27
|
};
|
|
24
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
|
+
}`;
|
|
25
49
|
class WebGPURenderBuffer {
|
|
26
50
|
id;
|
|
27
51
|
usage;
|
|
@@ -56,9 +80,13 @@ class WebGPUShaderProgram {
|
|
|
56
80
|
entryPoints;
|
|
57
81
|
nativeUniformLayout;
|
|
58
82
|
portableBindings;
|
|
83
|
+
nativeColorDefaultVertex;
|
|
84
|
+
nativeAtlasVertex;
|
|
59
85
|
disposed = false;
|
|
60
86
|
renderPipelines = new Map();
|
|
61
|
-
|
|
87
|
+
nativeColorDefaultModule;
|
|
88
|
+
atlasVertexModules = new Map();
|
|
89
|
+
constructor(id, label, marker, reflection, modules, entryPoints, nativeUniformLayout, portableBindings, nativeColorDefaultVertex, nativeAtlasVertex) {
|
|
62
90
|
this.id = id;
|
|
63
91
|
this.label = label;
|
|
64
92
|
this.marker = marker;
|
|
@@ -67,6 +95,8 @@ class WebGPUShaderProgram {
|
|
|
67
95
|
this.entryPoints = entryPoints;
|
|
68
96
|
this.nativeUniformLayout = nativeUniformLayout;
|
|
69
97
|
this.portableBindings = portableBindings;
|
|
98
|
+
this.nativeColorDefaultVertex = nativeColorDefaultVertex;
|
|
99
|
+
this.nativeAtlasVertex = nativeAtlasVertex;
|
|
70
100
|
}
|
|
71
101
|
dispose() {
|
|
72
102
|
this.disposed = true;
|
|
@@ -157,6 +187,24 @@ export class WebGPUDevice {
|
|
|
157
187
|
nativeMorphSubmissions = 0;
|
|
158
188
|
nativeEnvironmentBindings = 0;
|
|
159
189
|
nativeShadowMapBindings = 0;
|
|
190
|
+
nativeBloomPasses = 0;
|
|
191
|
+
nativeColorGradePasses = 0;
|
|
192
|
+
nativeFxaaPasses = 0;
|
|
193
|
+
nativeTaaPasses = 0;
|
|
194
|
+
nativeTemporalBindings = 0;
|
|
195
|
+
webgpuPostPipelines = new Map();
|
|
196
|
+
webgpuPostLinearSampler = null;
|
|
197
|
+
lastWebGPUBloom = null;
|
|
198
|
+
/**
|
|
199
|
+
* Uniform buffers referenced by submitted-but-maybe-unexecuted post
|
|
200
|
+
* passes. Destroying them synchronously after submit races the GPU (the
|
|
201
|
+
* reads happen later), so they retire here and are reclaimed at the start
|
|
202
|
+
* of the next post run — after any intervening readback fence — or on
|
|
203
|
+
* dispose.
|
|
204
|
+
*/
|
|
205
|
+
webgpuPostRetiredUniformBuffers = [];
|
|
206
|
+
webgpuPostErrorScopePending = [];
|
|
207
|
+
webgpuPostErrors = [];
|
|
160
208
|
canvasSubmissions = 0;
|
|
161
209
|
buffers = new Set();
|
|
162
210
|
shaders = new Set();
|
|
@@ -294,7 +342,7 @@ export class WebGPUDevice {
|
|
|
294
342
|
this.device.createShaderModule({ label: `${sources.label}-fragment`, code: nativeSources.fragment })
|
|
295
343
|
]
|
|
296
344
|
: [];
|
|
297
|
-
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);
|
|
298
346
|
this.shaders.add(shader);
|
|
299
347
|
return shader;
|
|
300
348
|
}
|
|
@@ -769,6 +817,13 @@ export class WebGPUDevice {
|
|
|
769
817
|
this.rasterizeDraw(command, vertexBuffer);
|
|
770
818
|
this.drawCalls += 1;
|
|
771
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
|
+
}
|
|
772
827
|
endFrame() {
|
|
773
828
|
this.assertFrame();
|
|
774
829
|
this.frameActive = false;
|
|
@@ -800,6 +855,11 @@ export class WebGPUDevice {
|
|
|
800
855
|
["nativeMorphSubmissions", this.nativeMorphSubmissions],
|
|
801
856
|
["nativeEnvironmentBindings", this.nativeEnvironmentBindings],
|
|
802
857
|
["nativeShadowMapBindings", this.nativeShadowMapBindings],
|
|
858
|
+
["nativeBloomPasses", this.nativeBloomPasses],
|
|
859
|
+
["nativeColorGradePasses", this.nativeColorGradePasses],
|
|
860
|
+
["nativeFxaaPasses", this.nativeFxaaPasses],
|
|
861
|
+
["nativeTaaPasses", this.nativeTaaPasses],
|
|
862
|
+
["nativeTemporalBindings", this.nativeTemporalBindings],
|
|
803
863
|
["canvasSubmissions", this.canvasSubmissions]
|
|
804
864
|
]);
|
|
805
865
|
}
|
|
@@ -825,6 +885,12 @@ export class WebGPUDevice {
|
|
|
825
885
|
disposedShaders: [...this.shaders].filter((shader) => shader.disposed).length,
|
|
826
886
|
disposedRenderTargets: [...this.renderTargets].filter((target) => target.disposed).length,
|
|
827
887
|
disposedTextures: [...this.renderTargets].reduce((total, target) => total + (target.colorTexture.disposed ? 1 : 0) + (target.depthTexture?.disposed ? 1 : 0), 0),
|
|
888
|
+
...spreadGpuTargetInventory(liveRenderTargets.map((target) => ({
|
|
889
|
+
label: target.label,
|
|
890
|
+
kind: "render-target",
|
|
891
|
+
bytes: target.colorTexture.byteLength + (target.depthTexture?.byteLength ?? 0),
|
|
892
|
+
owner: resolveGpuTargetOwner(target.label)
|
|
893
|
+
}))),
|
|
828
894
|
nativeSubmissions: this.nativeSubmissions,
|
|
829
895
|
nativeRenderPipelinesCreated: this.nativeRenderPipelinesCreated,
|
|
830
896
|
nativeRenderPasses: this.nativeRenderPasses,
|
|
@@ -840,13 +906,407 @@ export class WebGPUDevice {
|
|
|
840
906
|
nativeMorphSubmissions: this.nativeMorphSubmissions,
|
|
841
907
|
nativeEnvironmentBindings: this.nativeEnvironmentBindings,
|
|
842
908
|
nativeShadowMapBindings: this.nativeShadowMapBindings,
|
|
909
|
+
nativeBloomPasses: this.nativeBloomPasses,
|
|
910
|
+
nativeColorGradePasses: this.nativeColorGradePasses,
|
|
911
|
+
nativeFxaaPasses: this.nativeFxaaPasses,
|
|
843
912
|
lastError: this.lastError,
|
|
844
913
|
contextLost: this.contextLost
|
|
845
914
|
};
|
|
846
915
|
}
|
|
916
|
+
/**
|
|
917
|
+
* muse3jsparity-PRD J2 — native WebGPU bloom pyramid.
|
|
918
|
+
*
|
|
919
|
+
* Renders bright-extract -> separable-blur mip pyramid -> composite over
|
|
920
|
+
* the source, all as real WGSL fullscreen passes on this device. Returns a
|
|
921
|
+
* fresh rgba16f target owned by the caller (dispose it); every intermediate
|
|
922
|
+
* is disposed before return. Throws fail-closed when the native device
|
|
923
|
+
* cannot run fullscreen passes.
|
|
924
|
+
*/
|
|
925
|
+
executeWebGPUBloom(source, options = {}) {
|
|
926
|
+
this.reclaimWebGPUPostUniformBuffers();
|
|
927
|
+
const normalized = normalizeWebGPUBloomOptions(options);
|
|
928
|
+
const src = this.requireRenderTarget(source);
|
|
929
|
+
const srcView = src.nativeView;
|
|
930
|
+
if (!srcView) {
|
|
931
|
+
throw new RenderDeviceError("WebGPU bloom source target has no native view", "NATIVE_POST_UNSUPPORTED", { label: src.label });
|
|
932
|
+
}
|
|
933
|
+
const width = src.width;
|
|
934
|
+
const height = src.height;
|
|
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";
|
|
939
|
+
const owned = [];
|
|
940
|
+
const track = (target) => {
|
|
941
|
+
owned.push(target);
|
|
942
|
+
return target;
|
|
943
|
+
};
|
|
944
|
+
try {
|
|
945
|
+
const bright = track(this.createRenderTarget({ width, height, format: mipFormat, label: "a3d-webgpu-bloom-bright" }));
|
|
946
|
+
this.runWebGPUFullscreenPass({
|
|
947
|
+
label: "webgpu-bloom-bright",
|
|
948
|
+
fragment: webgpuBrightExtractFragment(),
|
|
949
|
+
entryPoint: "fs_bright",
|
|
950
|
+
uniforms: new Float32Array([normalized.threshold, normalized.knee, normalized.strength, 0]),
|
|
951
|
+
views: [srcView],
|
|
952
|
+
target: bright
|
|
953
|
+
});
|
|
954
|
+
const mips = [];
|
|
955
|
+
let level = bright;
|
|
956
|
+
let levelWidth = width;
|
|
957
|
+
let levelHeight = height;
|
|
958
|
+
for (let mip = 0; mip < normalized.mipCount; mip += 1) {
|
|
959
|
+
levelWidth = Math.max(1, levelWidth >> 1);
|
|
960
|
+
levelHeight = Math.max(1, levelHeight >> 1);
|
|
961
|
+
const scratch = track(this.createRenderTarget({ width: levelWidth, height: levelHeight, format: mipFormat, label: `a3d-webgpu-bloom-mip${mip}-scratch` }));
|
|
962
|
+
const mipTarget = track(this.createRenderTarget({ width: levelWidth, height: levelHeight, format: mipFormat, label: `a3d-webgpu-bloom-mip${mip}` }));
|
|
963
|
+
const levelView = this.requireRenderTarget(level).nativeView;
|
|
964
|
+
const scratchView = this.requireRenderTarget(scratch).nativeView;
|
|
965
|
+
if (!levelView || !scratchView) {
|
|
966
|
+
throw new RenderDeviceError("WebGPU bloom pyramid target has no native view", "NATIVE_POST_UNSUPPORTED", { mip });
|
|
967
|
+
}
|
|
968
|
+
this.runWebGPUFullscreenPass({
|
|
969
|
+
label: `webgpu-bloom-mip${mip}-h`,
|
|
970
|
+
fragment: webgpuBlurFragment(),
|
|
971
|
+
entryPoint: "fs_blur",
|
|
972
|
+
uniforms: new Float32Array([1, 0, 1 / levelWidth, 1 / levelHeight]),
|
|
973
|
+
views: [levelView],
|
|
974
|
+
target: scratch
|
|
975
|
+
});
|
|
976
|
+
this.runWebGPUFullscreenPass({
|
|
977
|
+
label: `webgpu-bloom-mip${mip}-v`,
|
|
978
|
+
fragment: webgpuBlurFragment(),
|
|
979
|
+
entryPoint: "fs_blur",
|
|
980
|
+
uniforms: new Float32Array([0, 1, 1 / levelWidth, 1 / levelHeight]),
|
|
981
|
+
views: [scratchView],
|
|
982
|
+
target: mipTarget
|
|
983
|
+
});
|
|
984
|
+
mips.push(mipTarget);
|
|
985
|
+
level = mipTarget;
|
|
986
|
+
}
|
|
987
|
+
const weights = defaultWebGPUBloomWeights(normalized.mipCount);
|
|
988
|
+
const composite = track(this.createRenderTarget({ width, height, format: "rgba16f", label: "a3d-webgpu-bloom-composite" }));
|
|
989
|
+
const mipViews = mips.map((mipTarget) => {
|
|
990
|
+
const view = this.requireRenderTarget(mipTarget).nativeView;
|
|
991
|
+
if (!view)
|
|
992
|
+
throw new RenderDeviceError("WebGPU bloom mip has no native view", "NATIVE_POST_UNSUPPORTED", {});
|
|
993
|
+
return view;
|
|
994
|
+
});
|
|
995
|
+
this.runWebGPUFullscreenPass({
|
|
996
|
+
label: "webgpu-bloom-composite",
|
|
997
|
+
fragment: webgpuBloomCompositeFragment({ weights, strength: normalized.strength }),
|
|
998
|
+
entryPoint: "fs_composite",
|
|
999
|
+
uniforms: new Float32Array([normalized.strength, 0, 0, 0]),
|
|
1000
|
+
views: [srcView, ...mipViews],
|
|
1001
|
+
target: composite
|
|
1002
|
+
});
|
|
1003
|
+
const bytesPerPixel = halfFloat ? 8 : 4;
|
|
1004
|
+
const targetBytes = width * height * bytesPerPixel
|
|
1005
|
+
+ mips.reduce((total, mipTarget) => total + mipTarget.width * mipTarget.height * bytesPerPixel * 2, 0)
|
|
1006
|
+
+ width * height * 8;
|
|
1007
|
+
this.lastWebGPUBloom = {
|
|
1008
|
+
mipCount: normalized.mipCount,
|
|
1009
|
+
halfFloat,
|
|
1010
|
+
targetBytes,
|
|
1011
|
+
compositeGain: normalized.strength,
|
|
1012
|
+
passes: 1 + normalized.mipCount * 2 + 1,
|
|
1013
|
+
executionMode: "webgpu-native-post"
|
|
1014
|
+
};
|
|
1015
|
+
this.nativeBloomPasses += this.lastWebGPUBloom.passes;
|
|
1016
|
+
for (const target of owned) {
|
|
1017
|
+
if (target !== composite)
|
|
1018
|
+
target.dispose();
|
|
1019
|
+
}
|
|
1020
|
+
return composite;
|
|
1021
|
+
}
|
|
1022
|
+
catch (error) {
|
|
1023
|
+
for (const target of owned)
|
|
1024
|
+
target.dispose();
|
|
1025
|
+
throw error;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
/** Last native bloom run on this device (null before the first run). */
|
|
1029
|
+
getWebGPUBloomDiagnostics() {
|
|
1030
|
+
return this.lastWebGPUBloom;
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Drains GPU validation errors observed by the native post passes.
|
|
1034
|
+
* Empty means every submitted post pass validated clean.
|
|
1035
|
+
*/
|
|
1036
|
+
async drainWebGPUPostErrors() {
|
|
1037
|
+
const pending = this.webgpuPostErrorScopePending;
|
|
1038
|
+
this.webgpuPostErrorScopePending = [];
|
|
1039
|
+
await Promise.all(pending);
|
|
1040
|
+
const errors = [...this.webgpuPostErrors];
|
|
1041
|
+
this.webgpuPostErrors = [];
|
|
1042
|
+
return errors;
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* muse3jsparity-PRD J2 — native WebGPU color grade.
|
|
1046
|
+
* Exposure (stops) -> saturation around luma -> contrast around middle
|
|
1047
|
+
* gray, one WGSL fullscreen pass into a fresh caller-owned rgba8 target.
|
|
1048
|
+
*/
|
|
1049
|
+
executeWebGPUColorGrade(source, options = {}) {
|
|
1050
|
+
this.reclaimWebGPUPostUniformBuffers();
|
|
1051
|
+
const normalized = normalizeWebGPUColorGradeOptions(options);
|
|
1052
|
+
const src = this.requireRenderTarget(source);
|
|
1053
|
+
const srcView = src.nativeView;
|
|
1054
|
+
if (!srcView) {
|
|
1055
|
+
throw new RenderDeviceError("WebGPU color-grade source target has no native view", "NATIVE_POST_UNSUPPORTED", { label: src.label });
|
|
1056
|
+
}
|
|
1057
|
+
const out = this.createRenderTarget({ width: src.width, height: src.height, format: "rgba8", label: "a3d-webgpu-color-grade" });
|
|
1058
|
+
try {
|
|
1059
|
+
this.runWebGPUFullscreenPass({
|
|
1060
|
+
label: "webgpu-color-grade",
|
|
1061
|
+
fragment: webgpuColorGradeFragment(),
|
|
1062
|
+
entryPoint: "fs_grade",
|
|
1063
|
+
uniforms: new Float32Array([normalized.exposure, normalized.contrast, normalized.saturation, 0]),
|
|
1064
|
+
views: [srcView],
|
|
1065
|
+
target: out
|
|
1066
|
+
});
|
|
1067
|
+
this.nativeColorGradePasses += 1;
|
|
1068
|
+
return out;
|
|
1069
|
+
}
|
|
1070
|
+
catch (error) {
|
|
1071
|
+
out.dispose();
|
|
1072
|
+
throw error;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* muse3jsparity-PRD J2 — native WebGPU FXAA.
|
|
1077
|
+
* Compact FXAA 3.11 console core (luma neighborhood, one perpendicular
|
|
1078
|
+
* blend tap pair; flat neighborhoods pass through untouched). Temporal
|
|
1079
|
+
* antialiasing has a separate GPU history contract in executeWebGPUTaa.
|
|
1080
|
+
*/
|
|
1081
|
+
executeWebGPUFxaa(source) {
|
|
1082
|
+
this.reclaimWebGPUPostUniformBuffers();
|
|
1083
|
+
const src = this.requireRenderTarget(source);
|
|
1084
|
+
const srcView = src.nativeView;
|
|
1085
|
+
if (!srcView) {
|
|
1086
|
+
throw new RenderDeviceError("WebGPU FXAA source target has no native view", "NATIVE_POST_UNSUPPORTED", { label: src.label });
|
|
1087
|
+
}
|
|
1088
|
+
const out = this.createRenderTarget({ width: src.width, height: src.height, format: "rgba8", label: "a3d-webgpu-fxaa" });
|
|
1089
|
+
try {
|
|
1090
|
+
this.runWebGPUFullscreenPass({
|
|
1091
|
+
label: "webgpu-fxaa",
|
|
1092
|
+
fragment: webgpuFxaaFragment(),
|
|
1093
|
+
entryPoint: "fs_fxaa",
|
|
1094
|
+
uniforms: new Float32Array([1 / src.width, 1 / src.height, 0, 0]),
|
|
1095
|
+
views: [srcView],
|
|
1096
|
+
target: out
|
|
1097
|
+
});
|
|
1098
|
+
this.nativeFxaaPasses += 1;
|
|
1099
|
+
return out;
|
|
1100
|
+
}
|
|
1101
|
+
catch (error) {
|
|
1102
|
+
out.dispose();
|
|
1103
|
+
throw error;
|
|
1104
|
+
}
|
|
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
|
+
}
|
|
1198
|
+
runWebGPUFullscreenPass(args) {
|
|
1199
|
+
// No assertFrame here on purpose: these are target-to-target passes with
|
|
1200
|
+
// their own encoders, so they run both inside a Renderer frame (scene ->
|
|
1201
|
+
// post chaining) and after it closed (proof harnesses that render, then
|
|
1202
|
+
// post, then read back).
|
|
1203
|
+
this.assertAlive();
|
|
1204
|
+
const target = args.target ? this.requireRenderTarget(args.target) : null;
|
|
1205
|
+
const targetView = target ? target.nativeView : this.canvasContext?.getCurrentTexture().createView();
|
|
1206
|
+
if (!targetView) {
|
|
1207
|
+
throw new RenderDeviceError("WebGPU post target has no native view", "NATIVE_POST_UNSUPPORTED", { label: target?.label ?? "canvas" });
|
|
1208
|
+
}
|
|
1209
|
+
if (!this.device.createShaderModule || !this.device.createRenderPipeline || !this.device.createCommandEncoder
|
|
1210
|
+
|| !this.device.createSampler || !this.device.createBuffer || !this.device.createBindGroup) {
|
|
1211
|
+
throw new RenderDeviceError("WebGPU fullscreen post requires shader/pipeline/sampler/buffer natives", "NATIVE_POST_UNSUPPORTED", { label: args.label });
|
|
1212
|
+
}
|
|
1213
|
+
const scopeDevice = this.device;
|
|
1214
|
+
const scopedErrors = Boolean(scopeDevice.pushErrorScope && scopeDevice.popErrorScope);
|
|
1215
|
+
if (scopedErrors) {
|
|
1216
|
+
// Bracket everything below (module/pipeline/bind-group creation plus
|
|
1217
|
+
// the submit) so per-pass validation failures land here labeled.
|
|
1218
|
+
scopeDevice.pushErrorScope?.("validation");
|
|
1219
|
+
}
|
|
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}`;
|
|
1224
|
+
let pipeline = this.webgpuPostPipelines.get(cacheKey);
|
|
1225
|
+
if (!pipeline) {
|
|
1226
|
+
const vertexModule = this.device.createShaderModule({ label: `${args.label}-vs`, code: WEBGPU_POST_VERTEX_WGSL });
|
|
1227
|
+
const fragmentModule = this.device.createShaderModule({ label: `${args.label}-fs`, code: args.fragment });
|
|
1228
|
+
pipeline = this.device.createRenderPipeline({
|
|
1229
|
+
label: `${args.label}-pipeline`,
|
|
1230
|
+
layout: "auto",
|
|
1231
|
+
vertex: { module: vertexModule, entryPoint: "vs_post" },
|
|
1232
|
+
fragment: { module: fragmentModule, entryPoint: args.entryPoint, targets: [{ format }] },
|
|
1233
|
+
primitive: { topology: "triangle-list" }
|
|
1234
|
+
});
|
|
1235
|
+
this.nativeRenderPipelinesCreated += 1;
|
|
1236
|
+
this.webgpuPostPipelines.set(cacheKey, pipeline);
|
|
1237
|
+
}
|
|
1238
|
+
if (!this.webgpuPostLinearSampler) {
|
|
1239
|
+
this.webgpuPostLinearSampler = this.device.createSampler({
|
|
1240
|
+
minFilter: "linear",
|
|
1241
|
+
magFilter: "linear",
|
|
1242
|
+
addressModeU: "clamp-to-edge",
|
|
1243
|
+
addressModeV: "clamp-to-edge"
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
const uniformBuffer = this.device.createBuffer({
|
|
1247
|
+
label: `${args.label}-uniforms`,
|
|
1248
|
+
size: args.uniforms.byteLength,
|
|
1249
|
+
usage: BUFFER_USAGE.UNIFORM | BUFFER_USAGE.COPY_DST
|
|
1250
|
+
});
|
|
1251
|
+
let submitted = false;
|
|
1252
|
+
try {
|
|
1253
|
+
this.device.queue.writeBuffer(uniformBuffer, 0, args.uniforms);
|
|
1254
|
+
const layout = pipeline.getBindGroupLayout?.(0);
|
|
1255
|
+
if (!layout)
|
|
1256
|
+
throw new RenderDeviceError("WebGPU post pipeline has no bind-group layout", "NATIVE_POST_UNSUPPORTED", { label: args.label });
|
|
1257
|
+
const group = this.device.createBindGroup({
|
|
1258
|
+
label: `${args.label}-bind`,
|
|
1259
|
+
layout,
|
|
1260
|
+
entries: [
|
|
1261
|
+
{ binding: 0, resource: { buffer: uniformBuffer } },
|
|
1262
|
+
{ binding: 1, resource: this.webgpuPostLinearSampler },
|
|
1263
|
+
...args.views.map((view, index) => ({ binding: 2 + index, resource: view }))
|
|
1264
|
+
]
|
|
1265
|
+
});
|
|
1266
|
+
const encoder = this.device.createCommandEncoder({ label: `${args.label}-encoder` });
|
|
1267
|
+
const pass = encoder.beginRenderPass({
|
|
1268
|
+
label: `${args.label}-pass`,
|
|
1269
|
+
colorAttachments: [{ view: targetView, loadOp: "load", storeOp: "store" }]
|
|
1270
|
+
});
|
|
1271
|
+
pass.setPipeline(pipeline);
|
|
1272
|
+
pass.setBindGroup?.(0, group);
|
|
1273
|
+
pass.draw(3, 1, 0);
|
|
1274
|
+
pass.end();
|
|
1275
|
+
this.device.queue.submit([encoder.finish()]);
|
|
1276
|
+
submitted = true;
|
|
1277
|
+
// The pass owns every pixel (fullscreen triangle), so a later
|
|
1278
|
+
// readPixelsAsync must NOT flush a stale pending clear over it.
|
|
1279
|
+
if (target)
|
|
1280
|
+
target.nativeNeedsClear = false;
|
|
1281
|
+
this.nativeRenderPasses += 1;
|
|
1282
|
+
// Retired, not destroyed: the submit only enqueues; the pass reads
|
|
1283
|
+
// these uniforms when the GPU gets to it.
|
|
1284
|
+
this.webgpuPostRetiredUniformBuffers.push(uniformBuffer);
|
|
1285
|
+
}
|
|
1286
|
+
catch (error) {
|
|
1287
|
+
if (!submitted)
|
|
1288
|
+
uniformBuffer.destroy();
|
|
1289
|
+
throw error;
|
|
1290
|
+
}
|
|
1291
|
+
finally {
|
|
1292
|
+
if (scopedErrors && scopeDevice.popErrorScope) {
|
|
1293
|
+
const pending = scopeDevice.popErrorScope().then((info) => {
|
|
1294
|
+
if (info?.message)
|
|
1295
|
+
this.webgpuPostErrors.push(`[${args.label}] ${info.message}`);
|
|
1296
|
+
}, () => undefined);
|
|
1297
|
+
this.webgpuPostErrorScopePending.push(pending);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
reclaimWebGPUPostUniformBuffers() {
|
|
1302
|
+
for (const buffer of this.webgpuPostRetiredUniformBuffers)
|
|
1303
|
+
buffer.destroy();
|
|
1304
|
+
this.webgpuPostRetiredUniformBuffers = [];
|
|
1305
|
+
}
|
|
847
1306
|
dispose() {
|
|
848
1307
|
if (this.disposed)
|
|
849
1308
|
return;
|
|
1309
|
+
this.reclaimWebGPUPostUniformBuffers();
|
|
850
1310
|
for (const buffer of this.buffers)
|
|
851
1311
|
buffer.dispose();
|
|
852
1312
|
for (const shader of this.shaders)
|
|
@@ -878,6 +1338,8 @@ export class WebGPUDevice {
|
|
|
878
1338
|
throw new RenderDeviceError("No active frame", "NO_ACTIVE_FRAME");
|
|
879
1339
|
}
|
|
880
1340
|
}
|
|
1341
|
+
temporalLossListeners = new Set();
|
|
1342
|
+
onDeviceLost(listener) { this.temporalLossListeners.add(listener); return () => this.temporalLossListeners.delete(listener); }
|
|
881
1343
|
observeDeviceLost() {
|
|
882
1344
|
const lost = this.device.lost;
|
|
883
1345
|
if (!lost || typeof lost.then !== "function")
|
|
@@ -886,12 +1348,24 @@ export class WebGPUDevice {
|
|
|
886
1348
|
if (this.disposed)
|
|
887
1349
|
return;
|
|
888
1350
|
this.contextLost = true;
|
|
1351
|
+
for (const listener of this.temporalLossListeners) {
|
|
1352
|
+
try {
|
|
1353
|
+
listener();
|
|
1354
|
+
}
|
|
1355
|
+
catch { /* Continue notifying owners. */ }
|
|
1356
|
+
}
|
|
889
1357
|
const detail = [info.reason, info.message].filter(Boolean).join(": ");
|
|
890
1358
|
this.lastError = detail ? `WebGPU device lost: ${detail}` : "WebGPU device lost";
|
|
891
1359
|
}).catch((error) => {
|
|
892
1360
|
if (this.disposed)
|
|
893
1361
|
return;
|
|
894
1362
|
this.contextLost = true;
|
|
1363
|
+
for (const listener of this.temporalLossListeners) {
|
|
1364
|
+
try {
|
|
1365
|
+
listener();
|
|
1366
|
+
}
|
|
1367
|
+
catch { /* Continue notifying owners. */ }
|
|
1368
|
+
}
|
|
895
1369
|
this.lastError = `WebGPU device lost promise rejected: ${error instanceof Error ? error.message : String(error)}`;
|
|
896
1370
|
});
|
|
897
1371
|
}
|
|
@@ -965,9 +1439,9 @@ export class WebGPUDevice {
|
|
|
965
1439
|
const worldA = transformPosition(localA, modelMatrix);
|
|
966
1440
|
const worldB = transformPosition(localB, modelMatrix);
|
|
967
1441
|
const worldC = transformPosition(localC, modelMatrix);
|
|
968
|
-
const colorA = readVertexColor(vertexBuffer.bytes, command.vertexFormat.stride, colorAttribute?.offset, indexA);
|
|
969
|
-
const colorB = readVertexColor(vertexBuffer.bytes, command.vertexFormat.stride, colorAttribute?.offset, indexB);
|
|
970
|
-
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);
|
|
971
1445
|
const uvA = readVertexUv(vertexBuffer.bytes, command.vertexFormat.stride, uvAttribute?.offset, indexA);
|
|
972
1446
|
const uvB = readVertexUv(vertexBuffer.bytes, command.vertexFormat.stride, uvAttribute?.offset, indexB);
|
|
973
1447
|
const uvC = readVertexUv(vertexBuffer.bytes, command.vertexFormat.stride, uvAttribute?.offset, indexC);
|
|
@@ -980,15 +1454,15 @@ export class WebGPUDevice {
|
|
|
980
1454
|
const indexB = indices[offset + 1];
|
|
981
1455
|
const a = transformPosition(readLocal(indexA), matrix);
|
|
982
1456
|
const b = transformPosition(readLocal(indexB), matrix);
|
|
983
|
-
const colorA = readVertexColor(vertexBuffer.bytes, command.vertexFormat.stride, colorAttribute?.offset, indexA);
|
|
984
|
-
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);
|
|
985
1459
|
rasterizeLine(this.activeRenderTarget, a, b, color, colorA, colorB, depthTest, depthWrite);
|
|
986
1460
|
}
|
|
987
1461
|
}
|
|
988
1462
|
else {
|
|
989
1463
|
for (const index of indices) {
|
|
990
1464
|
const point = transformPosition(readLocal(index), matrix);
|
|
991
|
-
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);
|
|
992
1466
|
rasterizePoint(this.activeRenderTarget, point, multiplyColor(color, vertexColor), depthTest, depthWrite);
|
|
993
1467
|
}
|
|
994
1468
|
}
|
|
@@ -1002,7 +1476,27 @@ export class WebGPUDevice {
|
|
|
1002
1476
|
if (!hasNativeRenderPipeline(this.device))
|
|
1003
1477
|
return;
|
|
1004
1478
|
const shader = this.requireShader(command.shader);
|
|
1005
|
-
|
|
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
|
+
}
|
|
1006
1500
|
const fragmentModule = shader.modules[1] ?? vertexModule;
|
|
1007
1501
|
if (!vertexModule || !fragmentModule)
|
|
1008
1502
|
return;
|
|
@@ -1062,12 +1556,19 @@ export class WebGPUDevice {
|
|
|
1062
1556
|
const uniformBuffer = usesNativeDrawUniforms(shader.nativeUniformLayout) ? this.createNativeDrawUniformBuffer(command) : null;
|
|
1063
1557
|
const sampledTexture = shader.nativeUniformLayout === "generated-texture" ? this.createNativeSampledTextureBinding(uniformBaseColorTextureBinding(command.uniforms)) : null;
|
|
1064
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
|
+
}
|
|
1065
1564
|
if (shader.nativeUniformLayout === "generated-texture" && !sampledTexture) {
|
|
1066
1565
|
uniformBuffer?.destroy();
|
|
1566
|
+
atlasResources?.buffer.destroy();
|
|
1067
1567
|
return;
|
|
1068
1568
|
}
|
|
1069
1569
|
if (usesNativePbrTextureBindings(shader.nativeUniformLayout) && !materialTextures) {
|
|
1070
1570
|
uniformBuffer?.destroy();
|
|
1571
|
+
atlasResources?.buffer.destroy();
|
|
1071
1572
|
return;
|
|
1072
1573
|
}
|
|
1073
1574
|
if (shader.nativeUniformLayout === "portable" && !portableResources)
|
|
@@ -1078,6 +1579,7 @@ export class WebGPUDevice {
|
|
|
1078
1579
|
layout: pipeline.getBindGroupLayout(0),
|
|
1079
1580
|
entries: portableResources?.entries ?? [
|
|
1080
1581
|
{ binding: 0, resource: { buffer: uniformBuffer } },
|
|
1582
|
+
...(atlasResources?.entries ?? []),
|
|
1081
1583
|
...(sampledTexture ? [
|
|
1082
1584
|
{ binding: 1, resource: sampledTexture.sampler },
|
|
1083
1585
|
{ binding: 2, resource: sampledTexture.view }
|
|
@@ -1098,7 +1600,9 @@ export class WebGPUDevice {
|
|
|
1098
1600
|
{ binding: 13, resource: materialTextures.occlusion.sampler },
|
|
1099
1601
|
{ binding: 14, resource: materialTextures.occlusion.view },
|
|
1100
1602
|
{ binding: 15, resource: materialTextures.clusterLightData.view },
|
|
1101
|
-
{ binding: 16, resource: materialTextures.clusterLightIndices.view }
|
|
1603
|
+
{ binding: 16, resource: materialTextures.clusterLightIndices.view },
|
|
1604
|
+
{ binding: 17, resource: materialTextures.spotShadow.sampler },
|
|
1605
|
+
{ binding: 18, resource: materialTextures.spotShadow.view }
|
|
1102
1606
|
] : [])
|
|
1103
1607
|
]
|
|
1104
1608
|
})
|
|
@@ -1132,6 +1636,7 @@ export class WebGPUDevice {
|
|
|
1132
1636
|
if (!pass.setIndexBuffer || !pass.drawIndexed) {
|
|
1133
1637
|
pass.end();
|
|
1134
1638
|
uniformBuffer?.destroy();
|
|
1639
|
+
atlasResources?.buffer.destroy();
|
|
1135
1640
|
portableResources?.buffer.destroy();
|
|
1136
1641
|
this.lastError = "Native WebGPU indexed draw skipped because the render-pass encoder does not expose indexed draw APIs.";
|
|
1137
1642
|
return;
|
|
@@ -1148,6 +1653,7 @@ export class WebGPUDevice {
|
|
|
1148
1653
|
if (this.activeRenderTarget)
|
|
1149
1654
|
this.activeRenderTarget.nativeNeedsClear = false;
|
|
1150
1655
|
uniformBuffer?.destroy();
|
|
1656
|
+
atlasResources?.buffer.destroy();
|
|
1151
1657
|
portableResources?.buffer.destroy();
|
|
1152
1658
|
this.nativeSubmissions += 1;
|
|
1153
1659
|
if (sampledTexture)
|
|
@@ -1162,7 +1668,7 @@ export class WebGPUDevice {
|
|
|
1162
1668
|
this.nativePassthroughSubmissions += 1;
|
|
1163
1669
|
this.nativeTextureBindings += portableResources?.actualTextureCount ?? 0;
|
|
1164
1670
|
}
|
|
1165
|
-
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")
|
|
1166
1672
|
this.nativePbrSubmissions += 1;
|
|
1167
1673
|
if (shader.nativeUniformLayout === "generated-instanced-pbr")
|
|
1168
1674
|
this.nativeInstancedSubmissions += 1;
|
|
@@ -1170,6 +1676,7 @@ export class WebGPUDevice {
|
|
|
1170
1676
|
this.nativeSkinnedSubmissions += 1;
|
|
1171
1677
|
if (shader.nativeUniformLayout === "generated-morph-unlit")
|
|
1172
1678
|
this.nativeMorphSubmissions += 1;
|
|
1679
|
+
this.nativeTextureBindings += atlasResources?.actualTextureCount ?? 0;
|
|
1173
1680
|
if (materialTextures?.actualBaseColor)
|
|
1174
1681
|
this.nativeTextureBindings += 1;
|
|
1175
1682
|
if (materialTextures?.actualNormal)
|
|
@@ -1184,6 +1691,8 @@ export class WebGPUDevice {
|
|
|
1184
1691
|
this.nativeEnvironmentBindings += 1;
|
|
1185
1692
|
if (materialTextures?.actualShadow)
|
|
1186
1693
|
this.nativeShadowMapBindings += 1;
|
|
1694
|
+
if (materialTextures?.actualSpotShadow)
|
|
1695
|
+
this.nativeShadowMapBindings += 1;
|
|
1187
1696
|
if (!this.activeRenderTarget)
|
|
1188
1697
|
this.canvasSubmissions += 1;
|
|
1189
1698
|
}
|
|
@@ -1221,6 +1730,20 @@ export class WebGPUDevice {
|
|
|
1221
1730
|
if (!binding?.texture || binding.texture.disposed || !binding.validate().ok || !hasNativeSampledTextureBinding(this.device))
|
|
1222
1731
|
return null;
|
|
1223
1732
|
const texture = binding.texture;
|
|
1733
|
+
// J2 zero-copy shadow path: a render target's native color texture is
|
|
1734
|
+
// created with TEXTURE_BINDING usage, so a shadow map rendered natively
|
|
1735
|
+
// (ShadowPass "encoded-color-depth") samples directly — no CPU readback
|
|
1736
|
+
// round-trip. Same-queue submission order guarantees the depth writes
|
|
1737
|
+
// are visible to the later lit pass.
|
|
1738
|
+
for (const target of this.renderTargets) {
|
|
1739
|
+
if (target.disposed || target.sampleCount !== 1 || target.colorTexture !== texture || !target.nativeView)
|
|
1740
|
+
continue;
|
|
1741
|
+
return {
|
|
1742
|
+
view: target.nativeView,
|
|
1743
|
+
sampler: this.device.createSampler(webgpuSamplerDescriptor(binding.sampler)),
|
|
1744
|
+
actual: true
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1224
1747
|
let resource = this.nativeSampledTextures.get(texture);
|
|
1225
1748
|
if (!resource) {
|
|
1226
1749
|
if (texture.source) {
|
|
@@ -1290,6 +1813,7 @@ export class WebGPUDevice {
|
|
|
1290
1813
|
const environmentBinding = uniformTextureBinding(command.uniforms, "u_environmentMapTexture");
|
|
1291
1814
|
const brdfBinding = uniformTextureBinding(command.uniforms, "u_environmentBrdfLutTexture");
|
|
1292
1815
|
const shadowBinding = uniformTextureBinding(command.uniforms, "u_shadowMapTexture");
|
|
1816
|
+
const spotShadowBinding = uniformTextureBinding(command.uniforms, "u_spotShadowMapTexture");
|
|
1293
1817
|
const normalBinding = uniformTextureBinding(command.uniforms, "u_normalTexture");
|
|
1294
1818
|
const metallicRoughnessBinding = uniformTextureBinding(command.uniforms, "u_metallicRoughnessTexture");
|
|
1295
1819
|
const occlusionBinding = uniformTextureBinding(command.uniforms, "u_occlusionTexture");
|
|
@@ -1299,18 +1823,20 @@ export class WebGPUDevice {
|
|
|
1299
1823
|
const environment = this.createNativeSampledTextureBinding(environmentBinding) ?? this.createNativeFallbackSampledTextureBinding("environment-srgb", [92, 116, 156, 255], "srgb");
|
|
1300
1824
|
const brdf = this.createNativeSampledTextureBinding(brdfBinding) ?? this.createNativeFallbackSampledTextureBinding("brdf-linear", [180, 180, 255, 255], "linear");
|
|
1301
1825
|
const shadow = this.createNativeSampledTextureBinding(shadowBinding) ?? this.createNativeFallbackSampledTextureBinding("shadow-lit-linear", [255, 255, 255, 255], "linear");
|
|
1826
|
+
const spotShadow = this.createNativeSampledTextureBinding(spotShadowBinding) ?? this.createNativeFallbackSampledTextureBinding("spot-shadow-lit-linear", [255, 255, 255, 255], "linear");
|
|
1302
1827
|
const normal = this.createNativeSampledTextureBinding(normalBinding) ?? this.createNativeFallbackSampledTextureBinding("flat-normal-linear", [128, 128, 255, 255], "linear");
|
|
1303
1828
|
const metallicRoughness = this.createNativeSampledTextureBinding(metallicRoughnessBinding) ?? this.createNativeFallbackSampledTextureBinding("metallic-roughness-linear", [255, 255, 255, 255], "linear");
|
|
1304
1829
|
const occlusion = this.createNativeSampledTextureBinding(occlusionBinding) ?? this.createNativeFallbackSampledTextureBinding("occlusion-linear", [255, 255, 255, 255], "linear");
|
|
1305
1830
|
const clusterLightData = this.createNativeSampledTextureBinding(clusterLightDataBinding) ?? this.createNativeFallbackSampledTextureBinding("cluster-light-data-linear", [0, 0, 0, 0], "linear");
|
|
1306
1831
|
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)
|
|
1832
|
+
if (!base || !environment || !brdf || !shadow || !spotShadow || !normal || !metallicRoughness || !occlusion || !clusterLightData || !clusterLightIndices)
|
|
1308
1833
|
return null;
|
|
1309
1834
|
return {
|
|
1310
1835
|
base,
|
|
1311
1836
|
environment,
|
|
1312
1837
|
brdf,
|
|
1313
1838
|
shadow,
|
|
1839
|
+
spotShadow,
|
|
1314
1840
|
normal,
|
|
1315
1841
|
metallicRoughness,
|
|
1316
1842
|
occlusion,
|
|
@@ -1320,6 +1846,7 @@ export class WebGPUDevice {
|
|
|
1320
1846
|
actualEnvironment: environmentBinding !== null && environment.actual,
|
|
1321
1847
|
actualBrdf: brdfBinding !== null && brdf.actual,
|
|
1322
1848
|
actualShadow: shadowBinding !== null && shadow.actual,
|
|
1849
|
+
actualSpotShadow: spotShadowBinding !== null && spotShadow.actual,
|
|
1323
1850
|
actualNormal: normalBinding !== null && normal.actual,
|
|
1324
1851
|
actualMetallicRoughness: metallicRoughnessBinding !== null && metallicRoughness.actual,
|
|
1325
1852
|
actualOcclusion: occlusionBinding !== null && occlusion.actual
|
|
@@ -1355,8 +1882,10 @@ export class WebGPUDevice {
|
|
|
1355
1882
|
createNativeDrawUniformBuffer(command) {
|
|
1356
1883
|
if (!this.device.createBindGroup)
|
|
1357
1884
|
return null;
|
|
1358
|
-
// 188 floats for the scalar/matrix fields + 96 joint matrices (16 floats each) appended at 188
|
|
1359
|
-
|
|
1885
|
+
// 188 floats for the scalar/matrix fields + 96 joint matrices (16 floats each) appended at 188
|
|
1886
|
+
// + J2 native shadow projections (312 floats at 1724: directional matrix/params/PCF, then spot)
|
|
1887
|
+
// + model matrix (16 floats at 2036) for instanced world composition.
|
|
1888
|
+
const data = new Float32Array(2052);
|
|
1360
1889
|
const baseColorBinding = uniformBaseColorTextureBinding(command.uniforms);
|
|
1361
1890
|
const environmentBinding = uniformTextureBinding(command.uniforms, "u_environmentMapTexture");
|
|
1362
1891
|
const normalBinding = uniformTextureBinding(command.uniforms, "u_normalTexture");
|
|
@@ -1408,6 +1937,37 @@ export class WebGPUDevice {
|
|
|
1408
1937
|
for (let index = 0; index < MAX_WEBGPU_SKINNING_JOINTS; index += 1) {
|
|
1409
1938
|
data.set(jointMatrices[index] ?? identityMatrix(), 188 + index * 16);
|
|
1410
1939
|
}
|
|
1940
|
+
// J2 native shadow projections (WGSL parity with a3dForwardShadowFactor /
|
|
1941
|
+
// a3dSpotShadowFactor). Identity matrices + zero enabled flags keep every
|
|
1942
|
+
// shadow factor at exactly 1.0 (lit) when the ForwardPass gates stay shut.
|
|
1943
|
+
data.set(uniformMat4(command.uniforms?.get("u_shadowMapMatrix")) ?? identityMatrix(), 1724);
|
|
1944
|
+
const shadowTexel = uniformVec2(command.uniforms?.get("u_shadowMapTexelSize")) ?? [1, 1];
|
|
1945
|
+
data.set([
|
|
1946
|
+
uniformNumber(command.uniforms?.get("u_shadowMapBias"), 0.001),
|
|
1947
|
+
uniformNumber(command.uniforms?.get("u_shadowMapSlopeBias"), 1),
|
|
1948
|
+
shadowTexel[0],
|
|
1949
|
+
shadowTexel[1]
|
|
1950
|
+
], 1740);
|
|
1951
|
+
data.set([uniformNumber(command.uniforms?.get("u_shadowPcfSampleCount"), 1), 0, 0, 0], 1744);
|
|
1952
|
+
setNativeShadowPcfSamples(data, command.uniforms?.get("u_shadowPcfSamples"), 1748);
|
|
1953
|
+
data.set(uniformMat4(command.uniforms?.get("u_spotShadowMatrix")) ?? identityMatrix(), 1876);
|
|
1954
|
+
const spotLightPosition = uniformVec3(command.uniforms?.get("u_spotShadowLightPosition")) ?? [0, 0, 0];
|
|
1955
|
+
data.set([spotLightPosition[0], spotLightPosition[1], spotLightPosition[2], uniformNumber(command.uniforms?.get("u_spotShadowRange"), 1)], 1892);
|
|
1956
|
+
const spotLightDirection = uniformVec3(command.uniforms?.get("u_spotShadowLightDirection")) ?? [0, -1, 0];
|
|
1957
|
+
data.set([spotLightDirection[0], spotLightDirection[1], spotLightDirection[2], 0], 1896);
|
|
1958
|
+
data.set([
|
|
1959
|
+
uniformNumber(command.uniforms?.get("u_spotShadowMapEnabled"), 0),
|
|
1960
|
+
uniformNumber(command.uniforms?.get("u_spotShadowStrength"), 0.65),
|
|
1961
|
+
uniformNumber(command.uniforms?.get("u_spotShadowBias"), 0.001),
|
|
1962
|
+
uniformNumber(command.uniforms?.get("u_spotShadowSlopeBias"), 1)
|
|
1963
|
+
], 1900);
|
|
1964
|
+
const spotTexel = uniformVec2(command.uniforms?.get("u_spotShadowTexelSize")) ?? [1, 1];
|
|
1965
|
+
const spotCone = uniformVec2(command.uniforms?.get("u_spotShadowCone")) ?? [Math.PI / 4, 0];
|
|
1966
|
+
data.set([spotTexel[0], spotTexel[1], uniformNumber(command.uniforms?.get("u_spotShadowPcfSampleCount"), 1), spotCone[0]], 1904);
|
|
1967
|
+
setNativeShadowPcfSamples(data, command.uniforms?.get("u_spotShadowPcfSamples"), 1908);
|
|
1968
|
+
// Model matrix for the instanced vertex world composition
|
|
1969
|
+
// (u_modelMatrix * instanceMatrix * position, GLSL parity).
|
|
1970
|
+
data.set(modelMatrix, 2036);
|
|
1411
1971
|
const morphDeltas = command.uniforms?.get("u_morphPositionDeltas");
|
|
1412
1972
|
const morphNumbers = morphDeltas instanceof Float32Array || Array.isArray(morphDeltas) ? Array.from(morphDeltas).slice(0, 32) : [];
|
|
1413
1973
|
if (morphNumbers.length > 0)
|
|
@@ -1420,6 +1980,36 @@ export class WebGPUDevice {
|
|
|
1420
1980
|
this.device.queue.writeBuffer(buffer, 0, data);
|
|
1421
1981
|
return buffer;
|
|
1422
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
|
+
}
|
|
1423
2013
|
createNativePortableBindings(command, bindings) {
|
|
1424
2014
|
if (!this.device.createBindGroup)
|
|
1425
2015
|
return null;
|
|
@@ -1726,6 +2316,7 @@ function usesNativeDrawUniforms(layout) {
|
|
|
1726
2316
|
}
|
|
1727
2317
|
function usesNativePbrTextureBindings(layout) {
|
|
1728
2318
|
return layout === "generated-pbr" ||
|
|
2319
|
+
layout === "generated-atlas-pbr" ||
|
|
1729
2320
|
layout === "generated-textured-pbr" ||
|
|
1730
2321
|
layout === "generated-instanced-pbr";
|
|
1731
2322
|
}
|
|
@@ -1806,6 +2397,15 @@ function uniformVec2(value) {
|
|
|
1806
2397
|
? [components[0], components[1]]
|
|
1807
2398
|
: null;
|
|
1808
2399
|
}
|
|
2400
|
+
function setNativeShadowPcfSamples(data, value, offset) {
|
|
2401
|
+
const numbers = value instanceof Float32Array || Array.isArray(value) ? Array.from(value) : [];
|
|
2402
|
+
for (let index = 0; index < 32; index += 1) {
|
|
2403
|
+
const quad = numbers.slice(index * 4, index * 4 + 4);
|
|
2404
|
+
// Default single center tap (weight 1) so an enabled shadow map without
|
|
2405
|
+
// an explicit kernel still occludes instead of dividing by zero weight.
|
|
2406
|
+
data.set(quad.length === 4 && quad.every(Number.isFinite) ? quad : [0, 0, 1, 0], offset + index * 4);
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
1809
2409
|
function uniformMat4Array(value, count) {
|
|
1810
2410
|
const numbers = value instanceof Float32Array || Array.isArray(value) ? Array.from(value) : [];
|
|
1811
2411
|
return Array.from({ length: count }, (_, index) => {
|
|
@@ -1972,7 +2572,7 @@ function jointPaletteFor(uniforms) {
|
|
|
1972
2572
|
const count = Math.min(MAX_WEBGPU_SKINNING_JOINTS, Math.floor(length / 16));
|
|
1973
2573
|
return count > 0 ? uniformMat4Array(value, count) : [];
|
|
1974
2574
|
}
|
|
1975
|
-
function readVertexColor(bytes, stride, offset, vertexIndex) {
|
|
2575
|
+
function readVertexColor(bytes, stride, offset, vertexIndex, components = 4) {
|
|
1976
2576
|
if (offset === undefined)
|
|
1977
2577
|
return [1, 1, 1, 1];
|
|
1978
2578
|
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
@@ -1981,7 +2581,7 @@ function readVertexColor(bytes, stride, offset, vertexIndex) {
|
|
|
1981
2581
|
view.getFloat32(base, true),
|
|
1982
2582
|
view.getFloat32(base + 4, true),
|
|
1983
2583
|
view.getFloat32(base + 8, true),
|
|
1984
|
-
view.getFloat32(base + 12, true)
|
|
2584
|
+
components >= 4 ? view.getFloat32(base + 12, true) : 1
|
|
1985
2585
|
];
|
|
1986
2586
|
}
|
|
1987
2587
|
function readVertexUv(bytes, stride, offset, vertexIndex) {
|
|
@@ -2166,7 +2766,9 @@ function createNativeShaderSources(sources) {
|
|
|
2166
2766
|
return { ...nativeInstancedPbrShader(vertexEntry, fragmentEntry, sources.marker), entryPoints: [vertexEntry, fragmentEntry], uniformLayout: "generated-instanced-pbr" };
|
|
2167
2767
|
}
|
|
2168
2768
|
if (sources.marker.includes("pbr-textured")) {
|
|
2169
|
-
|
|
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" };
|
|
2170
2772
|
}
|
|
2171
2773
|
if (sources.marker.includes("pbr-direct")) {
|
|
2172
2774
|
return sources.fragment.includes("u_baseColorTexture")
|
|
@@ -2180,22 +2782,100 @@ function createNativeShaderSources(sources) {
|
|
|
2180
2782
|
return { ...nativeMorphUnlitShader(vertexEntry, fragmentEntry, sources.marker), entryPoints: [vertexEntry, fragmentEntry], uniformLayout: "generated-morph-unlit" };
|
|
2181
2783
|
}
|
|
2182
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
|
+
`;
|
|
2183
2810
|
return {
|
|
2184
|
-
vertex
|
|
2185
|
-
|
|
2186
|
-
fragment: `// ${sources.marker}
|
|
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
|
+
`,
|
|
2187
2823
|
entryPoints: [vertexEntry, fragmentEntry],
|
|
2188
2824
|
uniformLayout: "generated-texture"
|
|
2189
2825
|
};
|
|
2190
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
|
+
`;
|
|
2191
2855
|
return {
|
|
2192
|
-
vertex
|
|
2193
|
-
|
|
2194
|
-
fragment:
|
|
2856
|
+
vertex,
|
|
2857
|
+
colorDefaultVertex: colorInput ? vertex.replace(colorParameter, "").replace(/output\.color = [^;]+;/, "output.color = vec4<f32>(1.0);") : undefined,
|
|
2858
|
+
fragment: sources.marker.includes(DEFAULT_DEPTH_SHADER_MARKER)
|
|
2859
|
+
? nativeDepthFragment(sources.marker, fragmentEntry)
|
|
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
|
+
`,
|
|
2195
2868
|
entryPoints: [vertexEntry, fragmentEntry],
|
|
2196
2869
|
uniformLayout: "generated-basic"
|
|
2197
2870
|
};
|
|
2198
2871
|
}
|
|
2872
|
+
function nativeDepthFragment(marker, fragmentEntry) {
|
|
2873
|
+
// J2 native depth encoding: the GLSL depth program writes gl_FragCoord.z
|
|
2874
|
+
// into an rgba8 shadow target ("encoded-color-depth"). The framebuffer
|
|
2875
|
+
// depth @builtin(position).z is already 0..1 in WebGPU, so packing it
|
|
2876
|
+
// samples identically to the GL path through the projective PCF reader.
|
|
2877
|
+
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`;
|
|
2878
|
+
}
|
|
2199
2879
|
const PORTABLE_BINDINGS_MARKER = "/* @aura3d-bindings */";
|
|
2200
2880
|
function injectPortableBindings(source, bindings) {
|
|
2201
2881
|
if (!source.includes(PORTABLE_BINDINGS_MARKER)) {
|
|
@@ -2299,6 +2979,23 @@ function nativeUniformStruct() {
|
|
|
2299
2979
|
// 96-joint skinning palette (WebGL2 \`u_jointMatrices[96]\` parity). Appended at the struct end so
|
|
2300
2980
|
// the existing field offsets are unchanged.
|
|
2301
2981
|
joints: array<mat4x4<f32>, 96>,
|
|
2982
|
+
// J2 native shadow projections (float offsets 1724+, after the 96-joint
|
|
2983
|
+
// palette). Directional block mirrors GLSL \`a3dForwardShadowFactor\`;
|
|
2984
|
+
// spot block mirrors \`a3dSpotShadowFactor\` (cone/range pre-check inside
|
|
2985
|
+
// the helper). Appended here so no existing field offset moves.
|
|
2986
|
+
shadowMapMatrix: mat4x4<f32>,
|
|
2987
|
+
shadowParams0: vec4<f32>,
|
|
2988
|
+
shadowParams1: vec4<f32>,
|
|
2989
|
+
shadowPcf: array<vec4<f32>, 32>,
|
|
2990
|
+
spotShadowMatrix: mat4x4<f32>,
|
|
2991
|
+
spotShadowParams0: vec4<f32>,
|
|
2992
|
+
spotShadowParams1: vec4<f32>,
|
|
2993
|
+
spotShadowParams2: vec4<f32>,
|
|
2994
|
+
spotShadowParams3: vec4<f32>,
|
|
2995
|
+
spotShadowPcf: array<vec4<f32>, 32>,
|
|
2996
|
+
// Model matrix for GLSL-parity world composition on the instanced path
|
|
2997
|
+
// (\`u_modelMatrix * instanceMatrix * position\`; float offset 2036).
|
|
2998
|
+
modelMatrix: mat4x4<f32>,
|
|
2302
2999
|
};
|
|
2303
3000
|
|
|
2304
3001
|
@group(0) @binding(0) var<uniform> u_draw: DrawUniforms;
|
|
@@ -2311,7 +3008,7 @@ fn a3dWebGPUClipPosition(clipPosition: vec4<f32>) -> vec4<f32> {
|
|
|
2311
3008
|
function nativeShaderUsesTangent(vertexSource) {
|
|
2312
3009
|
return /\ba_tangent\b/.test(vertexSource) || /layout\s*\(\s*location\s*=\s*3\s*\)\s*in\s+vec4/.test(vertexSource);
|
|
2313
3010
|
}
|
|
2314
|
-
function nativePbrFragmentPrelude(marker) {
|
|
3011
|
+
function nativePbrFragmentPrelude(marker, atlas = false) {
|
|
2315
3012
|
return `// ${marker}
|
|
2316
3013
|
${nativeUniformStruct()}
|
|
2317
3014
|
@group(0) @binding(1) var u_baseSampler: sampler;
|
|
@@ -2330,6 +3027,10 @@ ${nativeUniformStruct()}
|
|
|
2330
3027
|
@group(0) @binding(14) var u_occlusionTexture: texture_2d<f32>;
|
|
2331
3028
|
@group(0) @binding(15) var u_clusterLightData: texture_2d<f32>;
|
|
2332
3029
|
@group(0) @binding(16) var u_clusterLightIndices: texture_2d<f32>;
|
|
3030
|
+
@group(0) @binding(17) var u_spotShadowSampler: sampler;
|
|
3031
|
+
@group(0) @binding(18) var u_spotShadowTexture: texture_2d<f32>;
|
|
3032
|
+
|
|
3033
|
+
${atlas ? WEBGPU_EXTENSION_ATLAS_WGSL : ""}
|
|
2333
3034
|
|
|
2334
3035
|
struct VertexOutput {
|
|
2335
3036
|
@builtin(position) position: vec4<f32>,
|
|
@@ -2337,6 +3038,7 @@ struct VertexOutput {
|
|
|
2337
3038
|
@location(1) uv: vec2<f32>,
|
|
2338
3039
|
@location(2) worldPosition: vec3<f32>,
|
|
2339
3040
|
@location(3) tangent: vec4<f32>,
|
|
3041
|
+
${atlas ? "@location(4) uv1: vec2<f32>, @location(5) color: vec4<f32>," : ""}
|
|
2340
3042
|
};
|
|
2341
3043
|
|
|
2342
3044
|
fn fresnelSchlick(cosTheta: f32, f0: vec3<f32>) -> vec3<f32> {
|
|
@@ -2349,14 +3051,16 @@ fn fresnelSchlickRoughness(cosTheta: f32, f0: vec3<f32>, roughness: f32) -> vec3
|
|
|
2349
3051
|
}
|
|
2350
3052
|
|
|
2351
3053
|
fn ggxDistribution(nDotH: f32, roughness: f32) -> f32 {
|
|
2352
|
-
let a = max(roughness * roughness, 0.045);
|
|
3054
|
+
let a = ${atlas ? "max(roughness, 0.045) * max(roughness, 0.045)" : "max(roughness * roughness, 0.045)"};
|
|
2353
3055
|
let a2 = a * a;
|
|
2354
|
-
|
|
2355
|
-
|
|
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"});
|
|
2356
3060
|
}
|
|
2357
3061
|
|
|
2358
3062
|
fn ggxVisibilitySmithCorrelated(nDotV: f32, nDotL: f32, roughness: f32) -> f32 {
|
|
2359
|
-
let a = max(roughness * roughness, 0.045);
|
|
3063
|
+
let a = ${atlas ? "max(roughness, 0.045) * max(roughness, 0.045)" : "max(roughness * roughness, 0.045)"};
|
|
2360
3064
|
let a2 = a * a;
|
|
2361
3065
|
let lambdaV = nDotL * sqrt(max((nDotV - a2 * nDotV) * nDotV + a2, 0.00001));
|
|
2362
3066
|
let lambdaL = nDotV * sqrt(max((nDotL - a2 * nDotL) * nDotL + a2, 0.00001));
|
|
@@ -2421,18 +3125,79 @@ fn perturbNormal(normalInput: vec3<f32>, tangentFrame: vec4<f32>, normalSample:
|
|
|
2421
3125
|
return normalize(tangent * mapped.x + bitangent * mapped.y + n * max(mapped.z, 0.001));
|
|
2422
3126
|
}
|
|
2423
3127
|
|
|
2424
|
-
fn
|
|
3128
|
+
fn a3dNativeShadowFactor(worldPosition: vec3<f32>, normal: vec3<f32>, lightDirection: vec3<f32>, useSpot: bool, shadowSampler: sampler, shadowTexture: texture_2d<f32>) -> f32 {
|
|
3129
|
+
var shadowMatrix = u_draw.shadowMapMatrix;
|
|
3130
|
+
var bias = u_draw.shadowParams0.x;
|
|
3131
|
+
var slopeBias = u_draw.shadowParams0.y;
|
|
3132
|
+
var texelSize = u_draw.shadowParams0.zw;
|
|
3133
|
+
var strength = u_draw.flags.z;
|
|
3134
|
+
var sampleCount = i32(u_draw.shadowParams1.x + 0.5);
|
|
3135
|
+
var receiverLightDirection = lightDirection;
|
|
3136
|
+
if (useSpot) {
|
|
3137
|
+
if (u_draw.spotShadowParams2.x < 0.5) { return 1.0; }
|
|
3138
|
+
let toFragment = worldPosition - u_draw.spotShadowParams0.xyz;
|
|
3139
|
+
let distanceToLight = max(length(toFragment), 0.0001);
|
|
3140
|
+
if (distanceToLight > u_draw.spotShadowParams0.w) { return 1.0; }
|
|
3141
|
+
let coneAxis = u_draw.spotShadowParams1.xyz / max(length(u_draw.spotShadowParams1.xyz), 0.0001);
|
|
3142
|
+
if (dot(toFragment / distanceToLight, coneAxis) < cos(u_draw.spotShadowParams3.w)) { return 1.0; }
|
|
3143
|
+
receiverLightDirection = coneAxis * -1.0;
|
|
3144
|
+
shadowMatrix = u_draw.spotShadowMatrix;
|
|
3145
|
+
bias = u_draw.spotShadowParams2.z;
|
|
3146
|
+
slopeBias = u_draw.spotShadowParams2.w;
|
|
3147
|
+
texelSize = u_draw.spotShadowParams3.xy;
|
|
3148
|
+
strength = u_draw.spotShadowParams2.y;
|
|
3149
|
+
sampleCount = i32(u_draw.spotShadowParams3.z + 0.5);
|
|
3150
|
+
}
|
|
3151
|
+
let lightPosition = shadowMatrix * vec4<f32>(worldPosition, 1.0);
|
|
3152
|
+
let projected = lightPosition.xyz / max(lightPosition.w, 0.0001);
|
|
3153
|
+
let uv = projected.xy * 0.5 + vec2<f32>(0.5, 0.5);
|
|
3154
|
+
if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) { return 1.0; }
|
|
3155
|
+
// Native render targets store framebuffer row 0 at the top while the CPU
|
|
3156
|
+
// projection math addresses texels GL-style (row 0 at the bottom), so the
|
|
3157
|
+
// sample V must be mirrored to land on the texel the projection means.
|
|
3158
|
+
// Display and readback paths already account for the row order, which is
|
|
3159
|
+
// why screenshots and readPixels match GL while direct sampling does not.
|
|
3160
|
+
let sampleUv = vec2<f32>(uv.x, 1.0 - uv.y);
|
|
3161
|
+
let receiverNormal = normalize(normal);
|
|
3162
|
+
let shadowLightDirection = receiverLightDirection / max(length(receiverLightDirection), 0.0001);
|
|
3163
|
+
let normalDotLight = clamp(abs(dot(receiverNormal, shadowLightDirection)), 0.0, 1.0);
|
|
3164
|
+
// Shared bias-table policy (GLSL parity): per-sample tangent-scaled slope
|
|
3165
|
+
// bias — a centre-only bias under-compensates outer PCF taps on sloped
|
|
3166
|
+
// receivers, so each tap scales by its own texel distance.
|
|
3167
|
+
let slopeTangent = min(sqrt(max(1.0 - normalDotLight * normalDotLight, 0.0)) / max(normalDotLight, 0.05), 8.0);
|
|
3168
|
+
let slopeTexelBias = slopeTangent * slopeBias * max(texelSize.x, texelSize.y);
|
|
3169
|
+
let projectedDepth = projected.z * 0.5 + 0.5;
|
|
3170
|
+
var shadowed = 0.0;
|
|
3171
|
+
var totalWeight = 0.0;
|
|
3172
|
+
let taps = clamp(sampleCount, 1, 32);
|
|
3173
|
+
for (var i = 0; i < 32; i = i + 1) {
|
|
3174
|
+
if (i >= taps) { break; }
|
|
3175
|
+
var sampleData = u_draw.shadowPcf[i];
|
|
3176
|
+
if (useSpot) { sampleData = u_draw.spotShadowPcf[i]; }
|
|
3177
|
+
let weight = max(sampleData.z, 0.0);
|
|
3178
|
+
let offset = sampleData.xy * texelSize;
|
|
3179
|
+
let storedDepth = textureSampleLevel(shadowTexture, shadowSampler, sampleUv + offset, 0.0).r;
|
|
3180
|
+
let sampleTexelDistance = max(1.0, length(sampleData.xy));
|
|
3181
|
+
let receiverDepth = projectedDepth - bias - slopeTexelBias * sampleTexelDistance;
|
|
3182
|
+
shadowed = shadowed + select(0.0, 1.0, receiverDepth > storedDepth) * weight;
|
|
3183
|
+
totalWeight = totalWeight + weight;
|
|
3184
|
+
}
|
|
3185
|
+
let occlusion = select(0.0, shadowed / totalWeight, totalWeight > 0.0);
|
|
3186
|
+
return mix(1.0, 1.0 - occlusion, clamp(strength, 0.0, 1.0));
|
|
3187
|
+
}
|
|
3188
|
+
|
|
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> {
|
|
2425
3190
|
var normal = normalize(normalInput);
|
|
2426
3191
|
if (u_draw.morph0.x > 0.5) {
|
|
2427
|
-
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);
|
|
2428
3193
|
}
|
|
2429
3194
|
let viewDirection = normalize(u_draw.camera.xyz - worldPosition);
|
|
2430
3195
|
let lightDirection = normalize(vec3<f32>(0.36, 0.52, 0.78));
|
|
2431
3196
|
let halfVector = normalize(lightDirection + viewDirection);
|
|
2432
|
-
var baseColor = u_draw.color.rgb;
|
|
2433
|
-
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" : ""};
|
|
2434
3199
|
if (u_draw.flags.x > 0.5) {
|
|
2435
|
-
let baseSample = textureSample(u_baseTexture, u_baseSampler, uv);
|
|
3200
|
+
let baseSample = textureSample(u_baseTexture, u_baseSampler, ${atlas ? "atlasUv(0u, uv, uv1)" : "uv"});
|
|
2436
3201
|
baseColor = baseColor * baseSample.rgb;
|
|
2437
3202
|
materialAlpha = materialAlpha * baseSample.a;
|
|
2438
3203
|
}
|
|
@@ -2441,16 +3206,14 @@ fn shadePbr(normalInput: vec3<f32>, tangentFrame: vec4<f32>, uv: vec2<f32>, worl
|
|
|
2441
3206
|
let productBodyGate = productPropBodyGate(sourceProductBaseColor, u_draw.materialFlags.z) * (1.0 - productOrangeGate * 0.92);
|
|
2442
3207
|
let productSurfaceGate = clamp(productBodyGate + productOrangeGate * 0.72, 0.0, 1.0);
|
|
2443
3208
|
baseColor = productPropAlbedo(sourceProductBaseColor, u_draw.materialFlags.z);
|
|
2444
|
-
if (materialAlpha < u_draw.material.x) {
|
|
2445
|
-
discard;
|
|
2446
|
-
}
|
|
3209
|
+
${atlas ? "" : "if (materialAlpha < u_draw.material.x) { discard; }"}
|
|
2447
3210
|
let transmission = clamp(max(u_draw.material.y, u_draw.material.z), 0.0, 1.0);
|
|
2448
|
-
let metallicRoughnessSample = textureSample(u_metallicRoughnessTexture, u_metallicRoughnessSampler, uv);
|
|
3211
|
+
let metallicRoughnessSample = textureSample(u_metallicRoughnessTexture, u_metallicRoughnessSampler, ${atlas ? "atlasUv(2u, uv, uv1)" : "uv"});
|
|
2449
3212
|
let metallicRoughnessEnabled = step(0.5, u_draw.materialFlags.x);
|
|
2450
3213
|
let occlusionEnabled = step(0.5, u_draw.materialFlags.y);
|
|
2451
3214
|
let sampledMetallic = clamp(u_draw.params.x * metallicRoughnessSample.b, 0.0, 1.0);
|
|
2452
3215
|
let sampledRoughness = clamp(u_draw.params.y * metallicRoughnessSample.g, 0.045, 1.0);
|
|
2453
|
-
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));
|
|
2454
3217
|
let metallic = mix(clamp(u_draw.params.x, 0.0, 1.0), sampledMetallic, metallicRoughnessEnabled);
|
|
2455
3218
|
let roughness = mix(clamp(u_draw.params.y, 0.045, 1.0), sampledRoughness, metallicRoughnessEnabled);
|
|
2456
3219
|
let occlusion = mix(1.0, sampledOcclusion, occlusionEnabled);
|
|
@@ -2460,16 +3223,23 @@ fn shadePbr(normalInput: vec3<f32>, tangentFrame: vec4<f32>, uv: vec2<f32>, worl
|
|
|
2460
3223
|
let nDotV = max(dot(normal, viewDirection), 0.001);
|
|
2461
3224
|
let nDotH = max(dot(normal, halfVector), 0.001);
|
|
2462
3225
|
let vDotH = max(dot(viewDirection, halfVector), 0.001);
|
|
2463
|
-
let
|
|
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);
|
|
2464
3228
|
let fresnel = fresnelSchlick(vDotH, f0);
|
|
2465
3229
|
let distribution = ggxDistribution(nDotH, roughness);
|
|
2466
3230
|
let visibility = ggxVisibilitySmithCorrelated(nDotV, nDotL, roughness);
|
|
2467
3231
|
let lDotH = max(dot(lightDirection, halfVector), 0.0);
|
|
2468
|
-
|
|
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);"}
|
|
2469
3238
|
let specular = fresnel * distribution * visibility;
|
|
2470
3239
|
let diffuse = kd * baseColor * diffuseBurley(nDotV, nDotL, lDotH, roughness) / 3.14159265;
|
|
2471
3240
|
var environment = baseColor * u_draw.params.z * (0.28 + 0.72 * clamp(normal.y * 0.5 + 0.5, 0.0, 1.0)) * occlusion;
|
|
2472
3241
|
var environmentSpecularContribution = vec3<f32>(0.0, 0.0, 0.0);
|
|
3242
|
+
var environmentExtensionRadiance = vec3<f32>(0.0);
|
|
2473
3243
|
if (u_draw.flags.w > 0.5) {
|
|
2474
3244
|
let reflectionDirection = reflect(-viewDirection, normal);
|
|
2475
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);
|
|
@@ -2480,15 +3250,20 @@ fn shadePbr(normalInput: vec3<f32>, tangentFrame: vec4<f32>, uv: vec2<f32>, worl
|
|
|
2480
3250
|
let brdf = textureSampleLevel(u_brdfTexture, u_brdfSampler, vec2<f32>(nDotV, roughness), 0.0).rg;
|
|
2481
3251
|
let environmentFresnel = fresnelSchlickRoughness(nDotV, f0, roughness);
|
|
2482
3252
|
let environmentDiffuse = (vec3<f32>(1.0, 1.0, 1.0) - environmentFresnel) * (1.0 - metallic) * diffuseEnv * baseColor * u_draw.params.w * occlusion;
|
|
2483
|
-
let
|
|
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);
|
|
2484
3255
|
environmentSpecularContribution = environmentSpecular;
|
|
3256
|
+
environmentExtensionRadiance = specularEnv * max(u_draw.reserved0.w, 0.0);
|
|
2485
3257
|
environment = environment + environmentDiffuse + environmentSpecular;
|
|
2486
3258
|
}
|
|
3259
|
+
// J2 native shadow projections: directional PCF mirrors GLSL
|
|
3260
|
+
// a3dForwardShadowFactor; spot PCF mirrors a3dSpotShadowFactor. Both
|
|
3261
|
+
// default to 1.0 (lit) when disabled or outside the shadow frustum.
|
|
2487
3262
|
var shadow = 1.0;
|
|
2488
3263
|
if (u_draw.flags.y > 0.5) {
|
|
2489
|
-
|
|
2490
|
-
shadow = mix(1.0, shadowDepth, clamp(u_draw.flags.z, 0.0, 1.0));
|
|
3264
|
+
shadow = a3dNativeShadowFactor(worldPosition, normal, lightDirection, false, u_shadowSampler, u_shadowTexture);
|
|
2491
3265
|
}
|
|
3266
|
+
shadow = shadow * a3dNativeShadowFactor(worldPosition, normal, lightDirection, true, u_spotShadowSampler, u_spotShadowTexture);
|
|
2492
3267
|
var clusteredDirect = vec3<f32>(0.0, 0.0, 0.0);
|
|
2493
3268
|
if (u_draw.materialFlags.w > 0.5) {
|
|
2494
3269
|
let indexDimensions = textureDimensions(u_clusterLightIndices);
|
|
@@ -2518,7 +3293,8 @@ fn shadePbr(normalInput: vec3<f32>, tangentFrame: vec4<f32>, uv: vec2<f32>, worl
|
|
|
2518
3293
|
}
|
|
2519
3294
|
}
|
|
2520
3295
|
let legacyDirect = (diffuse + specular) * nDotL * 2.25 * shadow;
|
|
2521
|
-
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)" : ""};
|
|
2522
3298
|
let smoothedBodyColor = mix(baseColor, vec3<f32>(1.0, 0.88, 0.012), productBodyGate * 0.32);
|
|
2523
3299
|
let smoothedBeakColor = mix(baseColor, vec3<f32>(1.0, 0.24, 0.018), productOrangeGate * 0.82);
|
|
2524
3300
|
let softBodyProduct = smoothedBodyColor * (1.5 + 0.06 * nDotL) + specular * nDotL * 0.018;
|
|
@@ -2528,6 +3304,7 @@ fn shadePbr(normalInput: vec3<f32>, tangentFrame: vec4<f32>, uv: vec2<f32>, worl
|
|
|
2528
3304
|
let transmittedTint = baseColor * u_draw.params.w * (0.18 + 0.42 * clamp(1.0 - roughness, 0.0, 1.0)) + environmentSpecularContribution * 1.35;
|
|
2529
3305
|
let linearColor = mix(opaqueLinearColor, opaqueLinearColor * 0.22 + transmittedTint, transmission);
|
|
2530
3306
|
let outputAlpha = mix(materialAlpha, min(materialAlpha, 0.22), transmission);
|
|
3307
|
+
${atlas ? "if (materialAlpha < u_draw.material.x) { discard; }" : ""}
|
|
2531
3308
|
return vec4<f32>(encodePbrOutput(linearColor), outputAlpha);
|
|
2532
3309
|
}
|
|
2533
3310
|
`;
|
|
@@ -2564,7 +3341,7 @@ fn ${fragmentEntry}(input: VertexOutput) -> @location(0) vec4<f32> {
|
|
|
2564
3341
|
`
|
|
2565
3342
|
};
|
|
2566
3343
|
}
|
|
2567
|
-
function nativeTexturedPbrShader(vertexEntry, fragmentEntry, marker, usesTangent) {
|
|
3344
|
+
function nativeTexturedPbrShader(vertexEntry, fragmentEntry, marker, usesTangent, atlas = false) {
|
|
2568
3345
|
return {
|
|
2569
3346
|
vertex: `// ${marker}
|
|
2570
3347
|
${nativeUniformStruct()}
|
|
@@ -2574,24 +3351,26 @@ struct VertexOutput {
|
|
|
2574
3351
|
@location(1) uv: vec2<f32>,
|
|
2575
3352
|
@location(2) worldPosition: vec3<f32>,
|
|
2576
3353
|
@location(3) tangent: vec4<f32>,
|
|
3354
|
+
${atlas ? "@location(4) uv1: vec2<f32>, @location(5) color: vec4<f32>," : ""}
|
|
2577
3355
|
};
|
|
2578
3356
|
|
|
2579
3357
|
@vertex
|
|
2580
|
-
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 {
|
|
2581
3359
|
var output: VertexOutput;
|
|
2582
3360
|
let worldPosition = (u_draw.instance0 * vec4<f32>(position, 1.0)).xyz;
|
|
2583
3361
|
output.position = a3dWebGPUClipPosition(u_draw.modelViewProjection * vec4<f32>(position, 1.0));
|
|
2584
3362
|
output.normal = normalize((u_draw.normalMatrix * vec4<f32>(normal, 0.0)).xyz);
|
|
2585
3363
|
output.uv = uv;
|
|
3364
|
+
${atlas ? "output.uv1 = uv1; output.color = color;" : ""}
|
|
2586
3365
|
output.worldPosition = worldPosition;
|
|
2587
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);"}
|
|
2588
3367
|
return output;
|
|
2589
3368
|
}
|
|
2590
3369
|
`,
|
|
2591
|
-
fragment: `${nativePbrFragmentPrelude(marker)}
|
|
3370
|
+
fragment: `${nativePbrFragmentPrelude(marker, atlas)}
|
|
2592
3371
|
@fragment
|
|
2593
3372
|
fn ${fragmentEntry}(input: VertexOutput) -> @location(0) vec4<f32> {
|
|
2594
|
-
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" : ""});
|
|
2595
3374
|
}
|
|
2596
3375
|
`
|
|
2597
3376
|
};
|
|
@@ -2615,13 +3394,27 @@ fn instanceMatrix(index: u32) -> mat4x4<f32> {
|
|
|
2615
3394
|
return u_draw.instance0;
|
|
2616
3395
|
}
|
|
2617
3396
|
|
|
3397
|
+
fn a3dInstanceNormalMatrix(model: mat4x4<f32>) -> mat3x3<f32> {
|
|
3398
|
+
// GLSL parity: v_normal = u_normalMatrix * transpose(inverse(mat3(instance))) * a_normal.
|
|
3399
|
+
let m = mat3x3<f32>(model[0].xyz, model[1].xyz, model[2].xyz);
|
|
3400
|
+
let adj0 = cross(m[1], m[2]);
|
|
3401
|
+
let adj1 = cross(m[2], m[0]);
|
|
3402
|
+
let adj2 = cross(m[0], m[1]);
|
|
3403
|
+
let det = dot(m[0], adj0);
|
|
3404
|
+
let safeDet = select(det, 1.0, abs(det) < 0.00001);
|
|
3405
|
+
return mat3x3<f32>(
|
|
3406
|
+
vec3<f32>(adj0.x, adj1.x, adj2.x) / safeDet,
|
|
3407
|
+
vec3<f32>(adj0.y, adj1.y, adj2.y) / safeDet,
|
|
3408
|
+
vec3<f32>(adj0.z, adj1.z, adj2.z) / safeDet);
|
|
3409
|
+
}
|
|
3410
|
+
|
|
2618
3411
|
@vertex
|
|
2619
3412
|
fn ${vertexEntry}(@location(0) position: vec3<f32>, @location(1) normal: vec3<f32>, @builtin(instance_index) instanceIndex: u32) -> VertexOutput {
|
|
2620
3413
|
var output: VertexOutput;
|
|
2621
3414
|
let model = instanceMatrix(instanceIndex);
|
|
2622
|
-
let worldPosition = (model * vec4<f32>(position, 1.0)).xyz;
|
|
3415
|
+
let worldPosition = (u_draw.modelMatrix * model * vec4<f32>(position, 1.0)).xyz;
|
|
2623
3416
|
output.position = a3dWebGPUClipPosition(u_draw.modelViewProjection * model * vec4<f32>(position, 1.0));
|
|
2624
|
-
output.normal = normalize((
|
|
3417
|
+
output.normal = normalize((u_draw.normalMatrix * vec4<f32>(a3dInstanceNormalMatrix(model) * normal, 0.0)).xyz);
|
|
2625
3418
|
output.uv = vec2<f32>(0.5, 0.5);
|
|
2626
3419
|
output.worldPosition = worldPosition;
|
|
2627
3420
|
output.tangent = vec4<f32>(1.0, 0.0, 0.0, 1.0);
|