@aura3d/rendering 1.4.0 → 1.4.2
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/package.json +38 -4
- package/AGENTS.md +0 -46
- package/src/ArchitecturalLightingFixtures.ts +0 -241
- package/src/ArchitecturalMaterialCatalog.ts +0 -130
- package/src/ArchitecturalMeasurementFixtures.ts +0 -155
- package/src/BRDFLut.ts +0 -47
- package/src/CameraFraming.ts +0 -200
- package/src/CanonicalSceneFixtures.ts +0 -95
- package/src/CascadedShadowMaps.ts +0 -449
- package/src/ColorManagement.ts +0 -127
- package/src/CullingFixtures.ts +0 -280
- package/src/DecalGeometry.ts +0 -26
- package/src/DepthPass.ts +0 -110
- package/src/EnvironmentBackgroundPass.ts +0 -194
- package/src/EnvironmentBackgroundResources.ts +0 -309
- package/src/EnvironmentLighting.ts +0 -96
- package/src/EnvironmentMapResources.ts +0 -1026
- package/src/EnvironmentPipeline.ts +0 -69
- package/src/EnvironmentPlatform.ts +0 -1306
- package/src/EnvironmentPreset.ts +0 -155
- package/src/Exposure.ts +0 -66
- package/src/ExternalParityRenderPreset.ts +0 -725
- package/src/ForwardPass.ts +0 -1668
- package/src/FrameVisualMetrics.ts +0 -301
- package/src/Geometry.ts +0 -461
- package/src/GeometryPrimitives.ts +0 -2
- package/src/HDRRenderPipeline.ts +0 -94
- package/src/IBL.ts +0 -86
- package/src/IndexBuffer.ts +0 -57
- package/src/InstancedPBRMaterial.ts +0 -186
- package/src/InstancedUnlitMaterial.ts +0 -60
- package/src/Instancing.ts +0 -19
- package/src/LightCollector.ts +0 -82
- package/src/LightUniforms.ts +0 -47
- package/src/LightingDebug.ts +0 -80
- package/src/LightingDefaults.ts +0 -79
- package/src/LightingRig.ts +0 -301
- package/src/LineGeometry.ts +0 -2
- package/src/Material.ts +0 -209
- package/src/MaterialBinding.ts +0 -131
- package/src/MaterialInstance.ts +0 -59
- package/src/MaterialPresets.ts +0 -272
- package/src/MorphTarget.ts +0 -124
- package/src/MorphTargetPlan.ts +0 -178
- package/src/MorphUnlitMaterial.ts +0 -60
- package/src/NormalMappedPBRMaterial.ts +0 -188
- package/src/OceanFixtures.ts +0 -318
- package/src/PBRLightingDefaults.ts +0 -12
- package/src/PBRMaterial.ts +0 -442
- package/src/PMREM.ts +0 -53
- package/src/PbrReference.ts +0 -681
- package/src/PostProcessPass.ts +0 -2507
- package/src/ProceduralTextureFixtures.ts +0 -393
- package/src/ProductTurntableFixtures.ts +0 -815
- package/src/Raycaster.ts +0 -21
- package/src/ReflectionProbe.ts +0 -13
- package/src/ReflectionSurfaces.ts +0 -219
- package/src/RenderBackend.ts +0 -43
- package/src/RenderDebugViews.ts +0 -138
- package/src/RenderDevice.ts +0 -836
- package/src/RenderGraph.ts +0 -194
- package/src/RenderPass.ts +0 -27
- package/src/RenderPipeline.ts +0 -101
- package/src/RenderQueue.ts +0 -12
- package/src/RenderState.ts +0 -12
- package/src/Renderer.ts +0 -2631
- package/src/RendererDebugOverlay.ts +0 -80
- package/src/RendererFeatureGates.ts +0 -150
- package/src/RendererPostprocessPlan.ts +0 -279
- package/src/RendererTiming.ts +0 -251
- package/src/RendererVisualPipelineReport.ts +0 -836
- package/src/ResourceLifecycle.ts +0 -21
- package/src/Sampler.ts +0 -37
- package/src/SceneOptimization.ts +0 -518
- package/src/ShaderChunks.ts +0 -393
- package/src/ShaderLibrary.ts +0 -2766
- package/src/ShaderModule.ts +0 -36
- package/src/ShaderPreprocessor.ts +0 -260
- package/src/ShaderReflection.ts +0 -201
- package/src/ShadowMap.ts +0 -237
- package/src/ShadowPass.ts +0 -198
- package/src/ShadowProjection.ts +0 -115
- package/src/SkinnedLitMaterial.ts +0 -397
- package/src/SkinnedUnlitMaterial.ts +0 -63
- package/src/SkinningBounds.ts +0 -131
- package/src/SpaceEnvironmentFixtures.ts +0 -200
- package/src/SpriteGeometry.ts +0 -21
- package/src/StereoCameraRig.ts +0 -127
- package/src/StereoEffects.ts +0 -274
- package/src/TerrainFixtures.ts +0 -180
- package/src/Texture.ts +0 -307
- package/src/TextureBinding.ts +0 -91
- package/src/TexturedPBRMaterial.ts +0 -1112
- package/src/TexturedUnlitMaterial.ts +0 -67
- package/src/ToneMapping.ts +0 -105
- package/src/UniformBinder.ts +0 -16
- package/src/UniformLayout.ts +0 -129
- package/src/UnlitMaterial.ts +0 -75
- package/src/VegetationFixtures.ts +0 -314
- package/src/VertexBuffer.ts +0 -120
- package/src/VertexFormat.ts +0 -171
- package/src/VoxelWorldFixtures.ts +0 -280
- package/src/WeatherFixtures.ts +0 -223
- package/src/WebGL2Device.ts +0 -2280
- package/src/WebGL2StateCache.ts +0 -330
- package/src/WebGPUDevice.ts +0 -2877
- package/src/WebGPURenderToTextureProof.ts +0 -139
- package/src/advanced-runtime/AdvancedRenderer.ts +0 -73
- package/src/advanced-runtime/index.ts +0 -9
- package/src/animation/AnimationMaterialStyle.ts +0 -32
- package/src/animation/AnimationRenderPreset.ts +0 -45
- package/src/animation/AnimationToonMaterial.ts +0 -387
- package/src/animation/AnimationVisualQuality.ts +0 -71
- package/src/animation/applyAnimationRenderPreset.ts +0 -180
- package/src/animation/index.ts +0 -5
- package/src/cinematic/BloomPass.ts +0 -26
- package/src/cinematic/CinematicDepthComposition.ts +0 -30
- package/src/cinematic/CinematicEvidence.ts +0 -123
- package/src/cinematic/CinematicLightingRig.ts +0 -144
- package/src/cinematic/CinematicMaterialPresets.ts +0 -130
- package/src/cinematic/CinematicPostProcess.ts +0 -83
- package/src/cinematic/DepthHazePass.ts +0 -30
- package/src/cinematic/EmissivePracticalLightSystem.ts +0 -45
- package/src/cinematic/FilmGrainPass.ts +0 -24
- package/src/cinematic/FogVolumeSystem.ts +0 -30
- package/src/cinematic/GlowCardSystem.ts +0 -44
- package/src/cinematic/RainParticleSystem.ts +0 -151
- package/src/cinematic/VignettePass.ts +0 -25
- package/src/cinematic/WetReflectionApproximation.ts +0 -27
- package/src/cinematic/index.ts +0 -14
- package/src/effects/CollisionModule.ts +0 -64
- package/src/effects/ColorModule.ts +0 -60
- package/src/effects/ForceModule.ts +0 -25
- package/src/effects/GPUParticleBackend.ts +0 -569
- package/src/effects/Particle.ts +0 -108
- package/src/effects/ParticleDiagnostics.ts +0 -224
- package/src/effects/ParticleEffectPresets.ts +0 -147
- package/src/effects/ParticleEmitter.ts +0 -326
- package/src/effects/ParticleModule.ts +0 -26
- package/src/effects/ParticleRenderPass.ts +0 -117
- package/src/effects/ParticleRenderer.ts +0 -132
- package/src/effects/ParticleSystem.ts +0 -333
- package/src/effects/SizeModule.ts +0 -56
- package/src/effects/TrailModule.ts +0 -72
- package/src/effects/VelocityModule.ts +0 -75
- package/src/effects/index.ts +0 -15
- package/src/index.ts +0 -925
- package/src/materials/AlphaSorting.ts +0 -30
- package/src/materials/MaterialExtensions.ts +0 -42
- package/src/materials/PhysicalMaterial.ts +0 -133
- package/src/materials/TransmissionPass.ts +0 -51
- package/src/performance/BVH.ts +0 -18
- package/src/performance/Batcher.ts +0 -2
- package/src/performance/FrustumCuller.ts +0 -9
- package/src/performance/LOD.ts +0 -20
- package/src/performance/Octree.ts +0 -10
- package/src/performance/RenderItemSorting.ts +0 -170
- package/src/performance/RendererStats.ts +0 -43
- package/src/performance/ResourceBudget.ts +0 -44
- package/src/postprocess/BloomPass.ts +0 -28
- package/src/postprocess/CinematicDiagnostics.ts +0 -642
- package/src/postprocess/ColorGradingPass.ts +0 -24
- package/src/postprocess/DepthOfFieldPass.ts +0 -12
- package/src/postprocess/EffectComposer.ts +0 -349
- package/src/postprocess/SSAOPass.ts +0 -20
- package/src/postprocess/index.ts +0 -34
- package/src/production-runtime/AnimationControlsPipeline.ts +0 -77
- package/src/production-runtime/PBRHDRPipeline.ts +0 -381
- package/src/production-runtime/ProductionEffectsPipeline.ts +0 -85
- package/src/production-runtime/ProductionRendererTypes.ts +0 -136
- package/src/production-runtime/ProductionRuntimeRenderer.ts +0 -172
- package/src/production-runtime/ProductionWebGL2Renderer.ts +0 -313
- package/src/production-runtime/ProductionWebGPURenderer.ts +0 -396
- package/src/production-runtime/TransmissionBackdropCapture.ts +0 -130
- package/src/production-runtime/animation/MorphTargetRenderer.ts +0 -2
- package/src/production-runtime/animation/SkinningRenderer.ts +0 -2
- package/src/production-runtime/backends/RendererBackend.ts +0 -17
- package/src/production-runtime/backends/WebGL2RendererBackend.ts +0 -30
- package/src/production-runtime/backends/WebGPURendererBackend.ts +0 -24
- package/src/production-runtime/backends/webgl2/WebGL2Buffer.ts +0 -2
- package/src/production-runtime/backends/webgl2/WebGL2Capabilities.ts +0 -2
- package/src/production-runtime/backends/webgl2/WebGL2RenderTarget.ts +0 -2
- package/src/production-runtime/backends/webgl2/WebGL2Shader.ts +0 -2
- package/src/production-runtime/backends/webgl2/WebGL2StateCache.ts +0 -1
- package/src/production-runtime/backends/webgl2/WebGL2Texture.ts +0 -2
- package/src/production-runtime/backends/webgpu/WebGPUBuffer.ts +0 -2
- package/src/production-runtime/backends/webgpu/WebGPUCapabilities.ts +0 -2
- package/src/production-runtime/backends/webgpu/WebGPURenderTarget.ts +0 -2
- package/src/production-runtime/backends/webgpu/WebGPUShader.ts +0 -2
- package/src/production-runtime/backends/webgpu/WebGPUTexture.ts +0 -2
- package/src/production-runtime/color/ColorManagement.ts +0 -3
- package/src/production-runtime/color/ToneMapping.ts +0 -7
- package/src/production-runtime/diagnostics/FrameCapture.ts +0 -2
- package/src/production-runtime/diagnostics/GPUCapabilities.ts +0 -2
- package/src/production-runtime/diagnostics/RendererStats.ts +0 -2
- package/src/production-runtime/environment/EnvironmentMap.ts +0 -1
- package/src/production-runtime/environment/HDRLoader.ts +0 -57
- package/src/production-runtime/environment/PMREMGenerator.ts +0 -713
- package/src/production-runtime/framegraph/FrameGraph.ts +0 -18
- package/src/production-runtime/framegraph/RenderPass.ts +0 -15
- package/src/production-runtime/geometry/ProjectedDecalGeometry.ts +0 -439
- package/src/production-runtime/index.ts +0 -127
- package/src/production-runtime/lights/LightManager.ts +0 -7
- package/src/production-runtime/lights/ShadowMapRenderer.ts +0 -5
- package/src/production-runtime/materials/GLTFMaterialAdapter.ts +0 -6
- package/src/production-runtime/materials/GLTFPBRMaterialAdapter.ts +0 -2
- package/src/production-runtime/materials/MaterialCompiler.ts +0 -7
- package/src/production-runtime/materials/MaterialTextureBindings.ts +0 -5
- package/src/production-runtime/materials/PBRMaterial.ts +0 -2
- package/src/production-runtime/materials/PBRShaderFeatures.ts +0 -2
- package/src/production-runtime/passes/ContactShadowPass.ts +0 -184
- package/src/production-runtime/passes/DepthPrepass.ts +0 -7
- package/src/production-runtime/passes/OpaquePass.ts +0 -7
- package/src/production-runtime/passes/ShadowPass.ts +0 -7
- package/src/production-runtime/passes/SkyboxPass.ts +0 -7
- package/src/production-runtime/passes/ToneMappingPass.ts +0 -7
- package/src/production-runtime/passes/TransparentPass.ts +0 -7
- package/src/production-runtime/postprocess/BloomPass.ts +0 -51
- package/src/production-runtime/postprocess/ColorGradingPass.ts +0 -49
- package/src/production-runtime/postprocess/DOFPass.ts +0 -47
- package/src/production-runtime/postprocess/FXAAPass.ts +0 -53
- package/src/production-runtime/postprocess/ProductionEffectComposer.ts +0 -78
- package/src/production-runtime/postprocess/ProductionPostProcessTypes.ts +0 -174
- package/src/production-runtime/postprocess/SSAOPass.ts +0 -45
- package/src/production-runtime/resources/GPUBuffer.ts +0 -2
- package/src/production-runtime/resources/GPUTexture.ts +0 -2
- package/src/production-runtime/resources/RenderTarget.ts +0 -3
- package/src/production-runtime/resources/ResourceCache.ts +0 -7
- package/src/production-runtime/scene/Camera.ts +0 -1
- package/src/production-runtime/scene/Lights.ts +0 -2
- package/src/production-runtime/scene/RenderableMesh.ts +0 -2
- package/src/production-runtime/scene/RenderablePrimitive.ts +0 -1
- package/src/production-runtime/scene/RenderableScene.ts +0 -4
- package/src/production-runtime/shaders/ShaderProgramLibrary.ts +0 -7
- package/src/production-runtime/shaders/chunks/brdf.glsl +0 -2
- package/src/production-runtime/shaders/chunks/ibl.glsl +0 -3
- package/src/production-runtime/shaders/chunks/pbr.frag.glsl +0 -47
- package/src/production-runtime/shaders/chunks/pbr.vert.glsl +0 -8
- package/src/production-runtime/shaders/chunks/shadows.glsl +0 -2
- package/src/production-runtime/shaders/wgsl/pbr.wgsl +0 -3
- package/src/production-runtime/shaders/wgsl/postprocess.wgsl +0 -2
- package/src/production-runtime/shaders/wgsl/skybox.wgsl +0 -2
- package/src/shaders/depth.frag.glsl +0 -5
- package/src/shaders/depth.vert.glsl +0 -8
- package/src/shaders/pbr-direct.frag.glsl +0 -687
- package/src/shaders/pbr-direct.vert.glsl +0 -18
- package/src/shaders/unlit.frag.glsl +0 -18
- package/src/shaders/unlit.vert.glsl +0 -13
- package/src/shadows/CascadedShadowPipeline.ts +0 -53
- package/src/shadows/ContactShadows.ts +0 -106
- package/src/shadows/ShadowDebugViews.ts +0 -46
- package/src/threejs-compatibility/InstancingSystem.ts +0 -17
- package/src/threejs-compatibility/LightingSystem.ts +0 -22
- package/src/threejs-compatibility/MaterialSystem.ts +0 -9
- package/src/threejs-compatibility/RenderTargetSystem.ts +0 -35
- package/src/threejs-compatibility/RendererDiagnostics.ts +0 -61
- package/src/threejs-compatibility/SceneRenderer.ts +0 -34
- package/src/threejs-compatibility/ShadowSystem.ts +0 -19
- package/src/threejs-compatibility/TextureSystem.ts +0 -19
- package/src/threejs-compatibility/ThreeCompatRenderer.ts +0 -90
- package/src/threejs-compatibility/TransparencySystem.ts +0 -19
- package/src/threejs-compatibility/index.ts +0 -29
- package/src/threejs-compatibility/performance/BVH.ts +0 -14
- package/src/threejs-compatibility/performance/FrustumCulling.ts +0 -10
- package/src/threejs-compatibility/performance/Instancing.ts +0 -4
- package/src/threejs-compatibility/performance/LODSystem.ts +0 -16
- package/src/threejs-compatibility/performance/OcclusionCulling.ts +0 -6
- package/src/threejs-compatibility/performance/RaycastAcceleration.ts +0 -12
- package/src/threejs-compatibility/performance/RendererProfiler.ts +0 -19
- package/src/threejs-compatibility/performance/TextureStreaming.ts +0 -4
- package/src/threejs-compatibility/performance/index.ts +0 -12
- package/src/threejs-compatibility/postprocess/BloomPass.ts +0 -16
- package/src/threejs-compatibility/postprocess/ColorGradingPass.ts +0 -16
- package/src/threejs-compatibility/postprocess/DepthOfFieldPass.ts +0 -21
- package/src/threejs-compatibility/postprocess/EffectComposer.ts +0 -14
- package/src/threejs-compatibility/postprocess/FXAAPass.ts +0 -15
- package/src/threejs-compatibility/postprocess/MotionBlurPass.ts +0 -20
- package/src/threejs-compatibility/postprocess/OutlinePass.ts +0 -21
- package/src/threejs-compatibility/postprocess/PostProcessTypes.ts +0 -169
- package/src/threejs-compatibility/postprocess/RenderPass.ts +0 -9
- package/src/threejs-compatibility/postprocess/SMAAPass.ts +0 -15
- package/src/threejs-compatibility/postprocess/SSAOPass.ts +0 -21
- package/src/threejs-compatibility/postprocess/ShaderPass.ts +0 -7
- package/src/threejs-compatibility/postprocess/TAAPass.ts +0 -15
- package/src/threejs-compatibility/postprocess/VignettePass.ts +0 -16
- package/src/threejs-compatibility/postprocess/index.ts +0 -15
- package/src/threejs-compatibility/shaders/NodeMaterial.ts +0 -21
- package/src/threejs-compatibility/shaders/RawShaderMaterial.ts +0 -6
- package/src/threejs-compatibility/shaders/ShaderDiagnostics.ts +0 -17
- package/src/threejs-compatibility/shaders/ShaderMaterial.ts +0 -18
- package/src/threejs-compatibility/shaders/ThreeCompatShaderChunks.ts +0 -6
- package/src/threejs-compatibility/shaders/Uniforms.ts +0 -18
- package/src/threejs-compatibility/shaders/index.ts +0 -9
- package/src/threejs-compatibility/vfx/GPUPointCloud.ts +0 -7
- package/src/threejs-compatibility/vfx/LineRenderer.ts +0 -13
- package/src/threejs-compatibility/vfx/ParticleSystem.ts +0 -23
- package/src/threejs-compatibility/vfx/SpriteSystem.ts +0 -14
- package/src/threejs-compatibility/vfx/TrailRenderer.ts +0 -8
- package/src/threejs-compatibility/vfx/VFXDiagnostics.ts +0 -23
- package/src/threejs-compatibility/vfx/index.ts +0 -9
- package/src/threejs-example-parity/index.ts +0 -205
- package/src/webgpu/WebGPUBuffer.ts +0 -1
- package/src/webgpu/WebGPUCompute.ts +0 -2
- package/src/webgpu/WebGPUPipelineCache.ts +0 -15
- package/src/webgpu/WebGPUPostProcess.ts +0 -2
- package/src/webgpu/WebGPUTexture.ts +0 -2
- package/tsconfig.json +0 -26
- package/vitest.config.ts +0 -20
package/src/Renderer.ts
DELETED
|
@@ -1,2631 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Bounds3 as SceneBounds3,
|
|
3
|
-
Camera,
|
|
4
|
-
DirectionalLight,
|
|
5
|
-
Light,
|
|
6
|
-
PointLight,
|
|
7
|
-
Scene,
|
|
8
|
-
SpotLight,
|
|
9
|
-
identityMat4,
|
|
10
|
-
invertMat4,
|
|
11
|
-
multiplyMat4,
|
|
12
|
-
orthographicMat4,
|
|
13
|
-
perspectiveMat4,
|
|
14
|
-
transformPoint,
|
|
15
|
-
type Mat4,
|
|
16
|
-
type Vec3,
|
|
17
|
-
type SceneNode
|
|
18
|
-
} from "@aura3d/scene";
|
|
19
|
-
import type { Ray } from "@aura3d/math";
|
|
20
|
-
import { createRenderDevice, type RenderBackendOptions } from "./RenderBackend";
|
|
21
|
-
import { type LdrPostprocessPassDescriptor, type RenderDevice, RenderDeviceError, type RenderDeviceDiagnostics, type RenderTarget, type RenderTargetDescriptor } from "./RenderDevice";
|
|
22
|
-
import { ENVIRONMENT_BACKGROUND_COLOR_RESOURCE, EnvironmentBackgroundPass, type EnvironmentBackgroundOptions } from "./EnvironmentBackgroundPass";
|
|
23
|
-
import { ForwardPass, type EnvironmentLightingOptions, type ForwardEnvironmentFogOptions, type ForwardShadowMapOptions, type RenderItem, type RenderMaterial, type SkinningPaletteBinding } from "./ForwardPass";
|
|
24
|
-
import { type CollectedLight, LightCollector } from "./LightCollector";
|
|
25
|
-
import { Geometry } from "./Geometry";
|
|
26
|
-
import { type MorphTargetDelta } from "./MorphTarget";
|
|
27
|
-
import { computeSkinnedMorphTargetWeightedBounds } from "./SkinningBounds";
|
|
28
|
-
import { RenderGraph } from "./RenderGraph";
|
|
29
|
-
import {
|
|
30
|
-
BloomPass,
|
|
31
|
-
FXAAPass,
|
|
32
|
-
ToneMappingPass,
|
|
33
|
-
bloomFloatPixels,
|
|
34
|
-
bloomPixels,
|
|
35
|
-
chromaticAberrationPixels,
|
|
36
|
-
colorGradePixels,
|
|
37
|
-
contactShadowPixels,
|
|
38
|
-
createDepthTextureBinding,
|
|
39
|
-
depthOfFieldPixels,
|
|
40
|
-
filmGrainPixels,
|
|
41
|
-
fusedLdrPostprocessPixels,
|
|
42
|
-
fxaaPixels,
|
|
43
|
-
motionBlurPixels,
|
|
44
|
-
outlinePixels,
|
|
45
|
-
ssaoPixels,
|
|
46
|
-
ssrPixels,
|
|
47
|
-
taaPixels,
|
|
48
|
-
toneMapFloatPixels,
|
|
49
|
-
toneMapPixels,
|
|
50
|
-
writePostProcessPixels,
|
|
51
|
-
type BloomOptions,
|
|
52
|
-
type ChromaticAberrationOptions,
|
|
53
|
-
type ColorGradeOptions,
|
|
54
|
-
type ContactShadowPostProcessOptions,
|
|
55
|
-
type DepthTextureBinding,
|
|
56
|
-
type DepthOfFieldOptions,
|
|
57
|
-
type FXAAOptions,
|
|
58
|
-
type FilmGrainOptions,
|
|
59
|
-
type FusedLdrPostProcessPass,
|
|
60
|
-
type FusedLdrPostProcessScratch,
|
|
61
|
-
type MotionBlurOptions,
|
|
62
|
-
type OutlineOptions,
|
|
63
|
-
type SSAOOptions,
|
|
64
|
-
type SSROptions,
|
|
65
|
-
type TAAOptions,
|
|
66
|
-
type ToneMappingOptions
|
|
67
|
-
} from "./PostProcessPass";
|
|
68
|
-
import {
|
|
69
|
-
createRendererPostprocessPasses,
|
|
70
|
-
createRendererPostprocessPlanDiagnostics,
|
|
71
|
-
type RendererPostProcessPassName,
|
|
72
|
-
type RendererPostProcessPassPlan,
|
|
73
|
-
type RendererPostprocessPlanDiagnostics,
|
|
74
|
-
type RendererPostprocessPlanOptions,
|
|
75
|
-
type RendererPostprocessTargetFormat
|
|
76
|
-
} from "./RendererPostprocessPlan";
|
|
77
|
-
import { createDefaultShaderLibrary, type ShaderLibrary } from "./ShaderLibrary";
|
|
78
|
-
import { ShadowMap, type ShadowFilterKernel, type ShadowMapOptions } from "./ShadowMap";
|
|
79
|
-
import { ShadowPass } from "./ShadowPass";
|
|
80
|
-
import { Sampler } from "./Sampler";
|
|
81
|
-
import { TextureBinding } from "./TextureBinding";
|
|
82
|
-
import { computePerspectiveCameraFrame, type PerspectiveCameraFrameOptions } from "./CameraFraming";
|
|
83
|
-
import {
|
|
84
|
-
assertRendererFeatures,
|
|
85
|
-
createRendererFeatureReport,
|
|
86
|
-
type RendererFeature,
|
|
87
|
-
type RendererFeatureReport
|
|
88
|
-
} from "./RendererFeatureGates";
|
|
89
|
-
import { batchStaticRenderItems, type StaticBatchOptions, type StaticBatchInput } from "./SceneOptimization";
|
|
90
|
-
|
|
91
|
-
export interface RendererOptions extends RenderBackendOptions {
|
|
92
|
-
readonly width?: number;
|
|
93
|
-
readonly height?: number;
|
|
94
|
-
readonly clearColor?: readonly [number, number, number, number];
|
|
95
|
-
readonly shaderLibrary?: ShaderLibrary;
|
|
96
|
-
readonly requiredFeatures?: readonly RendererFeature[];
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export interface ResizeToDisplayOptions {
|
|
100
|
-
readonly cssWidth?: number;
|
|
101
|
-
readonly cssHeight?: number;
|
|
102
|
-
readonly devicePixelRatio?: number;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export interface ResizeToDisplayResult {
|
|
106
|
-
readonly resized: boolean;
|
|
107
|
-
readonly cssWidth: number;
|
|
108
|
-
readonly cssHeight: number;
|
|
109
|
-
readonly devicePixelRatio: number;
|
|
110
|
-
readonly width: number;
|
|
111
|
-
readonly height: number;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export interface RendererAnimationLoop {
|
|
115
|
-
readonly running: boolean;
|
|
116
|
-
stop(): void;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export interface RendererFrameCapture {
|
|
120
|
-
readonly width: number;
|
|
121
|
-
readonly height: number;
|
|
122
|
-
readonly pixels: Uint8Array;
|
|
123
|
-
readonly diagnostics: RenderDeviceDiagnostics;
|
|
124
|
-
readonly capturedAt?: string;
|
|
125
|
-
readonly renderSize?: RendererFrameCaptureRenderSize;
|
|
126
|
-
readonly pixelHash?: string;
|
|
127
|
-
readonly pixelDigest?: RendererFrameCapturePixelDigest;
|
|
128
|
-
readonly pixelStats?: RendererFrameCapturePixelStats;
|
|
129
|
-
readonly diagnosticsSummary?: RendererFrameCaptureDiagnosticsSummary;
|
|
130
|
-
readonly metadata?: RendererFrameCaptureMetadata;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export interface RendererFrameCaptureWithMetadata extends RendererFrameCapture {
|
|
134
|
-
readonly capturedAt: string;
|
|
135
|
-
readonly renderSize: RendererFrameCaptureRenderSize;
|
|
136
|
-
readonly pixelHash: string;
|
|
137
|
-
readonly pixelDigest: RendererFrameCapturePixelDigest;
|
|
138
|
-
readonly pixelStats: RendererFrameCapturePixelStats;
|
|
139
|
-
readonly diagnosticsSummary: RendererFrameCaptureDiagnosticsSummary;
|
|
140
|
-
readonly metadata: RendererFrameCaptureMetadata;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export interface RendererFrameCaptureRenderSize {
|
|
144
|
-
readonly width: number;
|
|
145
|
-
readonly height: number;
|
|
146
|
-
readonly pixelCount: number;
|
|
147
|
-
readonly byteLength: number;
|
|
148
|
-
readonly aspectRatio: number;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export interface RendererFrameCapturePixelDigest {
|
|
152
|
-
readonly algorithm: "sha256";
|
|
153
|
-
readonly encoding: "hex";
|
|
154
|
-
readonly source: "rgba8";
|
|
155
|
-
readonly value: string;
|
|
156
|
-
readonly byteLength: number;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export interface RendererFrameCapturePixelStats {
|
|
160
|
-
readonly totalPixels: number;
|
|
161
|
-
readonly opaquePixels: number;
|
|
162
|
-
readonly opaqueRatio: number;
|
|
163
|
-
readonly transparentPixels: number;
|
|
164
|
-
readonly transparentRatio: number;
|
|
165
|
-
readonly nonBlankPixels: number;
|
|
166
|
-
readonly nonBlankRatio: number;
|
|
167
|
-
readonly underexposedPixels: number;
|
|
168
|
-
readonly underexposedRatio: number;
|
|
169
|
-
readonly overexposedPixels: number;
|
|
170
|
-
readonly overexposedRatio: number;
|
|
171
|
-
readonly clippedChannelPixels: number;
|
|
172
|
-
readonly clippedChannelRatio: number;
|
|
173
|
-
readonly averageLuminance: number;
|
|
174
|
-
readonly minLuminance: number;
|
|
175
|
-
readonly maxLuminance: number;
|
|
176
|
-
readonly averageAlpha: number;
|
|
177
|
-
readonly averageRgb: readonly [number, number, number];
|
|
178
|
-
readonly thresholds: {
|
|
179
|
-
readonly nonBlankLuminance: number;
|
|
180
|
-
readonly underexposedLuminance: number;
|
|
181
|
-
readonly overexposedLuminance: number;
|
|
182
|
-
readonly clippedChannel: number;
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export interface RendererFrameCaptureDiagnosticsSummary {
|
|
187
|
-
readonly backend: RenderDevice["kind"];
|
|
188
|
-
readonly renderer: string;
|
|
189
|
-
readonly vendor: string;
|
|
190
|
-
readonly capabilities: readonly string[];
|
|
191
|
-
readonly limitations: readonly string[];
|
|
192
|
-
readonly drawCalls: number;
|
|
193
|
-
readonly resources: {
|
|
194
|
-
readonly buffers: number;
|
|
195
|
-
readonly shaders: number;
|
|
196
|
-
readonly renderTargets?: number;
|
|
197
|
-
readonly textures?: number;
|
|
198
|
-
};
|
|
199
|
-
readonly memoryBytes?: {
|
|
200
|
-
readonly buffers?: number;
|
|
201
|
-
readonly textures?: number;
|
|
202
|
-
readonly approximateGpu?: number;
|
|
203
|
-
};
|
|
204
|
-
readonly scene?: {
|
|
205
|
-
readonly submittedObjects?: number;
|
|
206
|
-
readonly visibleObjects?: number;
|
|
207
|
-
readonly culledObjects?: number;
|
|
208
|
-
readonly frustumTestedObjects?: number;
|
|
209
|
-
};
|
|
210
|
-
readonly postprocess?: {
|
|
211
|
-
readonly passes?: number;
|
|
212
|
-
readonly passNames?: readonly string[];
|
|
213
|
-
readonly targetFormat?: "rgba8" | "rgba16f" | "rgba32f";
|
|
214
|
-
readonly renderTargets?: number;
|
|
215
|
-
readonly textures?: number;
|
|
216
|
-
readonly targetWidth?: number;
|
|
217
|
-
readonly targetHeight?: number;
|
|
218
|
-
};
|
|
219
|
-
readonly contextLost: boolean;
|
|
220
|
-
readonly lastError: string | null;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export interface RendererFrameCaptureMetadata {
|
|
224
|
-
readonly capturedAt: string;
|
|
225
|
-
readonly renderSize: RendererFrameCaptureRenderSize;
|
|
226
|
-
readonly pixelHash: string;
|
|
227
|
-
readonly pixelDigest: RendererFrameCapturePixelDigest;
|
|
228
|
-
readonly pixelStats: RendererFrameCapturePixelStats;
|
|
229
|
-
readonly diagnosticsSummary: RendererFrameCaptureDiagnosticsSummary;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
export interface RenderSource {
|
|
233
|
-
collectRenderItems?(): Iterable<RenderItem>;
|
|
234
|
-
readonly renderItems?: Iterable<RenderItem>;
|
|
235
|
-
readonly scene?: Scene;
|
|
236
|
-
readonly renderTarget?: RenderTarget;
|
|
237
|
-
readonly cameraPolicy?: RendererCameraPolicy;
|
|
238
|
-
readonly cameraFrameBounds?: SceneBounds3 | {
|
|
239
|
-
readonly min: readonly [number, number, number];
|
|
240
|
-
readonly max: readonly [number, number, number];
|
|
241
|
-
};
|
|
242
|
-
readonly cameraFrameOptions?: PerspectiveCameraFrameOptions;
|
|
243
|
-
readonly collectedLights?: Iterable<CollectedLight>;
|
|
244
|
-
readonly environmentBackground?: EnvironmentBackgroundOptions | false;
|
|
245
|
-
readonly environmentLighting?: EnvironmentLightingOptions | false;
|
|
246
|
-
readonly environmentFog?: ForwardEnvironmentFogOptions | false;
|
|
247
|
-
readonly shadowMap?: ForwardShadowMapOptions;
|
|
248
|
-
readonly shadow?: RendererShadowOptions | boolean;
|
|
249
|
-
readonly postprocess?: RendererPostProcessOptions | boolean;
|
|
250
|
-
readonly cameraPosition?: readonly [number, number, number];
|
|
251
|
-
readonly geometryLibrary?: RenderResourceLookup<Geometry>;
|
|
252
|
-
readonly materialLibrary?: RenderResourceLookup<RenderMaterial>;
|
|
253
|
-
readonly morphTargetLibrary?: RenderResourceLookup<readonly MorphTargetDelta[]>;
|
|
254
|
-
readonly frustumCulling?: boolean;
|
|
255
|
-
readonly staticBatching?: boolean | StaticBatchOptions;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
export interface RendererInput {
|
|
259
|
-
readonly source: RenderSource | Iterable<RenderItem> | Scene;
|
|
260
|
-
readonly camera?: CameraLike;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
export type RendererCameraPolicy = "identity" | "auto-frame" | "require";
|
|
264
|
-
|
|
265
|
-
export const DEFAULT_RENDERER_AUTO_FRAME_OPTIONS: PerspectiveCameraFrameOptions = {
|
|
266
|
-
paddingRatio: 0.14,
|
|
267
|
-
yawRadians: -0.38,
|
|
268
|
-
pitchRadians: -0.18,
|
|
269
|
-
nearPadding: 0.2,
|
|
270
|
-
farPadding: 2.4
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
export const DEFAULT_RENDERER_ENVIRONMENT_LIGHTING: EnvironmentLightingOptions = {
|
|
274
|
-
color: [0.78, 0.8, 0.84],
|
|
275
|
-
intensity: 0.42,
|
|
276
|
-
proceduralMap: {
|
|
277
|
-
skyColor: [0.64, 0.76, 0.94],
|
|
278
|
-
horizonColor: [0.94, 0.82, 0.62],
|
|
279
|
-
groundColor: [0.12, 0.13, 0.15],
|
|
280
|
-
specularColor: [1, 0.94, 0.78],
|
|
281
|
-
intensity: 0.5,
|
|
282
|
-
specularIntensity: 0.82
|
|
283
|
-
}
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
const DISABLED_RENDERER_ENVIRONMENT_LIGHTING: EnvironmentLightingOptions = {
|
|
287
|
-
color: [0, 0, 0],
|
|
288
|
-
intensity: 0,
|
|
289
|
-
proceduralMap: {
|
|
290
|
-
skyColor: [0, 0, 0],
|
|
291
|
-
horizonColor: [0, 0, 0],
|
|
292
|
-
groundColor: [0, 0, 0],
|
|
293
|
-
specularColor: [0, 0, 0],
|
|
294
|
-
intensity: 0,
|
|
295
|
-
specularIntensity: 0
|
|
296
|
-
},
|
|
297
|
-
environmentMapIntensity: 0,
|
|
298
|
-
environmentMapSpecularIntensity: 0
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
export const DEFAULT_RENDERER_DIRECT_LIGHTING = {
|
|
302
|
-
key: {
|
|
303
|
-
color: [1, 0.92, 0.78] as const,
|
|
304
|
-
intensity: 2.25,
|
|
305
|
-
direction: [0.42, -0.58, -0.7] as const
|
|
306
|
-
},
|
|
307
|
-
fill: {
|
|
308
|
-
color: [0.48, 0.62, 0.9] as const,
|
|
309
|
-
intensity: 0.55,
|
|
310
|
-
direction: [-0.35, -0.22, -0.91] as const
|
|
311
|
-
}
|
|
312
|
-
} as const;
|
|
313
|
-
|
|
314
|
-
export interface RendererShadowOptions extends ShadowMapOptions {
|
|
315
|
-
readonly enabled?: boolean;
|
|
316
|
-
readonly light?: Light;
|
|
317
|
-
readonly lightMatrix?: Float32Array | readonly number[];
|
|
318
|
-
readonly strength?: number;
|
|
319
|
-
readonly slopeBias?: number;
|
|
320
|
-
readonly texelSize?: readonly [number, number];
|
|
321
|
-
readonly filterKernel?: ShadowFilterKernel;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
export interface RendererPostProcessOptions extends RendererPostprocessPlanOptions {
|
|
325
|
-
readonly targetFormat?: RendererPostprocessTargetFormat;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
export type RenderResourceLookup<T> = ReadonlyMap<string, T> | Readonly<Record<string, T>>;
|
|
329
|
-
|
|
330
|
-
interface RenderCollectionDiagnostics {
|
|
331
|
-
submittedObjects: number;
|
|
332
|
-
visibleObjects: number;
|
|
333
|
-
culledObjects: number;
|
|
334
|
-
frustumTestedObjects: number;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
interface RendererPostprocessDiagnostics {
|
|
338
|
-
readonly postprocessPasses: number;
|
|
339
|
-
readonly postprocessPassNames: readonly RendererPostProcessPassName[];
|
|
340
|
-
readonly postprocessTargetFormat: RendererPostprocessTargetFormat;
|
|
341
|
-
readonly postprocessRenderTargets: number;
|
|
342
|
-
readonly postprocessTextures: number;
|
|
343
|
-
readonly postprocessTargetWidth: number;
|
|
344
|
-
readonly postprocessTargetHeight: number;
|
|
345
|
-
readonly postprocessPlan: RendererPostprocessPlanDiagnostics;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
export interface CameraLike {
|
|
349
|
-
readonly projectionMatrix?: Float32Array | readonly number[];
|
|
350
|
-
readonly viewMatrix?: Float32Array | readonly number[];
|
|
351
|
-
readonly viewProjectionMatrix?: Float32Array | readonly number[];
|
|
352
|
-
resize?(width: number, height: number): void;
|
|
353
|
-
updateCameraMatrices?(): void;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
export interface ScenePickHit {
|
|
357
|
-
readonly node: SceneNode;
|
|
358
|
-
readonly geometry: Geometry;
|
|
359
|
-
readonly distance: number;
|
|
360
|
-
readonly bounds: SceneBounds3;
|
|
361
|
-
readonly hitPoint?: readonly [number, number, number];
|
|
362
|
-
readonly pointIndex?: number;
|
|
363
|
-
readonly instanceIndex?: number;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
export interface ScenePickOptions {
|
|
367
|
-
readonly pointRadius?: number;
|
|
368
|
-
readonly lineRadius?: number;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
export class Renderer {
|
|
372
|
-
public readonly device: RenderDevice;
|
|
373
|
-
private readonly graph = new RenderGraph();
|
|
374
|
-
private readonly shaderLibrary: ShaderLibrary;
|
|
375
|
-
private readonly canvas?: HTMLCanvasElement | OffscreenCanvas;
|
|
376
|
-
private width: number;
|
|
377
|
-
private height: number;
|
|
378
|
-
private clearColor: readonly [number, number, number, number];
|
|
379
|
-
private disposed = false;
|
|
380
|
-
private animationLoop: RendererAnimationLoopImpl | null = null;
|
|
381
|
-
private readonly fusedLdrPostprocessScratch: FusedLdrPostProcessScratch = {};
|
|
382
|
-
|
|
383
|
-
private constructor(device: RenderDevice, options: RendererOptions) {
|
|
384
|
-
this.device = device;
|
|
385
|
-
this.canvas = options.canvas;
|
|
386
|
-
this.width = options.width ?? inferInitialCanvasDimension(options.canvas, "width");
|
|
387
|
-
this.height = options.height ?? inferInitialCanvasDimension(options.canvas, "height");
|
|
388
|
-
this.clearColor = options.clearColor ?? [0, 0, 0, 1];
|
|
389
|
-
this.shaderLibrary = options.shaderLibrary ?? createDefaultShaderLibrary();
|
|
390
|
-
this.resizeCanvas(this.width, this.height);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
static async create(options: RendererOptions = {}): Promise<Renderer> {
|
|
394
|
-
const device = await createRenderDevice(options);
|
|
395
|
-
if (options.requiredFeatures && options.requiredFeatures.length > 0) {
|
|
396
|
-
assertRendererFeatures(device, options.requiredFeatures);
|
|
397
|
-
}
|
|
398
|
-
return new Renderer(device, options);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
getFeatureReport(): RendererFeatureReport {
|
|
402
|
-
return createRendererFeatureReport(this.device);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
resize(width: number, height: number): void {
|
|
406
|
-
this.assertAlive();
|
|
407
|
-
if (width <= 0 || height <= 0 || !Number.isInteger(width) || !Number.isInteger(height)) {
|
|
408
|
-
throw new RenderDeviceError("Renderer dimensions must be positive integers", "INVALID_FRAME_SIZE", { width, height });
|
|
409
|
-
}
|
|
410
|
-
this.width = width;
|
|
411
|
-
this.height = height;
|
|
412
|
-
this.resizeCanvas(width, height);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
resizeToDisplay(options: ResizeToDisplayOptions = {}): ResizeToDisplayResult {
|
|
416
|
-
this.assertAlive();
|
|
417
|
-
if (!this.canvas) {
|
|
418
|
-
throw new RenderDeviceError("resizeToDisplay requires a canvas-backed renderer", "CANVAS_REQUIRED");
|
|
419
|
-
}
|
|
420
|
-
const cssWidth = options.cssWidth ?? readCanvasCssSize(this.canvas, "width");
|
|
421
|
-
const cssHeight = options.cssHeight ?? readCanvasCssSize(this.canvas, "height");
|
|
422
|
-
const devicePixelRatio = options.devicePixelRatio ?? globalThis.devicePixelRatio ?? 1;
|
|
423
|
-
if (![cssWidth, cssHeight, devicePixelRatio].every(Number.isFinite) || cssWidth <= 0 || cssHeight <= 0 || devicePixelRatio <= 0) {
|
|
424
|
-
throw new RenderDeviceError("Display size and DPR must be finite positive values", "INVALID_DISPLAY_SIZE", {
|
|
425
|
-
cssWidth,
|
|
426
|
-
cssHeight,
|
|
427
|
-
devicePixelRatio
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
const width = Math.max(1, Math.round(cssWidth * devicePixelRatio));
|
|
431
|
-
const height = Math.max(1, Math.round(cssHeight * devicePixelRatio));
|
|
432
|
-
const resized = width !== this.width || height !== this.height;
|
|
433
|
-
if (resized) {
|
|
434
|
-
this.resize(width, height);
|
|
435
|
-
}
|
|
436
|
-
return { resized, cssWidth, cssHeight, devicePixelRatio, width, height };
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
startAnimationLoop(callback: (timeMs: number, renderer: Renderer) => void): RendererAnimationLoop {
|
|
440
|
-
this.assertAlive();
|
|
441
|
-
this.animationLoop?.stop();
|
|
442
|
-
const loop = new RendererAnimationLoopImpl(this, callback);
|
|
443
|
-
this.animationLoop = loop;
|
|
444
|
-
loop.start();
|
|
445
|
-
return loop;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
render(input: RendererInput): RenderDeviceDiagnostics;
|
|
449
|
-
render(source: RenderSource | Iterable<RenderItem> | Scene, camera?: CameraLike): RenderDeviceDiagnostics;
|
|
450
|
-
render(sourceOrInput: RendererInput | RenderSource | Iterable<RenderItem> | Scene, camera?: CameraLike): RenderDeviceDiagnostics {
|
|
451
|
-
this.assertAlive();
|
|
452
|
-
const { source, camera: inputCamera } = normalizeRendererInput(sourceOrInput, camera);
|
|
453
|
-
sceneFromSource(source)?.updateWorldTransforms();
|
|
454
|
-
const cameraPolicy = collectCameraPolicy(source);
|
|
455
|
-
const identityPolicyIgnoresSceneCamera = inputCamera === undefined && cameraPolicy === "identity";
|
|
456
|
-
const autoFrameOverridesSceneCamera = inputCamera === undefined && hasExplicitAutoFrameCameraPolicy(source);
|
|
457
|
-
const resolvedCamera = resolveCamera(source, inputCamera, { width: this.width, height: this.height }, {
|
|
458
|
-
allowSceneCamera: !identityPolicyIgnoresSceneCamera && !autoFrameOverridesSceneCamera
|
|
459
|
-
});
|
|
460
|
-
if (!resolvedCamera && cameraPolicy === "require") {
|
|
461
|
-
throw new RenderDeviceError("RenderSource requires an explicit camera but none was supplied or found in the scene.", "CAMERA_REQUIRED");
|
|
462
|
-
}
|
|
463
|
-
let cameraViewProjection = resolvedCamera?.viewProjectionMatrix;
|
|
464
|
-
const collectedItems = collectRenderItemsWithDiagnostics(source, cameraViewProjection, resolvedCamera?.camera);
|
|
465
|
-
const collectionDiagnostics = collectedItems.diagnostics;
|
|
466
|
-
let items = collectedItems.items;
|
|
467
|
-
const lights = collectRenderLights(source);
|
|
468
|
-
const environmentBackground = collectEnvironmentBackground(source);
|
|
469
|
-
const environmentLighting = collectEnvironmentLighting(source);
|
|
470
|
-
const environmentFog = collectEnvironmentFog(source);
|
|
471
|
-
const explicitShadowMap = collectForwardShadowMap(source);
|
|
472
|
-
const shadowOptions = collectRendererShadowOptions(source);
|
|
473
|
-
const sourceCameraPosition = collectSourceCameraPosition(source);
|
|
474
|
-
const explicitRenderTarget = collectRenderTarget(source);
|
|
475
|
-
validateExplicitRenderTarget(explicitRenderTarget, this.width, this.height);
|
|
476
|
-
let cameraPosition = sourceCameraPosition ?? resolvedCamera?.cameraPosition;
|
|
477
|
-
if (!resolvedCamera && cameraPolicy === "auto-frame") {
|
|
478
|
-
const autoFrame = createAutoFrameCamera(source, items, this.width, this.height);
|
|
479
|
-
if (autoFrame) {
|
|
480
|
-
cameraViewProjection = autoFrame.viewProjectionMatrix;
|
|
481
|
-
items = applyViewProjection(items, autoFrame.viewProjectionMatrix);
|
|
482
|
-
cameraPosition = sourceCameraPosition ?? autoFrame.cameraPosition;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
const postprocess = collectPostprocess(source);
|
|
486
|
-
const ownedTargets: RenderTarget[] = [];
|
|
487
|
-
const ownedShadowPasses: ShadowPass[] = [];
|
|
488
|
-
this.graph.clear();
|
|
489
|
-
let postprocessTargetFormat: Extract<RenderTargetDescriptor["format"], "rgba8" | "rgba16f" | "rgba32f"> | undefined;
|
|
490
|
-
if (postprocess) {
|
|
491
|
-
const format = postprocess.targetFormat ?? defaultPostprocessTargetFormat(this.device, postprocess);
|
|
492
|
-
postprocessTargetFormat = format;
|
|
493
|
-
if ((format === "rgba16f" || format === "rgba32f") && !this.device.info.capabilities?.includes("hdr-render-targets")) {
|
|
494
|
-
throw new RenderDeviceError("Renderer HDR postprocess requires an HDR-capable backend", "HDR_POSTPROCESS_UNSUPPORTED", {
|
|
495
|
-
backend: this.device.kind,
|
|
496
|
-
format
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
if ((format === "rgba16f" || format === "rgba32f") && postprocess.toneMapping === false) {
|
|
500
|
-
throw new RenderDeviceError("Renderer HDR postprocess requires tone mapping before presentation or LDR postprocess passes", "HDR_POSTPROCESS_TONEMAPPING_REQUIRED", {
|
|
501
|
-
backend: this.device.kind,
|
|
502
|
-
format
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
if (!this.device.presentRenderTarget) {
|
|
506
|
-
throw new RenderDeviceError("Renderer postprocess requires a backend presentation path", "POSTPROCESS_PRESENT_UNSUPPORTED", {
|
|
507
|
-
backend: this.device.kind
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
const requiresDepthTexture = postprocessRequiresDepthTexture(postprocess);
|
|
511
|
-
if (requiresDepthTexture && !this.device.info.capabilities?.includes("depth-textures")) {
|
|
512
|
-
throw new RenderDeviceError("Depth-aware renderer postprocess requires a backend with sampleable depth texture render targets.", "DEPTH_POSTPROCESS_UNSUPPORTED", {
|
|
513
|
-
backend: this.device.kind
|
|
514
|
-
});
|
|
515
|
-
}
|
|
516
|
-
const forwardTarget = this.device.createRenderTarget({
|
|
517
|
-
width: this.width,
|
|
518
|
-
height: this.height,
|
|
519
|
-
label: "renderer-forward-color",
|
|
520
|
-
format,
|
|
521
|
-
depth: requiresDepthTexture ? "texture" : true
|
|
522
|
-
});
|
|
523
|
-
ownedTargets.push(forwardTarget);
|
|
524
|
-
this.device.setRenderTarget(forwardTarget);
|
|
525
|
-
}
|
|
526
|
-
this.device.beginFrame(this.width, this.height);
|
|
527
|
-
try {
|
|
528
|
-
const rendererShadowMap = explicitShadowMap ?? this.executeRendererShadowMap({
|
|
529
|
-
shadowOptions,
|
|
530
|
-
source,
|
|
531
|
-
items,
|
|
532
|
-
lights,
|
|
533
|
-
ownedTargets,
|
|
534
|
-
ownedShadowPasses
|
|
535
|
-
});
|
|
536
|
-
if (postprocess) {
|
|
537
|
-
const forwardTarget = ownedTargets[0];
|
|
538
|
-
if (!forwardTarget) {
|
|
539
|
-
throw new RenderDeviceError("Renderer postprocess missing forward render target", "POSTPROCESS_TARGET_MISSING");
|
|
540
|
-
}
|
|
541
|
-
this.device.setRenderTarget(forwardTarget);
|
|
542
|
-
} else {
|
|
543
|
-
this.device.setRenderTarget(explicitRenderTarget ?? null);
|
|
544
|
-
}
|
|
545
|
-
this.device.clear(this.clearColor);
|
|
546
|
-
if (environmentBackground) {
|
|
547
|
-
this.graph.addPass(new EnvironmentBackgroundPass({
|
|
548
|
-
...environmentBackground,
|
|
549
|
-
outputColorSpace: postprocess ? "linear" : environmentBackground.outputColorSpace,
|
|
550
|
-
inverseViewProjectionMatrix: environmentBackground.inverseViewProjectionMatrix ?? invertMat4(cameraViewProjection ?? identityMat4()),
|
|
551
|
-
shaderLibrary: this.shaderLibrary
|
|
552
|
-
}));
|
|
553
|
-
}
|
|
554
|
-
this.graph.addPass(new ForwardPass({
|
|
555
|
-
items,
|
|
556
|
-
lights,
|
|
557
|
-
environmentLighting,
|
|
558
|
-
environmentFog,
|
|
559
|
-
inputColorResource: environmentBackground ? ENVIRONMENT_BACKGROUND_COLOR_RESOURCE : undefined,
|
|
560
|
-
shadowMap: rendererShadowMap,
|
|
561
|
-
cameraPosition,
|
|
562
|
-
outputColorSpace: postprocess ? "linear" : "srgb",
|
|
563
|
-
shaderLibrary: this.shaderLibrary
|
|
564
|
-
}));
|
|
565
|
-
this.graph.execute({ device: this.device, width: this.width, height: this.height });
|
|
566
|
-
if (postprocess) {
|
|
567
|
-
this.executePostprocess(postprocess, ownedTargets, explicitRenderTarget);
|
|
568
|
-
}
|
|
569
|
-
} finally {
|
|
570
|
-
this.device.endFrame();
|
|
571
|
-
for (const shadowPass of ownedShadowPasses) {
|
|
572
|
-
shadowPass.dispose();
|
|
573
|
-
}
|
|
574
|
-
for (const target of ownedTargets) {
|
|
575
|
-
target.dispose();
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
return withRendererFrameDiagnostics(this.device.getDiagnostics(), collectionDiagnostics, createPostprocessDiagnostics(postprocess, ownedTargets, this.width, this.height, {
|
|
579
|
-
targetFormat: postprocessTargetFormat,
|
|
580
|
-
nativeLdrPostprocess: Boolean(this.device.presentLdrPostprocess),
|
|
581
|
-
rendererDepthAvailable: Boolean(postprocess && postprocessRequiresDepthTexture(postprocess) && this.device.info.capabilities?.includes("depth-textures"))
|
|
582
|
-
}));
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
renderAsync(input: RendererInput): Promise<RenderDeviceDiagnostics>;
|
|
586
|
-
renderAsync(source: RenderSource | Iterable<RenderItem> | Scene, camera?: CameraLike): Promise<RenderDeviceDiagnostics>;
|
|
587
|
-
async renderAsync(sourceOrInput: RendererInput | RenderSource | Iterable<RenderItem> | Scene, camera?: CameraLike): Promise<RenderDeviceDiagnostics> {
|
|
588
|
-
this.assertAlive();
|
|
589
|
-
const { source, camera: inputCamera } = normalizeRendererInput(sourceOrInput, camera);
|
|
590
|
-
sceneFromSource(source)?.updateWorldTransforms();
|
|
591
|
-
const cameraPolicy = collectCameraPolicy(source);
|
|
592
|
-
const identityPolicyIgnoresSceneCamera = inputCamera === undefined && cameraPolicy === "identity";
|
|
593
|
-
const autoFrameOverridesSceneCamera = inputCamera === undefined && hasExplicitAutoFrameCameraPolicy(source);
|
|
594
|
-
const resolvedCamera = resolveCamera(source, inputCamera, { width: this.width, height: this.height }, {
|
|
595
|
-
allowSceneCamera: !identityPolicyIgnoresSceneCamera && !autoFrameOverridesSceneCamera
|
|
596
|
-
});
|
|
597
|
-
if (!resolvedCamera && cameraPolicy === "require") {
|
|
598
|
-
throw new RenderDeviceError("RenderSource requires an explicit camera but none was supplied or found in the scene.", "CAMERA_REQUIRED");
|
|
599
|
-
}
|
|
600
|
-
let cameraViewProjection = resolvedCamera?.viewProjectionMatrix;
|
|
601
|
-
const collectedItems = collectRenderItemsWithDiagnostics(source, cameraViewProjection, resolvedCamera?.camera);
|
|
602
|
-
const collectionDiagnostics = collectedItems.diagnostics;
|
|
603
|
-
let items = collectedItems.items;
|
|
604
|
-
const lights = collectRenderLights(source);
|
|
605
|
-
const environmentBackground = collectEnvironmentBackground(source);
|
|
606
|
-
const environmentLighting = collectEnvironmentLighting(source);
|
|
607
|
-
const environmentFog = collectEnvironmentFog(source);
|
|
608
|
-
const explicitShadowMap = collectForwardShadowMap(source);
|
|
609
|
-
const shadowOptions = collectRendererShadowOptions(source);
|
|
610
|
-
const sourceCameraPosition = collectSourceCameraPosition(source);
|
|
611
|
-
const explicitRenderTarget = collectRenderTarget(source);
|
|
612
|
-
validateExplicitRenderTarget(explicitRenderTarget, this.width, this.height);
|
|
613
|
-
let cameraPosition = sourceCameraPosition ?? resolvedCamera?.cameraPosition;
|
|
614
|
-
if (!resolvedCamera && cameraPolicy === "auto-frame") {
|
|
615
|
-
const autoFrame = createAutoFrameCamera(source, items, this.width, this.height);
|
|
616
|
-
if (autoFrame) {
|
|
617
|
-
cameraViewProjection = autoFrame.viewProjectionMatrix;
|
|
618
|
-
items = applyViewProjection(items, autoFrame.viewProjectionMatrix);
|
|
619
|
-
cameraPosition = sourceCameraPosition ?? autoFrame.cameraPosition;
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
const postprocess = collectPostprocess(source);
|
|
623
|
-
const ownedTargets: RenderTarget[] = [];
|
|
624
|
-
const ownedShadowPasses: ShadowPass[] = [];
|
|
625
|
-
this.graph.clear();
|
|
626
|
-
let postprocessTargetFormat: Extract<RenderTargetDescriptor["format"], "rgba8" | "rgba16f" | "rgba32f"> | undefined;
|
|
627
|
-
if (postprocess) {
|
|
628
|
-
const format = postprocess.targetFormat ?? defaultPostprocessTargetFormat(this.device, postprocess);
|
|
629
|
-
postprocessTargetFormat = format;
|
|
630
|
-
if ((format === "rgba16f" || format === "rgba32f") && !this.device.info.capabilities?.includes("hdr-render-targets")) {
|
|
631
|
-
throw new RenderDeviceError("Renderer HDR postprocess requires an HDR-capable backend", "HDR_POSTPROCESS_UNSUPPORTED", {
|
|
632
|
-
backend: this.device.kind,
|
|
633
|
-
format
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
if ((format === "rgba16f" || format === "rgba32f") && postprocess.toneMapping === false) {
|
|
637
|
-
throw new RenderDeviceError("Renderer HDR postprocess requires tone mapping before presentation or LDR postprocess passes", "HDR_POSTPROCESS_TONEMAPPING_REQUIRED", {
|
|
638
|
-
backend: this.device.kind,
|
|
639
|
-
format
|
|
640
|
-
});
|
|
641
|
-
}
|
|
642
|
-
if (!this.device.presentRenderTarget) {
|
|
643
|
-
throw new RenderDeviceError("Renderer postprocess requires a backend presentation path", "POSTPROCESS_PRESENT_UNSUPPORTED", {
|
|
644
|
-
backend: this.device.kind
|
|
645
|
-
});
|
|
646
|
-
}
|
|
647
|
-
const requiresDepthTexture = postprocessRequiresDepthTexture(postprocess);
|
|
648
|
-
if (requiresDepthTexture && !this.device.info.capabilities?.includes("depth-textures")) {
|
|
649
|
-
throw new RenderDeviceError("Depth-aware renderer postprocess requires a backend with sampleable depth texture render targets.", "DEPTH_POSTPROCESS_UNSUPPORTED", {
|
|
650
|
-
backend: this.device.kind
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
const forwardTarget = this.device.createRenderTarget({
|
|
654
|
-
width: this.width,
|
|
655
|
-
height: this.height,
|
|
656
|
-
label: "renderer-forward-color",
|
|
657
|
-
format,
|
|
658
|
-
depth: requiresDepthTexture ? "texture" : true
|
|
659
|
-
});
|
|
660
|
-
ownedTargets.push(forwardTarget);
|
|
661
|
-
this.device.setRenderTarget(forwardTarget);
|
|
662
|
-
}
|
|
663
|
-
this.device.beginFrame(this.width, this.height);
|
|
664
|
-
try {
|
|
665
|
-
const rendererShadowMap = explicitShadowMap ?? this.executeRendererShadowMap({
|
|
666
|
-
shadowOptions,
|
|
667
|
-
source,
|
|
668
|
-
items,
|
|
669
|
-
lights,
|
|
670
|
-
ownedTargets,
|
|
671
|
-
ownedShadowPasses
|
|
672
|
-
});
|
|
673
|
-
if (postprocess) {
|
|
674
|
-
const forwardTarget = ownedTargets[0];
|
|
675
|
-
if (!forwardTarget) {
|
|
676
|
-
throw new RenderDeviceError("Renderer postprocess missing forward render target", "POSTPROCESS_TARGET_MISSING");
|
|
677
|
-
}
|
|
678
|
-
this.device.setRenderTarget(forwardTarget);
|
|
679
|
-
} else {
|
|
680
|
-
this.device.setRenderTarget(explicitRenderTarget ?? null);
|
|
681
|
-
}
|
|
682
|
-
this.device.clear(this.clearColor);
|
|
683
|
-
if (environmentBackground) {
|
|
684
|
-
this.graph.addPass(new EnvironmentBackgroundPass({
|
|
685
|
-
...environmentBackground,
|
|
686
|
-
outputColorSpace: postprocess ? "linear" : environmentBackground.outputColorSpace,
|
|
687
|
-
inverseViewProjectionMatrix: environmentBackground.inverseViewProjectionMatrix ?? invertMat4(cameraViewProjection ?? identityMat4()),
|
|
688
|
-
shaderLibrary: this.shaderLibrary
|
|
689
|
-
}));
|
|
690
|
-
}
|
|
691
|
-
this.graph.addPass(new ForwardPass({
|
|
692
|
-
items,
|
|
693
|
-
lights,
|
|
694
|
-
environmentLighting,
|
|
695
|
-
environmentFog,
|
|
696
|
-
inputColorResource: environmentBackground ? ENVIRONMENT_BACKGROUND_COLOR_RESOURCE : undefined,
|
|
697
|
-
shadowMap: rendererShadowMap,
|
|
698
|
-
cameraPosition,
|
|
699
|
-
outputColorSpace: postprocess ? "linear" : "srgb",
|
|
700
|
-
shaderLibrary: this.shaderLibrary
|
|
701
|
-
}));
|
|
702
|
-
this.graph.execute({ device: this.device, width: this.width, height: this.height });
|
|
703
|
-
if (postprocess) {
|
|
704
|
-
await this.executePostprocessAsync(postprocess, ownedTargets, explicitRenderTarget);
|
|
705
|
-
}
|
|
706
|
-
} finally {
|
|
707
|
-
this.device.endFrame();
|
|
708
|
-
for (const shadowPass of ownedShadowPasses) {
|
|
709
|
-
shadowPass.dispose();
|
|
710
|
-
}
|
|
711
|
-
for (const target of ownedTargets) {
|
|
712
|
-
target.dispose();
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
return withRendererFrameDiagnostics(this.device.getDiagnostics(), collectionDiagnostics, createPostprocessDiagnostics(postprocess, ownedTargets, this.width, this.height, {
|
|
716
|
-
targetFormat: postprocessTargetFormat,
|
|
717
|
-
nativeLdrPostprocess: Boolean(this.device.presentLdrPostprocess),
|
|
718
|
-
rendererDepthAvailable: Boolean(postprocess && postprocessRequiresDepthTexture(postprocess) && this.device.info.capabilities?.includes("depth-textures"))
|
|
719
|
-
}));
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
renderScene(scene: RenderSource | Scene, camera?: CameraLike): RenderDeviceDiagnostics {
|
|
723
|
-
return this.render(scene, camera);
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
renderItems(items: Iterable<RenderItem>, camera?: CameraLike, options: Omit<RenderSource, "renderItems"> = {}): RenderDeviceDiagnostics {
|
|
727
|
-
return this.render({ ...options, renderItems: items }, camera);
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
captureFrame(source?: RenderSource | Iterable<RenderItem> | Scene, camera?: CameraLike): RendererFrameCaptureWithMetadata {
|
|
731
|
-
const diagnostics = source ? this.render(source, camera) : this.device.getDiagnostics();
|
|
732
|
-
const pixels = this.device.readPixels(0, 0, this.width, this.height);
|
|
733
|
-
const metadata = createRendererFrameCaptureMetadata(this.device, this.width, this.height, pixels, diagnostics);
|
|
734
|
-
return {
|
|
735
|
-
width: this.width,
|
|
736
|
-
height: this.height,
|
|
737
|
-
pixels,
|
|
738
|
-
diagnostics,
|
|
739
|
-
capturedAt: metadata.capturedAt,
|
|
740
|
-
renderSize: metadata.renderSize,
|
|
741
|
-
pixelHash: metadata.pixelHash,
|
|
742
|
-
pixelDigest: metadata.pixelDigest,
|
|
743
|
-
pixelStats: metadata.pixelStats,
|
|
744
|
-
diagnosticsSummary: metadata.diagnosticsSummary,
|
|
745
|
-
metadata
|
|
746
|
-
};
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
getDiagnostics(): RenderDeviceDiagnostics {
|
|
750
|
-
return this.device.getDiagnostics();
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
dispose(): void {
|
|
754
|
-
this.animationLoop?.stop();
|
|
755
|
-
this.animationLoop = null;
|
|
756
|
-
this.device.dispose();
|
|
757
|
-
this.disposed = true;
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
private assertAlive(): void {
|
|
761
|
-
if (this.disposed || this.device.disposed) {
|
|
762
|
-
throw new RenderDeviceError("Renderer is disposed", "DISPOSED_DEVICE");
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
private resizeCanvas(width: number, height: number): void {
|
|
767
|
-
if (!this.canvas) return;
|
|
768
|
-
if (this.canvas.width !== width) {
|
|
769
|
-
this.canvas.width = width;
|
|
770
|
-
}
|
|
771
|
-
if (this.canvas.height !== height) {
|
|
772
|
-
this.canvas.height = height;
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
private executePostprocess(postprocess: RendererPostProcessOptions, ownedTargets: RenderTarget[], outputTarget?: RenderTarget): void {
|
|
777
|
-
const forwardTarget = ownedTargets[0];
|
|
778
|
-
let current = forwardTarget;
|
|
779
|
-
if (!current) {
|
|
780
|
-
throw new RenderDeviceError("Renderer postprocess missing forward render target", "POSTPROCESS_TARGET_MISSING");
|
|
781
|
-
}
|
|
782
|
-
const passes = createRendererPostprocessPasses(postprocess);
|
|
783
|
-
if (passes.length === 0) {
|
|
784
|
-
if (outputTarget) {
|
|
785
|
-
this.device.setRenderTarget(current);
|
|
786
|
-
writePostProcessPixels(this.device, current, outputTarget, this.device.readPixels(0, 0, current.width, current.height));
|
|
787
|
-
} else {
|
|
788
|
-
this.device.presentRenderTarget?.(current);
|
|
789
|
-
}
|
|
790
|
-
return;
|
|
791
|
-
}
|
|
792
|
-
if (this.executeFusedLdrPostprocess(current, passes, outputTarget)) return;
|
|
793
|
-
for (let index = 0; index < passes.length; index += 1) {
|
|
794
|
-
const pass = passes[index]!;
|
|
795
|
-
const nextPass = passes[index + 1];
|
|
796
|
-
if (pass.name === "bloom" && isHdrRenderTarget(current)) {
|
|
797
|
-
if (nextPass?.name !== "tone-mapping") {
|
|
798
|
-
throw new RenderDeviceError("Renderer HDR bloom requires tone mapping immediately after the float bloom pass.", "HDR_BLOOM_TONEMAPPING_REQUIRED", {
|
|
799
|
-
source: current.label
|
|
800
|
-
});
|
|
801
|
-
}
|
|
802
|
-
const isCombinedLast = index + 1 === passes.length - 1;
|
|
803
|
-
const target = isCombinedLast ? outputTarget : this.device.createRenderTarget({
|
|
804
|
-
width: this.width,
|
|
805
|
-
height: this.height,
|
|
806
|
-
label: "renderer-postprocess-tone-mapping",
|
|
807
|
-
format: "rgba8",
|
|
808
|
-
depth: false
|
|
809
|
-
});
|
|
810
|
-
if (target) ownedTargets.push(target);
|
|
811
|
-
this.device.setRenderTarget(current);
|
|
812
|
-
const bloomed = bloomFloatPixels(
|
|
813
|
-
this.device.readFloatPixels(0, 0, current.width, current.height),
|
|
814
|
-
current.width,
|
|
815
|
-
current.height,
|
|
816
|
-
pass.options as BloomOptions
|
|
817
|
-
);
|
|
818
|
-
const mapped = toneMapFloatPixels(
|
|
819
|
-
bloomed.pixels,
|
|
820
|
-
current.width,
|
|
821
|
-
current.height,
|
|
822
|
-
{
|
|
823
|
-
outputColorSpace: "srgb",
|
|
824
|
-
...(nextPass.options as ToneMappingOptions)
|
|
825
|
-
}
|
|
826
|
-
);
|
|
827
|
-
writePostProcessPixels(this.device, current, target, mapped.pixels);
|
|
828
|
-
if (target) current = target;
|
|
829
|
-
index += 1;
|
|
830
|
-
continue;
|
|
831
|
-
}
|
|
832
|
-
const isLast = index === passes.length - 1;
|
|
833
|
-
const target = isLast ? outputTarget : this.device.createRenderTarget({
|
|
834
|
-
width: this.width,
|
|
835
|
-
height: this.height,
|
|
836
|
-
label: `renderer-postprocess-${pass.name}`,
|
|
837
|
-
format: "rgba8",
|
|
838
|
-
depth: false
|
|
839
|
-
});
|
|
840
|
-
if (target) ownedTargets.push(target);
|
|
841
|
-
if (pass.name === "tone-mapping") {
|
|
842
|
-
new ToneMappingPass({
|
|
843
|
-
source: current,
|
|
844
|
-
target,
|
|
845
|
-
outputColorSpace: "srgb",
|
|
846
|
-
...(pass.options as ToneMappingOptions)
|
|
847
|
-
}).execute({ device: this.device, width: this.width, height: this.height });
|
|
848
|
-
} else if (pass.name === "bloom") {
|
|
849
|
-
new BloomPass({
|
|
850
|
-
source: current,
|
|
851
|
-
target,
|
|
852
|
-
...(pass.options as BloomOptions)
|
|
853
|
-
}).execute({ device: this.device, width: this.width, height: this.height });
|
|
854
|
-
} else if (pass.name === "fxaa") {
|
|
855
|
-
new FXAAPass({
|
|
856
|
-
source: current,
|
|
857
|
-
target,
|
|
858
|
-
...(pass.options as FXAAOptions)
|
|
859
|
-
}).execute({ device: this.device, width: this.width, height: this.height });
|
|
860
|
-
} else {
|
|
861
|
-
this.executePixelPostprocessPass(pass, current, target, forwardTarget);
|
|
862
|
-
}
|
|
863
|
-
if (target) current = target;
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
private executeFusedLdrPostprocess(current: RenderTarget, passes: readonly RendererPostProcessPassPlan[], outputTarget?: RenderTarget): boolean {
|
|
868
|
-
if (!canFuseLdrPostprocess(current, passes)) return false;
|
|
869
|
-
if (this.device.presentLdrPostprocess) {
|
|
870
|
-
this.device.presentLdrPostprocess(current, {
|
|
871
|
-
passes: passes.map((pass) => ({
|
|
872
|
-
name: pass.name,
|
|
873
|
-
options: pass.options as Readonly<Record<string, unknown>>
|
|
874
|
-
})) as readonly LdrPostprocessPassDescriptor[],
|
|
875
|
-
...(outputTarget ? { outputTarget } : {}),
|
|
876
|
-
toneMappingDefaults: { outputColorSpace: "srgb" }
|
|
877
|
-
});
|
|
878
|
-
return true;
|
|
879
|
-
}
|
|
880
|
-
this.device.setRenderTarget(current);
|
|
881
|
-
const pixels = fusedLdrPostprocessPixels(
|
|
882
|
-
this.device.readPixels(0, 0, current.width, current.height),
|
|
883
|
-
current.width,
|
|
884
|
-
current.height,
|
|
885
|
-
passes as readonly FusedLdrPostProcessPass[],
|
|
886
|
-
{
|
|
887
|
-
mutateInput: true,
|
|
888
|
-
scratch: this.fusedLdrPostprocessScratch,
|
|
889
|
-
toneMappingDefaults: { outputColorSpace: "srgb" }
|
|
890
|
-
}
|
|
891
|
-
);
|
|
892
|
-
writePostProcessPixels(this.device, current, outputTarget, pixels);
|
|
893
|
-
return true;
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
private async executePostprocessAsync(postprocess: RendererPostProcessOptions, ownedTargets: RenderTarget[], outputTarget?: RenderTarget): Promise<void> {
|
|
897
|
-
const forwardTarget = ownedTargets[0];
|
|
898
|
-
let current = forwardTarget;
|
|
899
|
-
if (!current) {
|
|
900
|
-
throw new RenderDeviceError("Renderer postprocess missing forward render target", "POSTPROCESS_TARGET_MISSING");
|
|
901
|
-
}
|
|
902
|
-
const passes = createRendererPostprocessPasses(postprocess);
|
|
903
|
-
if (passes.length === 0) {
|
|
904
|
-
if (outputTarget) {
|
|
905
|
-
this.device.setRenderTarget(current);
|
|
906
|
-
writePostProcessPixels(this.device, current, outputTarget, await this.readRenderTargetPixelsAsync(current));
|
|
907
|
-
} else {
|
|
908
|
-
this.device.presentRenderTarget?.(current);
|
|
909
|
-
}
|
|
910
|
-
return;
|
|
911
|
-
}
|
|
912
|
-
if (await this.executeFusedLdrPostprocessAsync(current, passes, outputTarget)) return;
|
|
913
|
-
for (let index = 0; index < passes.length; index += 1) {
|
|
914
|
-
const pass = passes[index]!;
|
|
915
|
-
const nextPass = passes[index + 1];
|
|
916
|
-
if (pass.name === "bloom" && isHdrRenderTarget(current)) {
|
|
917
|
-
if (nextPass?.name !== "tone-mapping") {
|
|
918
|
-
throw new RenderDeviceError("Renderer HDR bloom requires tone mapping immediately after the float bloom pass.", "HDR_BLOOM_TONEMAPPING_REQUIRED", {
|
|
919
|
-
source: current.label
|
|
920
|
-
});
|
|
921
|
-
}
|
|
922
|
-
const isCombinedLast = index + 1 === passes.length - 1;
|
|
923
|
-
const target = isCombinedLast ? outputTarget : this.device.createRenderTarget({
|
|
924
|
-
width: this.width,
|
|
925
|
-
height: this.height,
|
|
926
|
-
label: "renderer-postprocess-tone-mapping",
|
|
927
|
-
format: "rgba8",
|
|
928
|
-
depth: false
|
|
929
|
-
});
|
|
930
|
-
if (target) ownedTargets.push(target);
|
|
931
|
-
const bloomed = bloomFloatPixels(
|
|
932
|
-
await this.readRenderTargetFloatPixelsAsync(current),
|
|
933
|
-
current.width,
|
|
934
|
-
current.height,
|
|
935
|
-
pass.options as BloomOptions
|
|
936
|
-
);
|
|
937
|
-
const mapped = toneMapFloatPixels(
|
|
938
|
-
bloomed.pixels,
|
|
939
|
-
current.width,
|
|
940
|
-
current.height,
|
|
941
|
-
{
|
|
942
|
-
outputColorSpace: "srgb",
|
|
943
|
-
...(nextPass.options as ToneMappingOptions)
|
|
944
|
-
}
|
|
945
|
-
);
|
|
946
|
-
writePostProcessPixels(this.device, current, target, mapped.pixels);
|
|
947
|
-
if (target) current = target;
|
|
948
|
-
index += 1;
|
|
949
|
-
continue;
|
|
950
|
-
}
|
|
951
|
-
const isLast = index === passes.length - 1;
|
|
952
|
-
const target = isLast ? outputTarget : this.device.createRenderTarget({
|
|
953
|
-
width: this.width,
|
|
954
|
-
height: this.height,
|
|
955
|
-
label: `renderer-postprocess-${pass.name}`,
|
|
956
|
-
format: "rgba8",
|
|
957
|
-
depth: false
|
|
958
|
-
});
|
|
959
|
-
if (target) ownedTargets.push(target);
|
|
960
|
-
if (pass.name === "tone-mapping") {
|
|
961
|
-
const mapped = isHdrRenderTarget(current)
|
|
962
|
-
? toneMapFloatPixels(await this.readRenderTargetFloatPixelsAsync(current), current.width, current.height, {
|
|
963
|
-
outputColorSpace: "srgb",
|
|
964
|
-
...(pass.options as ToneMappingOptions)
|
|
965
|
-
})
|
|
966
|
-
: toneMapPixels(await this.readRenderTargetPixelsAsync(current), current.width, current.height, {
|
|
967
|
-
outputColorSpace: "srgb",
|
|
968
|
-
...(pass.options as ToneMappingOptions)
|
|
969
|
-
});
|
|
970
|
-
writePostProcessPixels(this.device, current, target, mapped.pixels);
|
|
971
|
-
} else if (pass.name === "bloom") {
|
|
972
|
-
const bloomed = bloomPixels(await this.readRenderTargetPixelsAsync(current), current.width, current.height, pass.options as BloomOptions);
|
|
973
|
-
writePostProcessPixels(this.device, current, target, bloomed.pixels);
|
|
974
|
-
} else if (pass.name === "fxaa") {
|
|
975
|
-
const smoothed = fxaaPixels(await this.readRenderTargetPixelsAsync(current), current.width, current.height, pass.options as FXAAOptions);
|
|
976
|
-
writePostProcessPixels(this.device, current, target, smoothed.pixels);
|
|
977
|
-
} else {
|
|
978
|
-
await this.executePixelPostprocessPassAsync(pass, current, target, forwardTarget);
|
|
979
|
-
}
|
|
980
|
-
if (target) current = target;
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
private async executeFusedLdrPostprocessAsync(current: RenderTarget, passes: readonly RendererPostProcessPassPlan[], outputTarget?: RenderTarget): Promise<boolean> {
|
|
985
|
-
if (!canFuseLdrPostprocess(current, passes)) return false;
|
|
986
|
-
if (this.device.presentLdrPostprocess) {
|
|
987
|
-
this.device.presentLdrPostprocess(current, {
|
|
988
|
-
passes: passes.map((pass) => ({
|
|
989
|
-
name: pass.name,
|
|
990
|
-
options: pass.options as Readonly<Record<string, unknown>>
|
|
991
|
-
})) as readonly LdrPostprocessPassDescriptor[],
|
|
992
|
-
...(outputTarget ? { outputTarget } : {}),
|
|
993
|
-
toneMappingDefaults: { outputColorSpace: "srgb" }
|
|
994
|
-
});
|
|
995
|
-
return true;
|
|
996
|
-
}
|
|
997
|
-
const pixels = fusedLdrPostprocessPixels(
|
|
998
|
-
await this.readRenderTargetPixelsAsync(current),
|
|
999
|
-
current.width,
|
|
1000
|
-
current.height,
|
|
1001
|
-
passes as readonly FusedLdrPostProcessPass[],
|
|
1002
|
-
{
|
|
1003
|
-
mutateInput: true,
|
|
1004
|
-
scratch: this.fusedLdrPostprocessScratch,
|
|
1005
|
-
toneMappingDefaults: { outputColorSpace: "srgb" }
|
|
1006
|
-
}
|
|
1007
|
-
);
|
|
1008
|
-
writePostProcessPixels(this.device, current, outputTarget, pixels);
|
|
1009
|
-
return true;
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
private executePixelPostprocessPass(pass: RendererPostProcessPassPlan, source: RenderTarget, target: RenderTarget | undefined, forwardTarget: RenderTarget): void {
|
|
1013
|
-
this.device.setRenderTarget(source);
|
|
1014
|
-
const input = this.device.readPixels(0, 0, source.width, source.height);
|
|
1015
|
-
const rendererDepth = isDepthPostprocessPass(pass.name) && !postprocessPassHasDepth(pass.options)
|
|
1016
|
-
? this.readRendererOwnedDepthTexture(forwardTarget)
|
|
1017
|
-
: undefined;
|
|
1018
|
-
const result = pass.name === "color-grade"
|
|
1019
|
-
? colorGradePixels(input, source.width, source.height, pass.options as ColorGradeOptions).pixels
|
|
1020
|
-
: pass.name === "chromatic-aberration"
|
|
1021
|
-
? chromaticAberrationPixels(input, source.width, source.height, pass.options as ChromaticAberrationOptions).pixels
|
|
1022
|
-
: pass.name === "film-grain"
|
|
1023
|
-
? filmGrainPixels(input, source.width, source.height, pass.options as FilmGrainOptions).pixels
|
|
1024
|
-
: pass.name === "depth-of-field"
|
|
1025
|
-
? depthOfFieldPixels(input, source.width, source.height, withRendererDepth(pass.options as DepthOfFieldOptions, rendererDepth)).pixels
|
|
1026
|
-
: pass.name === "motion-blur"
|
|
1027
|
-
? motionBlurPixels(input, source.width, source.height, pass.options as MotionBlurOptions).pixels
|
|
1028
|
-
: pass.name === "contact-shadow"
|
|
1029
|
-
? contactShadowPixels(input, source.width, source.height, withRendererDepth(pass.options as ContactShadowPostProcessOptions, rendererDepth)).pixels
|
|
1030
|
-
: pass.name === "ssao"
|
|
1031
|
-
? ssaoPixels(input, source.width, source.height, withRendererDepth(pass.options as SSAOOptions, rendererDepth)).pixels
|
|
1032
|
-
: pass.name === "ssr"
|
|
1033
|
-
? ssrPixels(input, source.width, source.height, withRendererDepth(pass.options as SSROptions, rendererDepth)).pixels
|
|
1034
|
-
: pass.name === "taa"
|
|
1035
|
-
? taaPixels(input, source.width, source.height, pass.options as TAAOptions).pixels
|
|
1036
|
-
: pass.name === "outline"
|
|
1037
|
-
? outlinePixels(input, source.width, source.height, pass.options as OutlineOptions).pixels
|
|
1038
|
-
: undefined;
|
|
1039
|
-
if (!result) {
|
|
1040
|
-
throw new RenderDeviceError("Renderer postprocess pass is outside the supported renderer pass catalog", "POSTPROCESS_PASS_UNKNOWN", {
|
|
1041
|
-
pass: pass.name
|
|
1042
|
-
});
|
|
1043
|
-
}
|
|
1044
|
-
writePostProcessPixels(this.device, source, target, result);
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
private async executePixelPostprocessPassAsync(pass: RendererPostProcessPassPlan, source: RenderTarget, target: RenderTarget | undefined, forwardTarget: RenderTarget): Promise<void> {
|
|
1048
|
-
const input = await this.readRenderTargetPixelsAsync(source);
|
|
1049
|
-
const rendererDepth = isDepthPostprocessPass(pass.name) && !postprocessPassHasDepth(pass.options)
|
|
1050
|
-
? this.readRendererOwnedDepthTexture(forwardTarget)
|
|
1051
|
-
: undefined;
|
|
1052
|
-
const result = pass.name === "color-grade"
|
|
1053
|
-
? colorGradePixels(input, source.width, source.height, pass.options as ColorGradeOptions).pixels
|
|
1054
|
-
: pass.name === "chromatic-aberration"
|
|
1055
|
-
? chromaticAberrationPixels(input, source.width, source.height, pass.options as ChromaticAberrationOptions).pixels
|
|
1056
|
-
: pass.name === "film-grain"
|
|
1057
|
-
? filmGrainPixels(input, source.width, source.height, pass.options as FilmGrainOptions).pixels
|
|
1058
|
-
: pass.name === "depth-of-field"
|
|
1059
|
-
? depthOfFieldPixels(input, source.width, source.height, withRendererDepth(pass.options as DepthOfFieldOptions, rendererDepth)).pixels
|
|
1060
|
-
: pass.name === "motion-blur"
|
|
1061
|
-
? motionBlurPixels(input, source.width, source.height, pass.options as MotionBlurOptions).pixels
|
|
1062
|
-
: pass.name === "contact-shadow"
|
|
1063
|
-
? contactShadowPixels(input, source.width, source.height, withRendererDepth(pass.options as ContactShadowPostProcessOptions, rendererDepth)).pixels
|
|
1064
|
-
: pass.name === "ssao"
|
|
1065
|
-
? ssaoPixels(input, source.width, source.height, withRendererDepth(pass.options as SSAOOptions, rendererDepth)).pixels
|
|
1066
|
-
: pass.name === "ssr"
|
|
1067
|
-
? ssrPixels(input, source.width, source.height, withRendererDepth(pass.options as SSROptions, rendererDepth)).pixels
|
|
1068
|
-
: pass.name === "taa"
|
|
1069
|
-
? taaPixels(input, source.width, source.height, pass.options as TAAOptions).pixels
|
|
1070
|
-
: pass.name === "outline"
|
|
1071
|
-
? outlinePixels(input, source.width, source.height, pass.options as OutlineOptions).pixels
|
|
1072
|
-
: undefined;
|
|
1073
|
-
if (!result) {
|
|
1074
|
-
throw new RenderDeviceError("Renderer postprocess pass is outside the supported renderer pass catalog", "POSTPROCESS_PASS_UNKNOWN", {
|
|
1075
|
-
pass: pass.name
|
|
1076
|
-
});
|
|
1077
|
-
}
|
|
1078
|
-
writePostProcessPixels(this.device, source, target, result);
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
private async readRenderTargetPixelsAsync(target: RenderTarget): Promise<Uint8Array> {
|
|
1082
|
-
this.device.setRenderTarget(target);
|
|
1083
|
-
if (this.device.readPixelsAsync && target.colorTexture.format === "rgba8") {
|
|
1084
|
-
return this.device.readPixelsAsync(0, 0, target.width, target.height);
|
|
1085
|
-
}
|
|
1086
|
-
return this.device.readPixels(0, 0, target.width, target.height);
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
private async readRenderTargetFloatPixelsAsync(target: RenderTarget): Promise<Float32Array> {
|
|
1090
|
-
this.device.setRenderTarget(target);
|
|
1091
|
-
if (this.device.readFloatPixelsAsync && isHdrRenderTarget(target)) {
|
|
1092
|
-
return this.device.readFloatPixelsAsync(0, 0, target.width, target.height);
|
|
1093
|
-
}
|
|
1094
|
-
return this.device.readFloatPixels(0, 0, target.width, target.height);
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
private readRendererOwnedDepthTexture(forwardTarget: RenderTarget): DepthTextureBinding {
|
|
1098
|
-
if (!forwardTarget.depthTexture) {
|
|
1099
|
-
throw new RenderDeviceError("Renderer-owned depth postprocess requires the forward target to expose a depth texture.", "POSTPROCESS_DEPTH_TARGET_MISSING", {
|
|
1100
|
-
renderTarget: forwardTarget.label
|
|
1101
|
-
});
|
|
1102
|
-
}
|
|
1103
|
-
if (!this.device.readDepthPixels) {
|
|
1104
|
-
throw new RenderDeviceError("Renderer-owned depth postprocess requires backend depth readback.", "DEPTH_READBACK_UNSUPPORTED", {
|
|
1105
|
-
backend: this.device.kind
|
|
1106
|
-
});
|
|
1107
|
-
}
|
|
1108
|
-
this.device.setRenderTarget(forwardTarget);
|
|
1109
|
-
return createDepthTextureBinding({
|
|
1110
|
-
label: forwardTarget.depthTexture.label,
|
|
1111
|
-
width: forwardTarget.width,
|
|
1112
|
-
height: forwardTarget.height,
|
|
1113
|
-
data: this.device.readDepthPixels(0, 0, forwardTarget.width, forwardTarget.height)
|
|
1114
|
-
});
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
private executeRendererShadowMap(options: {
|
|
1118
|
-
readonly shadowOptions: RendererShadowOptions | undefined;
|
|
1119
|
-
readonly source: RenderSource | Iterable<RenderItem> | Scene;
|
|
1120
|
-
readonly items: readonly RenderItem[];
|
|
1121
|
-
readonly lights: readonly CollectedLight[];
|
|
1122
|
-
readonly ownedTargets: RenderTarget[];
|
|
1123
|
-
readonly ownedShadowPasses: ShadowPass[];
|
|
1124
|
-
}): ForwardShadowMapOptions | undefined {
|
|
1125
|
-
if (!options.shadowOptions || options.shadowOptions.enabled === false) {
|
|
1126
|
-
return undefined;
|
|
1127
|
-
}
|
|
1128
|
-
if (!this.device.info.capabilities?.includes("render-targets")) {
|
|
1129
|
-
throw new RenderDeviceError("Renderer-owned shadows require render targets", "SHADOW_RENDER_TARGET_UNSUPPORTED", {
|
|
1130
|
-
backend: this.device.kind
|
|
1131
|
-
});
|
|
1132
|
-
}
|
|
1133
|
-
const light = options.shadowOptions.light ?? firstShadowCastingLight(options.source, options.lights);
|
|
1134
|
-
if (light instanceof PointLight) {
|
|
1135
|
-
return this.executeRendererPointShadowMap({
|
|
1136
|
-
shadowOptions: options.shadowOptions,
|
|
1137
|
-
items: options.items,
|
|
1138
|
-
ownedTargets: options.ownedTargets,
|
|
1139
|
-
ownedShadowPasses: options.ownedShadowPasses,
|
|
1140
|
-
light
|
|
1141
|
-
});
|
|
1142
|
-
}
|
|
1143
|
-
if (light && !(light instanceof DirectionalLight) && !(light instanceof SpotLight)) {
|
|
1144
|
-
throw new RenderDeviceError("Renderer-owned shadow maps require directional, spot, or point lights.", "SHADOW_LIGHT_TYPE_UNSUPPORTED", {
|
|
1145
|
-
lightName: light.name,
|
|
1146
|
-
lightType: light.constructor.name
|
|
1147
|
-
});
|
|
1148
|
-
}
|
|
1149
|
-
const lightMatrix = options.shadowOptions.lightMatrix
|
|
1150
|
-
? toMat4(options.shadowOptions.lightMatrix, "shadow.lightMatrix")
|
|
1151
|
-
: createRendererOwnedShadowMatrix(light, options.items, firstShadowCastingCollectedLight(options.lights));
|
|
1152
|
-
const shadowMap = new ShadowMap({
|
|
1153
|
-
size: options.shadowOptions.size,
|
|
1154
|
-
bias: options.shadowOptions.bias,
|
|
1155
|
-
filter: options.shadowOptions.filter,
|
|
1156
|
-
pcfRadius: options.shadowOptions.pcfRadius,
|
|
1157
|
-
pcfSamples: options.shadowOptions.pcfSamples,
|
|
1158
|
-
pcfDistribution: options.shadowOptions.pcfDistribution,
|
|
1159
|
-
label: options.shadowOptions.label ?? "renderer-shadow-map"
|
|
1160
|
-
});
|
|
1161
|
-
const shadowPass = new ShadowPass({
|
|
1162
|
-
light,
|
|
1163
|
-
casters: options.items,
|
|
1164
|
-
shadowMap,
|
|
1165
|
-
viewProjectionMatrix: lightMatrix,
|
|
1166
|
-
shaderLibrary: this.shaderLibrary
|
|
1167
|
-
});
|
|
1168
|
-
options.ownedShadowPasses.push(shadowPass);
|
|
1169
|
-
const result = shadowPass.execute({ device: this.device, width: this.width, height: this.height });
|
|
1170
|
-
if (!result.rendered) {
|
|
1171
|
-
if (result.reason === "no-light" || result.reason === "light-disabled" || result.reason === "not-shadow-casting") {
|
|
1172
|
-
throw new RenderDeviceError("Renderer-owned shadows require an enabled shadow-casting light.", "SHADOW_LIGHT_REQUIRED", {
|
|
1173
|
-
reason: result.reason
|
|
1174
|
-
});
|
|
1175
|
-
}
|
|
1176
|
-
return undefined;
|
|
1177
|
-
}
|
|
1178
|
-
return shadowPass.getForwardShadowMap({
|
|
1179
|
-
lightMatrix,
|
|
1180
|
-
strength: options.shadowOptions.strength,
|
|
1181
|
-
slopeBias: options.shadowOptions.slopeBias,
|
|
1182
|
-
texelSize: options.shadowOptions.texelSize,
|
|
1183
|
-
bias: options.shadowOptions.bias,
|
|
1184
|
-
filterKernel: options.shadowOptions.filterKernel
|
|
1185
|
-
}) ?? undefined;
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
private executeRendererPointShadowMap(options: {
|
|
1189
|
-
readonly shadowOptions: RendererShadowOptions;
|
|
1190
|
-
readonly items: readonly RenderItem[];
|
|
1191
|
-
readonly ownedTargets: RenderTarget[];
|
|
1192
|
-
readonly ownedShadowPasses: ShadowPass[];
|
|
1193
|
-
readonly light: PointLight;
|
|
1194
|
-
}): ForwardShadowMapOptions | undefined {
|
|
1195
|
-
if (!this.device.writeRenderTargetPixels) {
|
|
1196
|
-
throw new RenderDeviceError("Renderer-owned point shadows require render-target pixel upload for the point-light atlas.", "POINT_SHADOW_ATLAS_UPLOAD_UNSUPPORTED", {
|
|
1197
|
-
backend: this.device.kind
|
|
1198
|
-
});
|
|
1199
|
-
}
|
|
1200
|
-
options.light.transform.updateWorld(undefined, true);
|
|
1201
|
-
const size = options.shadowOptions.size ?? 512;
|
|
1202
|
-
const faceMatrices = createPointShadowFaceMatrices(options.light);
|
|
1203
|
-
const faceRects = createPointShadowFaceRects();
|
|
1204
|
-
const shadowMapOptions = {
|
|
1205
|
-
size,
|
|
1206
|
-
bias: options.shadowOptions.bias,
|
|
1207
|
-
filter: options.shadowOptions.filter,
|
|
1208
|
-
pcfRadius: options.shadowOptions.pcfRadius,
|
|
1209
|
-
pcfSamples: options.shadowOptions.pcfSamples,
|
|
1210
|
-
pcfDistribution: options.shadowOptions.pcfDistribution
|
|
1211
|
-
};
|
|
1212
|
-
const atlasPixels = new Uint8Array(size * 3 * size * 2 * 4);
|
|
1213
|
-
for (let face = 0; face < 6; face += 1) {
|
|
1214
|
-
const faceMatrix = faceMatrices.slice(face * 16, face * 16 + 16);
|
|
1215
|
-
const shadowPass = new ShadowPass({
|
|
1216
|
-
light: options.light,
|
|
1217
|
-
casters: options.items,
|
|
1218
|
-
shadowMap: new ShadowMap({ ...shadowMapOptions, label: `${options.shadowOptions.label ?? "renderer-point-shadow"}-face-${face}` }),
|
|
1219
|
-
viewProjectionMatrix: faceMatrix,
|
|
1220
|
-
shaderLibrary: this.shaderLibrary
|
|
1221
|
-
});
|
|
1222
|
-
options.ownedShadowPasses.push(shadowPass);
|
|
1223
|
-
const result = shadowPass.execute({ device: this.device, width: this.width, height: this.height });
|
|
1224
|
-
if (!result.rendered) {
|
|
1225
|
-
return undefined;
|
|
1226
|
-
}
|
|
1227
|
-
const target = shadowPass.getRenderTarget();
|
|
1228
|
-
if (!target) {
|
|
1229
|
-
throw new RenderDeviceError("Renderer-owned point shadow face did not expose a render target.", "POINT_SHADOW_FACE_TARGET_MISSING", { face });
|
|
1230
|
-
}
|
|
1231
|
-
const facePixels = readShadowFacePixels(this.device, target);
|
|
1232
|
-
blitPointShadowFace(atlasPixels, size * 3, size * 2, facePixels, size, faceRects, face);
|
|
1233
|
-
}
|
|
1234
|
-
const atlasTarget = this.device.createRenderTarget({
|
|
1235
|
-
width: size * 3,
|
|
1236
|
-
height: size * 2,
|
|
1237
|
-
label: `${options.shadowOptions.label ?? "renderer-point-shadow"}-atlas`,
|
|
1238
|
-
format: "rgba8",
|
|
1239
|
-
depth: false
|
|
1240
|
-
});
|
|
1241
|
-
options.ownedTargets.push(atlasTarget);
|
|
1242
|
-
this.device.writeRenderTargetPixels(atlasTarget, atlasPixels);
|
|
1243
|
-
const texture = new TextureBinding({
|
|
1244
|
-
name: "u_pointShadowMapTexture",
|
|
1245
|
-
texture: atlasTarget.colorTexture,
|
|
1246
|
-
sampler: new Sampler({ minFilter: "nearest", magFilter: "nearest", addressU: "clamp-to-edge", addressV: "clamp-to-edge" }),
|
|
1247
|
-
required: true
|
|
1248
|
-
});
|
|
1249
|
-
return {
|
|
1250
|
-
texture: new TextureBinding({
|
|
1251
|
-
name: "u_shadowMapTexture",
|
|
1252
|
-
texture: atlasTarget.colorTexture,
|
|
1253
|
-
sampler: new Sampler({ minFilter: "nearest", magFilter: "nearest", addressU: "clamp-to-edge", addressV: "clamp-to-edge" }),
|
|
1254
|
-
required: true
|
|
1255
|
-
}),
|
|
1256
|
-
lightMatrix: identityMat4(),
|
|
1257
|
-
strength: options.shadowOptions.strength,
|
|
1258
|
-
slopeBias: options.shadowOptions.slopeBias,
|
|
1259
|
-
texelSize: options.shadowOptions.texelSize,
|
|
1260
|
-
bias: options.shadowOptions.bias,
|
|
1261
|
-
filterKernel: options.shadowOptions.filterKernel,
|
|
1262
|
-
pointLight: {
|
|
1263
|
-
texture,
|
|
1264
|
-
lightPosition: [options.light.transform.worldMatrix[12], options.light.transform.worldMatrix[13], options.light.transform.worldMatrix[14]],
|
|
1265
|
-
range: options.light.range,
|
|
1266
|
-
faceMatrices,
|
|
1267
|
-
faceRects,
|
|
1268
|
-
strength: options.shadowOptions.strength,
|
|
1269
|
-
slopeBias: options.shadowOptions.slopeBias,
|
|
1270
|
-
texelSize: [1 / Math.max(1, size * 3), 1 / Math.max(1, size * 2)],
|
|
1271
|
-
bias: options.shadowOptions.bias,
|
|
1272
|
-
filterKernel: options.shadowOptions.filterKernel
|
|
1273
|
-
}
|
|
1274
|
-
};
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
export function pickSceneRenderables(
|
|
1279
|
-
source: Pick<RenderSource, "geometryLibrary" | "morphTargetLibrary" | "scene">,
|
|
1280
|
-
ray: Ray,
|
|
1281
|
-
options: ScenePickOptions = {}
|
|
1282
|
-
): ScenePickHit | undefined {
|
|
1283
|
-
return pickSceneRenderableHits(source, ray, options)[0];
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
export function pickSceneRenderableHits(
|
|
1287
|
-
source: Pick<RenderSource, "geometryLibrary" | "morphTargetLibrary" | "scene">,
|
|
1288
|
-
ray: Ray,
|
|
1289
|
-
options: ScenePickOptions = {}
|
|
1290
|
-
): readonly ScenePickHit[] {
|
|
1291
|
-
const pointRadius = options.pointRadius;
|
|
1292
|
-
const lineRadius = options.lineRadius;
|
|
1293
|
-
if (pointRadius !== undefined && (!Number.isFinite(pointRadius) || pointRadius <= 0)) {
|
|
1294
|
-
throw new RenderDeviceError("Scene point picking radius must be a finite positive number", "SCENE_PICK_RADIUS_INVALID", { pointRadius });
|
|
1295
|
-
}
|
|
1296
|
-
if (lineRadius !== undefined && (!Number.isFinite(lineRadius) || lineRadius <= 0)) {
|
|
1297
|
-
throw new RenderDeviceError("Scene line picking radius must be a finite positive number", "SCENE_PICK_RADIUS_INVALID", { lineRadius });
|
|
1298
|
-
}
|
|
1299
|
-
const scene = source.scene;
|
|
1300
|
-
if (!scene) {
|
|
1301
|
-
throw new RenderDeviceError("Scene picking requires a scene", "SCENE_PICKING_SCENE_MISSING");
|
|
1302
|
-
}
|
|
1303
|
-
if (!source.geometryLibrary) {
|
|
1304
|
-
throw new RenderDeviceError("Scene picking requires a geometryLibrary resource lookup", "SCENE_PICKING_RESOURCES_MISSING");
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
scene.updateWorldTransforms();
|
|
1308
|
-
const hits: ScenePickHit[] = [];
|
|
1309
|
-
for (const { node, renderable } of scene.collectRenderables()) {
|
|
1310
|
-
const geometry = lookupRenderResource(source.geometryLibrary, renderable.geometry);
|
|
1311
|
-
if (!geometry) {
|
|
1312
|
-
throw new RenderDeviceError("Scene pick target references missing geometry", "SCENE_PICK_GEOMETRY_MISSING", {
|
|
1313
|
-
node: node.name,
|
|
1314
|
-
geometry: renderable.geometry
|
|
1315
|
-
});
|
|
1316
|
-
}
|
|
1317
|
-
const morphTargets = source.morphTargetLibrary ? lookupRenderResource(source.morphTargetLibrary, renderable.geometry) : undefined;
|
|
1318
|
-
if (renderable.morphWeights.length > 0 && !morphTargets) {
|
|
1319
|
-
throw new RenderDeviceError("Scene pick target has morph weights but no morph target resource entry", "SCENE_PICK_MORPH_TARGETS_MISSING", {
|
|
1320
|
-
node: node.name,
|
|
1321
|
-
geometry: renderable.geometry,
|
|
1322
|
-
morphWeights: renderable.morphWeights.length
|
|
1323
|
-
});
|
|
1324
|
-
}
|
|
1325
|
-
const bounds = renderableWorldBounds(geometry, node.transform.worldMatrix, renderable.instanceTransforms, morphTargets, renderable.morphWeights, renderable.skinning);
|
|
1326
|
-
if (geometry.topology === "points" && options.pointRadius !== undefined) {
|
|
1327
|
-
hits.push(...pickPoints(node, geometry, ray, node.transform.worldMatrix, renderable.instanceTransforms, options.pointRadius));
|
|
1328
|
-
continue;
|
|
1329
|
-
}
|
|
1330
|
-
const pickBounds = geometry.topology === "lines" && options.lineRadius !== undefined
|
|
1331
|
-
? expandSceneBounds(bounds, options.lineRadius)
|
|
1332
|
-
: bounds;
|
|
1333
|
-
const hitPoint = ray.intersectBox(pickBounds.toMathBox());
|
|
1334
|
-
if (hitPoint) {
|
|
1335
|
-
hits.push({ node, geometry, bounds: pickBounds, distance: hitPoint.distanceTo(ray.origin), hitPoint: hitPoint.toArray() });
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
return hits.sort((left, right) => left.distance - right.distance);
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
function pickPoints(
|
|
1342
|
-
node: SceneNode,
|
|
1343
|
-
geometry: Geometry,
|
|
1344
|
-
ray: Ray,
|
|
1345
|
-
modelMatrix: Mat4,
|
|
1346
|
-
instanceTransforms: Float32Array | readonly number[] | undefined,
|
|
1347
|
-
pointRadius: number
|
|
1348
|
-
): readonly ScenePickHit[] {
|
|
1349
|
-
const hits: ScenePickHit[] = [];
|
|
1350
|
-
const radiusSquared = pointRadius * pointRadius;
|
|
1351
|
-
const transforms = instanceTransforms ? collectInstanceWorldMatrices(modelMatrix, instanceTransforms) : [modelMatrix];
|
|
1352
|
-
for (let instanceIndex = 0; instanceIndex < transforms.length; instanceIndex += 1) {
|
|
1353
|
-
const transform = transforms[instanceIndex]!;
|
|
1354
|
-
for (let pointIndex = 0; pointIndex < geometry.vertexBuffer.vertexCount; pointIndex += 1) {
|
|
1355
|
-
const position = geometry.vertexBuffer.getAttribute(pointIndex, "position");
|
|
1356
|
-
const worldPoint = transformPoint(transform, [position[0] ?? 0, position[1] ?? 0, position[2] ?? 0]);
|
|
1357
|
-
const distance = distanceAlongRay(ray, worldPoint);
|
|
1358
|
-
if (distance < 0) continue;
|
|
1359
|
-
const radialDistanceSquared = squaredDistanceToRayAt(ray, worldPoint, distance);
|
|
1360
|
-
if (radialDistanceSquared <= radiusSquared) {
|
|
1361
|
-
hits.push({
|
|
1362
|
-
node,
|
|
1363
|
-
geometry,
|
|
1364
|
-
bounds: expandPointBounds(worldPoint, pointRadius),
|
|
1365
|
-
distance,
|
|
1366
|
-
hitPoint: worldPoint,
|
|
1367
|
-
pointIndex,
|
|
1368
|
-
instanceIndex: instanceTransforms ? instanceIndex : undefined
|
|
1369
|
-
});
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
return hits;
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
function collectInstanceWorldMatrices(modelMatrix: Mat4, instanceTransforms: Float32Array | readonly number[]): readonly Mat4[] {
|
|
1377
|
-
const matrices: Mat4[] = [];
|
|
1378
|
-
for (let offset = 0; offset < instanceTransforms.length; offset += 16) {
|
|
1379
|
-
matrices.push(multiplyMat4(modelMatrix, toMat4(instanceTransforms.slice(offset, offset + 16), "instanceTransforms")));
|
|
1380
|
-
}
|
|
1381
|
-
return matrices;
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
|
-
function distanceAlongRay(ray: Ray, point: readonly [number, number, number]): number {
|
|
1385
|
-
return (
|
|
1386
|
-
(point[0] - ray.origin.x) * ray.direction.x +
|
|
1387
|
-
(point[1] - ray.origin.y) * ray.direction.y +
|
|
1388
|
-
(point[2] - ray.origin.z) * ray.direction.z
|
|
1389
|
-
);
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
|
-
function squaredDistanceToRayAt(ray: Ray, point: readonly [number, number, number], distance: number): number {
|
|
1393
|
-
const closestX = ray.origin.x + ray.direction.x * distance;
|
|
1394
|
-
const closestY = ray.origin.y + ray.direction.y * distance;
|
|
1395
|
-
const closestZ = ray.origin.z + ray.direction.z * distance;
|
|
1396
|
-
const dx = point[0] - closestX;
|
|
1397
|
-
const dy = point[1] - closestY;
|
|
1398
|
-
const dz = point[2] - closestZ;
|
|
1399
|
-
return dx * dx + dy * dy + dz * dz;
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
function expandPointBounds(center: readonly [number, number, number], radius: number): SceneBounds3 {
|
|
1403
|
-
return new SceneBounds3(
|
|
1404
|
-
[center[0] - radius, center[1] - radius, center[2] - radius],
|
|
1405
|
-
[center[0] + radius, center[1] + radius, center[2] + radius]
|
|
1406
|
-
);
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1409
|
-
function expandSceneBounds(bounds: SceneBounds3, radius: number): SceneBounds3 {
|
|
1410
|
-
return new SceneBounds3(
|
|
1411
|
-
[bounds.min[0] - radius, bounds.min[1] - radius, bounds.min[2] - radius],
|
|
1412
|
-
[bounds.max[0] + radius, bounds.max[1] + radius, bounds.max[2] + radius]
|
|
1413
|
-
);
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
class RendererAnimationLoopImpl implements RendererAnimationLoop {
|
|
1417
|
-
private requestId: number | null = null;
|
|
1418
|
-
public running = false;
|
|
1419
|
-
|
|
1420
|
-
constructor(
|
|
1421
|
-
private readonly renderer: Renderer,
|
|
1422
|
-
private readonly callback: (timeMs: number, renderer: Renderer) => void
|
|
1423
|
-
) {}
|
|
1424
|
-
|
|
1425
|
-
start(): void {
|
|
1426
|
-
if (typeof requestAnimationFrame !== "function" || typeof cancelAnimationFrame !== "function") {
|
|
1427
|
-
throw new RenderDeviceError("Renderer animation loops require requestAnimationFrame", "ANIMATION_LOOP_UNAVAILABLE");
|
|
1428
|
-
}
|
|
1429
|
-
this.running = true;
|
|
1430
|
-
this.requestId = requestAnimationFrame((timeMs) => this.tick(timeMs));
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
stop(): void {
|
|
1434
|
-
this.running = false;
|
|
1435
|
-
if (this.requestId !== null) {
|
|
1436
|
-
cancelAnimationFrame(this.requestId);
|
|
1437
|
-
this.requestId = null;
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
|
-
private tick(timeMs: number): void {
|
|
1442
|
-
if (!this.running) {
|
|
1443
|
-
return;
|
|
1444
|
-
}
|
|
1445
|
-
this.callback(timeMs, this.renderer);
|
|
1446
|
-
if (this.running) {
|
|
1447
|
-
this.requestId = requestAnimationFrame((nextTimeMs) => this.tick(nextTimeMs));
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
function readCanvasCssSize(canvas: HTMLCanvasElement | OffscreenCanvas, axis: "width" | "height"): number {
|
|
1453
|
-
if ("getBoundingClientRect" in canvas) {
|
|
1454
|
-
const bounds = canvas.getBoundingClientRect();
|
|
1455
|
-
const value = axis === "width" ? bounds.width : bounds.height;
|
|
1456
|
-
if (value > 0) {
|
|
1457
|
-
return value;
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
|
-
return axis === "width" ? canvas.width : canvas.height;
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
function inferInitialCanvasDimension(canvas: HTMLCanvasElement | OffscreenCanvas | undefined, axis: "width" | "height"): number {
|
|
1464
|
-
if (!canvas) return 1;
|
|
1465
|
-
const cssSize = readCanvasCssSize(canvas, axis);
|
|
1466
|
-
const dpr = Number.isFinite(globalThis.devicePixelRatio) && globalThis.devicePixelRatio > 0 ? globalThis.devicePixelRatio : 1;
|
|
1467
|
-
const displaySize = Math.round(cssSize * dpr);
|
|
1468
|
-
return Math.max(1, displaySize || (axis === "width" ? canvas.width : canvas.height) || 1);
|
|
1469
|
-
}
|
|
1470
|
-
|
|
1471
|
-
function collectEnvironmentLighting(source: RenderSource | Iterable<RenderItem> | Scene): EnvironmentLightingOptions | undefined {
|
|
1472
|
-
if (source instanceof Scene || isIterable(source)) return undefined;
|
|
1473
|
-
if (source.environmentLighting === false) return cloneEnvironmentLighting(DISABLED_RENDERER_ENVIRONMENT_LIGHTING);
|
|
1474
|
-
if (source.environmentLighting) return source.environmentLighting;
|
|
1475
|
-
return cloneEnvironmentLighting(DEFAULT_RENDERER_ENVIRONMENT_LIGHTING);
|
|
1476
|
-
}
|
|
1477
|
-
|
|
1478
|
-
function collectEnvironmentBackground(source: RenderSource | Iterable<RenderItem> | Scene): EnvironmentBackgroundOptions | undefined {
|
|
1479
|
-
if (source instanceof Scene || isIterable(source) || source.environmentBackground === false) return undefined;
|
|
1480
|
-
return source.environmentBackground;
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
function collectEnvironmentFog(source: RenderSource | Iterable<RenderItem> | Scene): ForwardEnvironmentFogOptions | false | undefined {
|
|
1484
|
-
if (source instanceof Scene || isIterable(source)) return undefined;
|
|
1485
|
-
return source.environmentFog;
|
|
1486
|
-
}
|
|
1487
|
-
|
|
1488
|
-
function collectForwardShadowMap(source: RenderSource | Iterable<RenderItem> | Scene): ForwardShadowMapOptions | undefined {
|
|
1489
|
-
return source instanceof Scene || isIterable(source) ? undefined : source.shadowMap;
|
|
1490
|
-
}
|
|
1491
|
-
|
|
1492
|
-
function collectRenderTarget(source: RenderSource | Iterable<RenderItem> | Scene): RenderTarget | undefined {
|
|
1493
|
-
return source instanceof Scene || isIterable(source) ? undefined : source.renderTarget;
|
|
1494
|
-
}
|
|
1495
|
-
|
|
1496
|
-
function validateExplicitRenderTarget(target: RenderTarget | undefined, width: number, height: number): void {
|
|
1497
|
-
if (!target) return;
|
|
1498
|
-
if (target.disposed) {
|
|
1499
|
-
throw new RenderDeviceError("Renderer renderTarget must be a live render target.", "RENDER_TARGET_DISPOSED", {
|
|
1500
|
-
renderTarget: target.label
|
|
1501
|
-
});
|
|
1502
|
-
}
|
|
1503
|
-
if (target.width !== width || target.height !== height) {
|
|
1504
|
-
throw new RenderDeviceError("Renderer renderTarget dimensions must match the renderer viewport.", "RENDER_TARGET_VIEWPORT_MISMATCH", {
|
|
1505
|
-
renderTarget: target.label,
|
|
1506
|
-
targetWidth: target.width,
|
|
1507
|
-
targetHeight: target.height,
|
|
1508
|
-
rendererWidth: width,
|
|
1509
|
-
rendererHeight: height
|
|
1510
|
-
});
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
function collectRendererShadowOptions(source: RenderSource | Iterable<RenderItem> | Scene): RendererShadowOptions | undefined {
|
|
1515
|
-
if (source instanceof Scene || isIterable(source)) return undefined;
|
|
1516
|
-
if (source.shadow === true) return {};
|
|
1517
|
-
if (!source.shadow) return undefined;
|
|
1518
|
-
return source.shadow;
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
function firstShadowCastingLight(source: RenderSource | Iterable<RenderItem> | Scene, lights: readonly CollectedLight[]): Light | null {
|
|
1522
|
-
const explicit = lights.find((light) => light.castsShadow && light.source.visible)?.source;
|
|
1523
|
-
if (explicit) return explicit;
|
|
1524
|
-
const scene = sceneFromSource(source);
|
|
1525
|
-
return scene?.collectLights().find((light) => light.visible && light.castsShadow) ?? null;
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
|
-
function firstShadowCastingCollectedLight(lights: readonly CollectedLight[]): CollectedLight | undefined {
|
|
1529
|
-
return lights.find((light) => light.castsShadow && light.source.visible);
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
function lightDirectionFromLight(light: Light | null): Vec3 {
|
|
1533
|
-
if (!light) return [0, -1, -1];
|
|
1534
|
-
const direction = light instanceof DirectionalLight ? light.getDirection() : undefined;
|
|
1535
|
-
return normalizeVec3([
|
|
1536
|
-
Number(direction?.[0] ?? 0),
|
|
1537
|
-
Number(direction?.[1] ?? -1),
|
|
1538
|
-
Number(direction?.[2] ?? -1)
|
|
1539
|
-
]);
|
|
1540
|
-
}
|
|
1541
|
-
|
|
1542
|
-
function createRendererOwnedShadowMatrix(light: Light | null, items: readonly RenderItem[], collectedLight: CollectedLight | undefined): Mat4 {
|
|
1543
|
-
if (light instanceof SpotLight) {
|
|
1544
|
-
light.transform.updateWorld(undefined, true);
|
|
1545
|
-
const near = 0.01;
|
|
1546
|
-
const far = Math.max(near + 0.01, light.range);
|
|
1547
|
-
const projectionMatrix = perspectiveMat4(light.angle * 2, 1, near, far);
|
|
1548
|
-
return multiplyMat4(projectionMatrix, light.transform.inverseWorldMatrix);
|
|
1549
|
-
}
|
|
1550
|
-
return createDirectionalShadowMatrix(items, collectedLight?.direction ?? lightDirectionFromLight(light));
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
function createPointShadowFaceMatrices(light: PointLight): Float32Array {
|
|
1554
|
-
light.transform.updateWorld(undefined, true);
|
|
1555
|
-
const position: Vec3 = [
|
|
1556
|
-
light.transform.worldMatrix[12],
|
|
1557
|
-
light.transform.worldMatrix[13],
|
|
1558
|
-
light.transform.worldMatrix[14]
|
|
1559
|
-
];
|
|
1560
|
-
const projection = perspectiveMat4(Math.PI / 2, 1, 0.01, Math.max(0.02, light.range));
|
|
1561
|
-
const faces: readonly { readonly direction: Vec3; readonly up: Vec3 }[] = [
|
|
1562
|
-
{ direction: [1, 0, 0], up: [0, -1, 0] },
|
|
1563
|
-
{ direction: [-1, 0, 0], up: [0, -1, 0] },
|
|
1564
|
-
{ direction: [0, 1, 0], up: [0, 0, 1] },
|
|
1565
|
-
{ direction: [0, -1, 0], up: [0, 0, -1] },
|
|
1566
|
-
{ direction: [0, 0, 1], up: [0, -1, 0] },
|
|
1567
|
-
{ direction: [0, 0, -1], up: [0, -1, 0] }
|
|
1568
|
-
];
|
|
1569
|
-
const matrices = new Float32Array(6 * 16);
|
|
1570
|
-
for (const [index, face] of faces.entries()) {
|
|
1571
|
-
const view = lookAtMatrix(position, addVec3(position, face.direction), face.up);
|
|
1572
|
-
matrices.set(multiplyMat4(projection, view), index * 16);
|
|
1573
|
-
}
|
|
1574
|
-
return matrices;
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
function createPointShadowFaceRects(): Float32Array {
|
|
1578
|
-
const rects = new Float32Array(6 * 4);
|
|
1579
|
-
for (let face = 0; face < 6; face += 1) {
|
|
1580
|
-
const column = face % 3;
|
|
1581
|
-
const row = Math.floor(face / 3);
|
|
1582
|
-
rects.set([column / 3, row / 2, 1 / 3, 1 / 2], face * 4);
|
|
1583
|
-
}
|
|
1584
|
-
return rects;
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
function readShadowFacePixels(device: RenderDevice, target: RenderTarget): Uint8Array {
|
|
1588
|
-
device.setRenderTarget(target);
|
|
1589
|
-
if (target.depthTexture && device.readDepthPixels) {
|
|
1590
|
-
const depth = device.readDepthPixels(0, 0, target.width, target.height);
|
|
1591
|
-
const pixels = new Uint8Array(target.width * target.height * 4);
|
|
1592
|
-
for (let index = 0; index < depth.length; index += 1) {
|
|
1593
|
-
const byte = Math.max(0, Math.min(255, Math.round((depth[index] ?? 1) * 255)));
|
|
1594
|
-
const offset = index * 4;
|
|
1595
|
-
pixels[offset] = byte;
|
|
1596
|
-
pixels[offset + 1] = byte;
|
|
1597
|
-
pixels[offset + 2] = byte;
|
|
1598
|
-
pixels[offset + 3] = 255;
|
|
1599
|
-
}
|
|
1600
|
-
return pixels;
|
|
1601
|
-
}
|
|
1602
|
-
return device.readPixels(0, 0, target.width, target.height);
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
|
-
function blitPointShadowFace(
|
|
1606
|
-
atlasPixels: Uint8Array,
|
|
1607
|
-
atlasWidth: number,
|
|
1608
|
-
atlasHeight: number,
|
|
1609
|
-
facePixels: Uint8Array,
|
|
1610
|
-
faceSize: number,
|
|
1611
|
-
faceRects: Float32Array,
|
|
1612
|
-
face: number
|
|
1613
|
-
): void {
|
|
1614
|
-
const rectOffset = face * 4;
|
|
1615
|
-
const destX = Math.round((faceRects[rectOffset] ?? 0) * atlasWidth);
|
|
1616
|
-
const destY = Math.round((faceRects[rectOffset + 1] ?? 0) * atlasHeight);
|
|
1617
|
-
for (let row = 0; row < faceSize; row += 1) {
|
|
1618
|
-
const sourceOffset = row * faceSize * 4;
|
|
1619
|
-
const targetOffset = ((destY + row) * atlasWidth + destX) * 4;
|
|
1620
|
-
atlasPixels.set(facePixels.subarray(sourceOffset, sourceOffset + faceSize * 4), targetOffset);
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
|
-
function lookAtMatrix(eye: Vec3, target: Vec3, up: Vec3): Mat4 {
|
|
1625
|
-
const forward = normalizeVec3(subtractVec3(target, eye));
|
|
1626
|
-
const right = normalizeVec3(crossVec3(forward, up));
|
|
1627
|
-
const correctedUp = crossVec3(right, forward);
|
|
1628
|
-
return [
|
|
1629
|
-
right[0], correctedUp[0], -forward[0], 0,
|
|
1630
|
-
right[1], correctedUp[1], -forward[1], 0,
|
|
1631
|
-
right[2], correctedUp[2], -forward[2], 0,
|
|
1632
|
-
-dotVec3(right, eye), -dotVec3(correctedUp, eye), dotVec3(forward, eye), 1
|
|
1633
|
-
];
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
function createDirectionalShadowMatrix(items: readonly RenderItem[], lightDirection: readonly [number, number, number]): Mat4 {
|
|
1637
|
-
const bounds = collectRenderItemBounds(items);
|
|
1638
|
-
if (!bounds || bounds.isEmpty()) {
|
|
1639
|
-
return identityMat4();
|
|
1640
|
-
}
|
|
1641
|
-
const basis = directionalLightBasis(lightDirection);
|
|
1642
|
-
const points = boundsCorners(bounds).map((point) => projectPointToLightSpace(point, basis));
|
|
1643
|
-
const lightBounds = boundsFromVec3(points);
|
|
1644
|
-
const width = Math.max(0.01, lightBounds.max[0] - lightBounds.min[0]);
|
|
1645
|
-
const height = Math.max(0.01, lightBounds.max[1] - lightBounds.min[1]);
|
|
1646
|
-
const depth = Math.max(0.01, lightBounds.max[2] - lightBounds.min[2]);
|
|
1647
|
-
const padding = Math.max(width, height, depth) * 0.08 + 0.05;
|
|
1648
|
-
const viewMatrix = lightViewMatrix(basis);
|
|
1649
|
-
const projectionMatrix = orthographicMat4(
|
|
1650
|
-
lightBounds.min[0] - padding,
|
|
1651
|
-
lightBounds.max[0] + padding,
|
|
1652
|
-
lightBounds.min[1] - padding,
|
|
1653
|
-
lightBounds.max[1] + padding,
|
|
1654
|
-
lightBounds.min[2] - padding,
|
|
1655
|
-
lightBounds.max[2] + padding
|
|
1656
|
-
);
|
|
1657
|
-
return multiplyMat4(projectionMatrix, viewMatrix);
|
|
1658
|
-
}
|
|
1659
|
-
|
|
1660
|
-
function directionalLightBasis(lightDirection: readonly [number, number, number]): {
|
|
1661
|
-
readonly right: Vec3;
|
|
1662
|
-
readonly up: Vec3;
|
|
1663
|
-
readonly forward: Vec3;
|
|
1664
|
-
} {
|
|
1665
|
-
const forward = normalizeVec3(scaleVec3(normalizeVec3(lightDirection), -1));
|
|
1666
|
-
const fallbackUp: Vec3 = Math.abs(forward[1]) > 0.94 ? [0, 0, 1] : [0, 1, 0];
|
|
1667
|
-
const right = normalizeVec3(crossVec3(fallbackUp, forward));
|
|
1668
|
-
const up = normalizeVec3(crossVec3(forward, right));
|
|
1669
|
-
return { right, up, forward };
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1672
|
-
function lightViewMatrix(basis: { readonly right: Vec3; readonly up: Vec3; readonly forward: Vec3 }): Mat4 {
|
|
1673
|
-
return [
|
|
1674
|
-
basis.right[0], basis.up[0], basis.forward[0], 0,
|
|
1675
|
-
basis.right[1], basis.up[1], basis.forward[1], 0,
|
|
1676
|
-
basis.right[2], basis.up[2], basis.forward[2], 0,
|
|
1677
|
-
0, 0, 0, 1
|
|
1678
|
-
];
|
|
1679
|
-
}
|
|
1680
|
-
|
|
1681
|
-
function projectPointToLightSpace(point: Vec3, basis: { readonly right: Vec3; readonly up: Vec3; readonly forward: Vec3 }): Vec3 {
|
|
1682
|
-
return [dotVec3(point, basis.right), dotVec3(point, basis.up), dotVec3(point, basis.forward)];
|
|
1683
|
-
}
|
|
1684
|
-
|
|
1685
|
-
function boundsCorners(bounds: SceneBounds3): readonly Vec3[] {
|
|
1686
|
-
return [
|
|
1687
|
-
[bounds.min[0], bounds.min[1], bounds.min[2]],
|
|
1688
|
-
[bounds.max[0], bounds.min[1], bounds.min[2]],
|
|
1689
|
-
[bounds.min[0], bounds.max[1], bounds.min[2]],
|
|
1690
|
-
[bounds.min[0], bounds.min[1], bounds.max[2]],
|
|
1691
|
-
[bounds.max[0], bounds.max[1], bounds.min[2]],
|
|
1692
|
-
[bounds.max[0], bounds.min[1], bounds.max[2]],
|
|
1693
|
-
[bounds.min[0], bounds.max[1], bounds.max[2]],
|
|
1694
|
-
[bounds.max[0], bounds.max[1], bounds.max[2]]
|
|
1695
|
-
];
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
|
-
function boundsFromVec3(points: readonly Vec3[]): { readonly min: Vec3; readonly max: Vec3 } {
|
|
1699
|
-
const min: Vec3 = [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY];
|
|
1700
|
-
const max: Vec3 = [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY];
|
|
1701
|
-
for (const point of points) {
|
|
1702
|
-
for (let axis = 0; axis < 3; axis += 1) {
|
|
1703
|
-
min[axis] = Math.min(min[axis]!, point[axis]!);
|
|
1704
|
-
max[axis] = Math.max(max[axis]!, point[axis]!);
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
|
-
return { min, max };
|
|
1708
|
-
}
|
|
1709
|
-
|
|
1710
|
-
function normalizeVec3(value: readonly [number, number, number]): Vec3 {
|
|
1711
|
-
const length = Math.hypot(value[0], value[1], value[2]);
|
|
1712
|
-
if (!Number.isFinite(length) || length <= 1e-8) return [0, -1 / Math.SQRT2, -1 / Math.SQRT2];
|
|
1713
|
-
return [value[0] / length, value[1] / length, value[2] / length];
|
|
1714
|
-
}
|
|
1715
|
-
|
|
1716
|
-
function scaleVec3(value: Vec3, amount: number): Vec3 {
|
|
1717
|
-
return [value[0] * amount, value[1] * amount, value[2] * amount];
|
|
1718
|
-
}
|
|
1719
|
-
|
|
1720
|
-
function addVec3(left: Vec3, right: Vec3): Vec3 {
|
|
1721
|
-
return [left[0] + right[0], left[1] + right[1], left[2] + right[2]];
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
function subtractVec3(left: Vec3, right: Vec3): Vec3 {
|
|
1725
|
-
return [left[0] - right[0], left[1] - right[1], left[2] - right[2]];
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
function crossVec3(left: Vec3, right: Vec3): Vec3 {
|
|
1729
|
-
return [
|
|
1730
|
-
left[1] * right[2] - left[2] * right[1],
|
|
1731
|
-
left[2] * right[0] - left[0] * right[2],
|
|
1732
|
-
left[0] * right[1] - left[1] * right[0]
|
|
1733
|
-
];
|
|
1734
|
-
}
|
|
1735
|
-
|
|
1736
|
-
function dotVec3(left: Vec3, right: Vec3): number {
|
|
1737
|
-
return left[0] * right[0] + left[1] * right[1] + left[2] * right[2];
|
|
1738
|
-
}
|
|
1739
|
-
|
|
1740
|
-
function collectPostprocess(source: RenderSource | Iterable<RenderItem> | Scene): RendererPostProcessOptions | undefined {
|
|
1741
|
-
if (source instanceof Scene || isIterable(source)) return undefined;
|
|
1742
|
-
if (source.postprocess === true) return {};
|
|
1743
|
-
if (!source.postprocess) return undefined;
|
|
1744
|
-
return source.postprocess;
|
|
1745
|
-
}
|
|
1746
|
-
|
|
1747
|
-
function normalizeRendererInput(
|
|
1748
|
-
sourceOrInput: RendererInput | RenderSource | Iterable<RenderItem> | Scene,
|
|
1749
|
-
camera?: CameraLike
|
|
1750
|
-
): RendererInput {
|
|
1751
|
-
if (isRendererInput(sourceOrInput)) {
|
|
1752
|
-
return { source: sourceOrInput.source, camera: camera ?? sourceOrInput.camera };
|
|
1753
|
-
}
|
|
1754
|
-
return { source: sourceOrInput, ...(camera ? { camera } : {}) };
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
function isRendererInput(value: RendererInput | RenderSource | Iterable<RenderItem> | Scene): value is RendererInput {
|
|
1758
|
-
return !(value instanceof Scene) && !isIterable(value) && isRecord(value) && "source" in value;
|
|
1759
|
-
}
|
|
1760
|
-
|
|
1761
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
1762
|
-
return typeof value === "object" && value !== null;
|
|
1763
|
-
}
|
|
1764
|
-
|
|
1765
|
-
function collectCameraPolicy(source: RenderSource | Iterable<RenderItem> | Scene): RendererCameraPolicy {
|
|
1766
|
-
if (source instanceof Scene || isIterable(source)) return "identity";
|
|
1767
|
-
if (source.cameraPolicy) return source.cameraPolicy;
|
|
1768
|
-
if (source.scene && !source.renderItems && !source.collectRenderItems) return "auto-frame";
|
|
1769
|
-
if ((source.renderItems || source.collectRenderItems) && !source.renderTarget) return "auto-frame";
|
|
1770
|
-
return "identity";
|
|
1771
|
-
}
|
|
1772
|
-
|
|
1773
|
-
function postprocessRequiresDepthTexture(postprocess: RendererPostProcessOptions): boolean {
|
|
1774
|
-
return Boolean(
|
|
1775
|
-
(postprocess.depthOfField && !postprocess.depthOfField.depth) ||
|
|
1776
|
-
(postprocess.contactShadow && !postprocess.contactShadow.depth) ||
|
|
1777
|
-
(postprocess.ssao && !postprocess.ssao.depth) ||
|
|
1778
|
-
(postprocess.ssr && !postprocess.ssr.depth)
|
|
1779
|
-
);
|
|
1780
|
-
}
|
|
1781
|
-
|
|
1782
|
-
function canFuseLdrPostprocess(source: RenderTarget, passes: readonly RendererPostProcessPassPlan[]): boolean {
|
|
1783
|
-
const sourceIsHdr = isHdrRenderTarget(source);
|
|
1784
|
-
return passes.length > 1
|
|
1785
|
-
&& (!sourceIsHdr || passes[0]?.name === "tone-mapping")
|
|
1786
|
-
&& passes.every((pass) => pass.name === "tone-mapping" || pass.name === "color-grade" || pass.name === "fxaa")
|
|
1787
|
-
&& passes.every((pass, index) => {
|
|
1788
|
-
const previousRank = index === 0 ? -1 : ldrFusionPassRank(passes[index - 1]!.name);
|
|
1789
|
-
return ldrFusionPassRank(pass.name) >= previousRank;
|
|
1790
|
-
});
|
|
1791
|
-
}
|
|
1792
|
-
|
|
1793
|
-
function ldrFusionPassRank(name: RendererPostProcessPassName): number {
|
|
1794
|
-
if (name === "tone-mapping") return 0;
|
|
1795
|
-
if (name === "color-grade") return 1;
|
|
1796
|
-
if (name === "fxaa") return 2;
|
|
1797
|
-
return Number.POSITIVE_INFINITY;
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1800
|
-
function isDepthPostprocessPass(name: RendererPostProcessPassName): name is "depth-of-field" | "contact-shadow" | "ssao" | "ssr" {
|
|
1801
|
-
return name === "depth-of-field" || name === "contact-shadow" || name === "ssao" || name === "ssr";
|
|
1802
|
-
}
|
|
1803
|
-
|
|
1804
|
-
function postprocessPassHasDepth(options: RendererPostProcessPassPlan["options"]): boolean {
|
|
1805
|
-
return typeof options === "object" && options !== null && "depth" in options && Boolean((options as { readonly depth?: unknown }).depth);
|
|
1806
|
-
}
|
|
1807
|
-
|
|
1808
|
-
function withRendererDepth<T extends DepthOfFieldOptions | ContactShadowPostProcessOptions | SSAOOptions | SSROptions>(options: T, depth: DepthTextureBinding | undefined): T {
|
|
1809
|
-
return depth && !options.depth ? { ...options, depth } : options;
|
|
1810
|
-
}
|
|
1811
|
-
|
|
1812
|
-
function defaultPostprocessTargetFormat(
|
|
1813
|
-
device: RenderDevice,
|
|
1814
|
-
postprocess: RendererPostProcessOptions
|
|
1815
|
-
): Extract<RenderTargetDescriptor["format"], "rgba8" | "rgba16f" | "rgba32f"> {
|
|
1816
|
-
if (postprocess.toneMapping === false) return "rgba8";
|
|
1817
|
-
return device.info.capabilities?.includes("hdr-render-targets") ? "rgba16f" : "rgba8";
|
|
1818
|
-
}
|
|
1819
|
-
|
|
1820
|
-
function isHdrRenderTarget(target: RenderTarget): boolean {
|
|
1821
|
-
return target.colorTexture.format === "rgba16f" || target.colorTexture.format === "rgba32f";
|
|
1822
|
-
}
|
|
1823
|
-
|
|
1824
|
-
function collectSourceCameraPosition(source: RenderSource | Iterable<RenderItem> | Scene): readonly [number, number, number] | undefined {
|
|
1825
|
-
if (source instanceof Scene || isIterable(source)) return undefined;
|
|
1826
|
-
const position = source.cameraPosition;
|
|
1827
|
-
if (position === undefined) return undefined;
|
|
1828
|
-
if (position.length !== 3 || position.some((value) => !Number.isFinite(value))) {
|
|
1829
|
-
throw new RenderDeviceError("RenderSource cameraPosition must contain three finite numbers", "CAMERA_POSITION_INVALID", {
|
|
1830
|
-
cameraPosition: [...position]
|
|
1831
|
-
});
|
|
1832
|
-
}
|
|
1833
|
-
return position;
|
|
1834
|
-
}
|
|
1835
|
-
|
|
1836
|
-
function collectRenderItems(
|
|
1837
|
-
source: RenderSource | Iterable<RenderItem> | Scene,
|
|
1838
|
-
cameraViewProjection?: Mat4,
|
|
1839
|
-
camera?: Camera
|
|
1840
|
-
): readonly RenderItem[] {
|
|
1841
|
-
return collectRenderItemsWithDiagnostics(source, cameraViewProjection, camera).items;
|
|
1842
|
-
}
|
|
1843
|
-
|
|
1844
|
-
function collectRenderItemsWithDiagnostics(
|
|
1845
|
-
source: RenderSource | Iterable<RenderItem> | Scene,
|
|
1846
|
-
cameraViewProjection?: Mat4,
|
|
1847
|
-
camera?: Camera
|
|
1848
|
-
): { readonly items: readonly RenderItem[]; readonly diagnostics: RenderCollectionDiagnostics } {
|
|
1849
|
-
const diagnostics = createRenderCollectionDiagnostics();
|
|
1850
|
-
if (isIterable(source)) {
|
|
1851
|
-
const items = applyViewProjection([...source], cameraViewProjection);
|
|
1852
|
-
diagnostics.submittedObjects += items.length;
|
|
1853
|
-
diagnostics.visibleObjects += items.length;
|
|
1854
|
-
return { items, diagnostics };
|
|
1855
|
-
}
|
|
1856
|
-
if (source instanceof Scene) {
|
|
1857
|
-
const items = collectSceneRenderItems(source, {}, cameraViewProjection, camera, diagnostics);
|
|
1858
|
-
return { items, diagnostics };
|
|
1859
|
-
}
|
|
1860
|
-
const items: RenderItem[] = [];
|
|
1861
|
-
if (source.scene) {
|
|
1862
|
-
items.push(...collectSceneRenderItems(source.scene, source, cameraViewProjection, source.frustumCulling === false ? undefined : camera, diagnostics));
|
|
1863
|
-
}
|
|
1864
|
-
const explicitItems: RenderItem[] = [];
|
|
1865
|
-
if (source.collectRenderItems) {
|
|
1866
|
-
explicitItems.push(...source.collectRenderItems());
|
|
1867
|
-
}
|
|
1868
|
-
if (source.renderItems) {
|
|
1869
|
-
explicitItems.push(...source.renderItems);
|
|
1870
|
-
}
|
|
1871
|
-
if (explicitItems.length > 0) {
|
|
1872
|
-
const optimizedItems = applyRendererOwnedStaticBatching(source, explicitItems);
|
|
1873
|
-
const projectedItems = applyViewProjection(optimizedItems, cameraViewProjection);
|
|
1874
|
-
diagnostics.submittedObjects += explicitItems.length;
|
|
1875
|
-
diagnostics.visibleObjects += explicitItems.length;
|
|
1876
|
-
items.push(...projectedItems);
|
|
1877
|
-
}
|
|
1878
|
-
return { items, diagnostics };
|
|
1879
|
-
}
|
|
1880
|
-
|
|
1881
|
-
const staticBatchGeometryIds = new WeakMap<Geometry, number>();
|
|
1882
|
-
const staticBatchMaterialIds = new WeakMap<object, number>();
|
|
1883
|
-
let nextStaticBatchResourceId = 1;
|
|
1884
|
-
|
|
1885
|
-
function applyRendererOwnedStaticBatching(source: RenderSource, items: readonly RenderItem[]): readonly RenderItem[] {
|
|
1886
|
-
if (!source.staticBatching) return items;
|
|
1887
|
-
const batchable: StaticBatchInput[] = [];
|
|
1888
|
-
const passthrough: RenderItem[] = [];
|
|
1889
|
-
for (const item of items) {
|
|
1890
|
-
if (isStaticBatchCandidate(item)) {
|
|
1891
|
-
batchable.push({
|
|
1892
|
-
geometry: item.geometry,
|
|
1893
|
-
material: item.material,
|
|
1894
|
-
modelMatrix: item.modelMatrix ?? identityMat4(),
|
|
1895
|
-
batchKey: staticBatchKey(item),
|
|
1896
|
-
label: item.label
|
|
1897
|
-
});
|
|
1898
|
-
} else {
|
|
1899
|
-
passthrough.push(item);
|
|
1900
|
-
}
|
|
1901
|
-
}
|
|
1902
|
-
if (batchable.length === 0) return items;
|
|
1903
|
-
const options = source.staticBatching === true ? {} : source.staticBatching;
|
|
1904
|
-
const batched = batchStaticRenderItems(batchable, {
|
|
1905
|
-
labelPrefix: "renderer-static-batch",
|
|
1906
|
-
...options
|
|
1907
|
-
});
|
|
1908
|
-
return [...passthrough, ...batched.renderItems];
|
|
1909
|
-
}
|
|
1910
|
-
|
|
1911
|
-
function isStaticBatchCandidate(item: RenderItem): item is RenderItem & { readonly material: RenderMaterial } {
|
|
1912
|
-
return item.material !== undefined &&
|
|
1913
|
-
item.drawRange === undefined &&
|
|
1914
|
-
item.skinning === undefined &&
|
|
1915
|
-
item.morphTargets === undefined &&
|
|
1916
|
-
item.morphWeights === undefined &&
|
|
1917
|
-
item.instanceTransforms === undefined &&
|
|
1918
|
-
item.instanceColors === undefined &&
|
|
1919
|
-
item.instanceAttributes === undefined;
|
|
1920
|
-
}
|
|
1921
|
-
|
|
1922
|
-
function staticBatchKey(item: RenderItem & { readonly material: RenderMaterial }): string {
|
|
1923
|
-
return `${resourceId(staticBatchGeometryIds, item.geometry)}:${resourceId(staticBatchMaterialIds, item.material)}`;
|
|
1924
|
-
}
|
|
1925
|
-
|
|
1926
|
-
function resourceId<T extends object>(ids: WeakMap<T, number>, resource: T): number {
|
|
1927
|
-
const existing = ids.get(resource);
|
|
1928
|
-
if (existing !== undefined) return existing;
|
|
1929
|
-
const next = nextStaticBatchResourceId++;
|
|
1930
|
-
ids.set(resource, next);
|
|
1931
|
-
return next;
|
|
1932
|
-
}
|
|
1933
|
-
|
|
1934
|
-
function collectSceneRenderItems(
|
|
1935
|
-
scene: Scene,
|
|
1936
|
-
source: Pick<RenderSource, "geometryLibrary" | "materialLibrary" | "morphTargetLibrary">,
|
|
1937
|
-
cameraViewProjection?: Mat4,
|
|
1938
|
-
camera?: Camera,
|
|
1939
|
-
diagnostics?: RenderCollectionDiagnostics
|
|
1940
|
-
): readonly RenderItem[] {
|
|
1941
|
-
scene.updateWorldTransforms();
|
|
1942
|
-
const renderables = scene.collectRenderables();
|
|
1943
|
-
if (renderables.length === 0) {
|
|
1944
|
-
return [];
|
|
1945
|
-
}
|
|
1946
|
-
if (!source.geometryLibrary || !source.materialLibrary) {
|
|
1947
|
-
throw new RenderDeviceError("Scene rendering requires geometryLibrary and materialLibrary resource lookups", "SCENE_RENDER_RESOURCES_MISSING", {
|
|
1948
|
-
renderables: renderables.length
|
|
1949
|
-
});
|
|
1950
|
-
}
|
|
1951
|
-
const items: RenderItem[] = [];
|
|
1952
|
-
for (const { node, renderable } of renderables) {
|
|
1953
|
-
const geometry = lookupRenderResource(source.geometryLibrary!, renderable.geometry);
|
|
1954
|
-
const material = lookupRenderResource(source.materialLibrary!, renderable.material);
|
|
1955
|
-
if (!geometry) {
|
|
1956
|
-
throw new RenderDeviceError("Scene renderable references missing geometry", "SCENE_GEOMETRY_MISSING", {
|
|
1957
|
-
node: node.name,
|
|
1958
|
-
geometry: renderable.geometry
|
|
1959
|
-
});
|
|
1960
|
-
}
|
|
1961
|
-
if (!material) {
|
|
1962
|
-
throw new RenderDeviceError("Scene renderable references missing material", "SCENE_MATERIAL_MISSING", {
|
|
1963
|
-
node: node.name,
|
|
1964
|
-
material: renderable.material
|
|
1965
|
-
});
|
|
1966
|
-
}
|
|
1967
|
-
const morphTargets = source.morphTargetLibrary ? lookupRenderResource(source.morphTargetLibrary, renderable.geometry) : undefined;
|
|
1968
|
-
if (renderable.morphWeights.length > 0 && !morphTargets) {
|
|
1969
|
-
throw new RenderDeviceError("Scene renderable has morph weights but no morph target resource entry", "SCENE_MORPH_TARGETS_MISSING", {
|
|
1970
|
-
node: node.name,
|
|
1971
|
-
geometry: renderable.geometry,
|
|
1972
|
-
morphWeights: renderable.morphWeights.length
|
|
1973
|
-
});
|
|
1974
|
-
}
|
|
1975
|
-
const modelMatrix = node.transform.worldMatrix;
|
|
1976
|
-
const bounds = renderableWorldBounds(geometry, modelMatrix, renderable.instanceTransforms, morphTargets, renderable.morphWeights, renderable.skinning);
|
|
1977
|
-
if (diagnostics) {
|
|
1978
|
-
diagnostics.submittedObjects += 1;
|
|
1979
|
-
if (camera) diagnostics.frustumTestedObjects += 1;
|
|
1980
|
-
}
|
|
1981
|
-
if (camera && !camera.frustum.intersectsBox(bounds.toMathBox())) {
|
|
1982
|
-
if (diagnostics) diagnostics.culledObjects += 1;
|
|
1983
|
-
continue;
|
|
1984
|
-
}
|
|
1985
|
-
if (diagnostics) diagnostics.visibleObjects += 1;
|
|
1986
|
-
items.push({
|
|
1987
|
-
geometry,
|
|
1988
|
-
material,
|
|
1989
|
-
label: node.name,
|
|
1990
|
-
modelMatrix,
|
|
1991
|
-
normalMatrix: normalMatrixFromModel(modelMatrix),
|
|
1992
|
-
modelViewProjectionMatrix: multiplyMat4(cameraViewProjection ?? identityMat4(), modelMatrix),
|
|
1993
|
-
...(renderable.skinning ? { skinning: renderable.skinning } : {}),
|
|
1994
|
-
...(renderable.instanceTransforms ? { instanceTransforms: renderable.instanceTransforms } : {}),
|
|
1995
|
-
...(renderable.instanceColors ? { instanceColors: renderable.instanceColors } : {}),
|
|
1996
|
-
...(morphTargets && renderable.morphWeights.length > 0 ? { morphTargets, morphWeights: renderable.morphWeights } : {})
|
|
1997
|
-
});
|
|
1998
|
-
}
|
|
1999
|
-
return items;
|
|
2000
|
-
}
|
|
2001
|
-
|
|
2002
|
-
function createRenderCollectionDiagnostics(): RenderCollectionDiagnostics {
|
|
2003
|
-
return {
|
|
2004
|
-
submittedObjects: 0,
|
|
2005
|
-
visibleObjects: 0,
|
|
2006
|
-
culledObjects: 0,
|
|
2007
|
-
frustumTestedObjects: 0
|
|
2008
|
-
};
|
|
2009
|
-
}
|
|
2010
|
-
|
|
2011
|
-
function createPostprocessDiagnostics(
|
|
2012
|
-
postprocess: RendererPostProcessOptions | undefined,
|
|
2013
|
-
ownedTargets: readonly RenderTarget[],
|
|
2014
|
-
width: number,
|
|
2015
|
-
height: number,
|
|
2016
|
-
context: {
|
|
2017
|
-
readonly targetFormat?: RendererPostprocessTargetFormat;
|
|
2018
|
-
readonly nativeLdrPostprocess?: boolean;
|
|
2019
|
-
readonly rendererDepthAvailable?: boolean;
|
|
2020
|
-
} = {}
|
|
2021
|
-
): RendererPostprocessDiagnostics | undefined {
|
|
2022
|
-
if (!postprocess) return undefined;
|
|
2023
|
-
const passes = createRendererPostprocessPasses(postprocess);
|
|
2024
|
-
const targetFormat = context.targetFormat ?? postprocess.targetFormat ?? "rgba8";
|
|
2025
|
-
return {
|
|
2026
|
-
postprocessPasses: passes.length,
|
|
2027
|
-
postprocessPassNames: passes.map((pass) => pass.name),
|
|
2028
|
-
postprocessTargetFormat: targetFormat,
|
|
2029
|
-
postprocessRenderTargets: ownedTargets.length,
|
|
2030
|
-
postprocessTextures: ownedTargets.reduce((total, target) => total + 1 + (target.depthTexture ? 1 : 0), 0),
|
|
2031
|
-
postprocessTargetWidth: width,
|
|
2032
|
-
postprocessTargetHeight: height,
|
|
2033
|
-
postprocessPlan: createRendererPostprocessPlanDiagnostics(postprocess, {
|
|
2034
|
-
sourceTargetFormat: targetFormat,
|
|
2035
|
-
targetFormat,
|
|
2036
|
-
nativeLdrPostprocess: context.nativeLdrPostprocess,
|
|
2037
|
-
rendererDepthAvailable: context.rendererDepthAvailable
|
|
2038
|
-
})
|
|
2039
|
-
};
|
|
2040
|
-
}
|
|
2041
|
-
|
|
2042
|
-
const CAPTURE_NON_BLANK_LUMINANCE_THRESHOLD = 1 / 255;
|
|
2043
|
-
const CAPTURE_UNDEREXPOSED_LUMINANCE_THRESHOLD = 5 / 255;
|
|
2044
|
-
const CAPTURE_OVEREXPOSED_LUMINANCE_THRESHOLD = 250 / 255;
|
|
2045
|
-
const CAPTURE_CLIPPED_CHANNEL_THRESHOLD = 254;
|
|
2046
|
-
|
|
2047
|
-
function createRendererFrameCaptureMetadata(
|
|
2048
|
-
device: RenderDevice,
|
|
2049
|
-
width: number,
|
|
2050
|
-
height: number,
|
|
2051
|
-
pixels: Uint8Array,
|
|
2052
|
-
diagnostics: RenderDeviceDiagnostics
|
|
2053
|
-
): RendererFrameCaptureMetadata {
|
|
2054
|
-
const pixelDigest = createRendererFramePixelDigest(pixels);
|
|
2055
|
-
return {
|
|
2056
|
-
capturedAt: new Date().toISOString(),
|
|
2057
|
-
renderSize: {
|
|
2058
|
-
width,
|
|
2059
|
-
height,
|
|
2060
|
-
pixelCount: width * height,
|
|
2061
|
-
byteLength: pixels.byteLength,
|
|
2062
|
-
aspectRatio: roundCaptureMetric(height > 0 ? width / height : 0)
|
|
2063
|
-
},
|
|
2064
|
-
pixelHash: pixelDigest.value,
|
|
2065
|
-
pixelDigest,
|
|
2066
|
-
pixelStats: createRendererFramePixelStats(pixels, width, height),
|
|
2067
|
-
diagnosticsSummary: createRendererFrameDiagnosticsSummary(device, diagnostics)
|
|
2068
|
-
};
|
|
2069
|
-
}
|
|
2070
|
-
|
|
2071
|
-
function createRendererFramePixelDigest(pixels: Uint8Array): RendererFrameCapturePixelDigest {
|
|
2072
|
-
return {
|
|
2073
|
-
algorithm: "sha256",
|
|
2074
|
-
encoding: "hex",
|
|
2075
|
-
source: "rgba8",
|
|
2076
|
-
value: sha256Hex(pixels),
|
|
2077
|
-
byteLength: pixels.byteLength
|
|
2078
|
-
};
|
|
2079
|
-
}
|
|
2080
|
-
|
|
2081
|
-
function createRendererFramePixelStats(pixels: Uint8Array, width: number, height: number): RendererFrameCapturePixelStats {
|
|
2082
|
-
const totalPixels = Math.max(0, width * height);
|
|
2083
|
-
const divisor = totalPixels > 0 ? totalPixels : 1;
|
|
2084
|
-
let opaquePixels = 0;
|
|
2085
|
-
let transparentPixels = 0;
|
|
2086
|
-
let nonBlankPixels = 0;
|
|
2087
|
-
let underexposedPixels = 0;
|
|
2088
|
-
let overexposedPixels = 0;
|
|
2089
|
-
let clippedChannelPixels = 0;
|
|
2090
|
-
let luminanceSum = 0;
|
|
2091
|
-
let alphaSum = 0;
|
|
2092
|
-
let redSum = 0;
|
|
2093
|
-
let greenSum = 0;
|
|
2094
|
-
let blueSum = 0;
|
|
2095
|
-
let minLuminance = totalPixels > 0 ? Number.POSITIVE_INFINITY : 0;
|
|
2096
|
-
let maxLuminance = 0;
|
|
2097
|
-
|
|
2098
|
-
for (let pixel = 0; pixel < totalPixels; pixel += 1) {
|
|
2099
|
-
const offset = pixel * 4;
|
|
2100
|
-
const red = pixels[offset] ?? 0;
|
|
2101
|
-
const green = pixels[offset + 1] ?? 0;
|
|
2102
|
-
const blue = pixels[offset + 2] ?? 0;
|
|
2103
|
-
const alpha = pixels[offset + 3] ?? 0;
|
|
2104
|
-
const luminance = ((0.2126 * red) + (0.7152 * green) + (0.0722 * blue)) / 255;
|
|
2105
|
-
|
|
2106
|
-
redSum += red;
|
|
2107
|
-
greenSum += green;
|
|
2108
|
-
blueSum += blue;
|
|
2109
|
-
alphaSum += alpha;
|
|
2110
|
-
luminanceSum += luminance;
|
|
2111
|
-
minLuminance = Math.min(minLuminance, luminance);
|
|
2112
|
-
maxLuminance = Math.max(maxLuminance, luminance);
|
|
2113
|
-
|
|
2114
|
-
if (alpha === 255) opaquePixels += 1;
|
|
2115
|
-
if (alpha === 0) transparentPixels += 1;
|
|
2116
|
-
if (alpha > 0 && luminance > CAPTURE_NON_BLANK_LUMINANCE_THRESHOLD) nonBlankPixels += 1;
|
|
2117
|
-
if (alpha > 0 && luminance <= CAPTURE_UNDEREXPOSED_LUMINANCE_THRESHOLD) underexposedPixels += 1;
|
|
2118
|
-
if (alpha > 0 && luminance >= CAPTURE_OVEREXPOSED_LUMINANCE_THRESHOLD) overexposedPixels += 1;
|
|
2119
|
-
if (
|
|
2120
|
-
alpha > 0 &&
|
|
2121
|
-
(red >= CAPTURE_CLIPPED_CHANNEL_THRESHOLD ||
|
|
2122
|
-
green >= CAPTURE_CLIPPED_CHANNEL_THRESHOLD ||
|
|
2123
|
-
blue >= CAPTURE_CLIPPED_CHANNEL_THRESHOLD)
|
|
2124
|
-
) {
|
|
2125
|
-
clippedChannelPixels += 1;
|
|
2126
|
-
}
|
|
2127
|
-
}
|
|
2128
|
-
|
|
2129
|
-
return {
|
|
2130
|
-
totalPixels,
|
|
2131
|
-
opaquePixels,
|
|
2132
|
-
opaqueRatio: captureRatio(opaquePixels, totalPixels),
|
|
2133
|
-
transparentPixels,
|
|
2134
|
-
transparentRatio: captureRatio(transparentPixels, totalPixels),
|
|
2135
|
-
nonBlankPixels,
|
|
2136
|
-
nonBlankRatio: captureRatio(nonBlankPixels, totalPixels),
|
|
2137
|
-
underexposedPixels,
|
|
2138
|
-
underexposedRatio: captureRatio(underexposedPixels, totalPixels),
|
|
2139
|
-
overexposedPixels,
|
|
2140
|
-
overexposedRatio: captureRatio(overexposedPixels, totalPixels),
|
|
2141
|
-
clippedChannelPixels,
|
|
2142
|
-
clippedChannelRatio: captureRatio(clippedChannelPixels, totalPixels),
|
|
2143
|
-
averageLuminance: roundCaptureMetric(luminanceSum / divisor),
|
|
2144
|
-
minLuminance: roundCaptureMetric(minLuminance),
|
|
2145
|
-
maxLuminance: roundCaptureMetric(maxLuminance),
|
|
2146
|
-
averageAlpha: roundCaptureMetric((alphaSum / divisor) / 255),
|
|
2147
|
-
averageRgb: [
|
|
2148
|
-
roundCaptureMetric((redSum / divisor) / 255),
|
|
2149
|
-
roundCaptureMetric((greenSum / divisor) / 255),
|
|
2150
|
-
roundCaptureMetric((blueSum / divisor) / 255)
|
|
2151
|
-
],
|
|
2152
|
-
thresholds: {
|
|
2153
|
-
nonBlankLuminance: roundCaptureMetric(CAPTURE_NON_BLANK_LUMINANCE_THRESHOLD),
|
|
2154
|
-
underexposedLuminance: roundCaptureMetric(CAPTURE_UNDEREXPOSED_LUMINANCE_THRESHOLD),
|
|
2155
|
-
overexposedLuminance: roundCaptureMetric(CAPTURE_OVEREXPOSED_LUMINANCE_THRESHOLD),
|
|
2156
|
-
clippedChannel: roundCaptureMetric(CAPTURE_CLIPPED_CHANNEL_THRESHOLD / 255)
|
|
2157
|
-
}
|
|
2158
|
-
};
|
|
2159
|
-
}
|
|
2160
|
-
|
|
2161
|
-
function createRendererFrameDiagnosticsSummary(device: RenderDevice, diagnostics: RenderDeviceDiagnostics): RendererFrameCaptureDiagnosticsSummary {
|
|
2162
|
-
const resources = {
|
|
2163
|
-
buffers: diagnostics.buffers,
|
|
2164
|
-
shaders: diagnostics.shaders,
|
|
2165
|
-
...(diagnostics.renderTargets !== undefined ? { renderTargets: diagnostics.renderTargets } : {}),
|
|
2166
|
-
...(diagnostics.textures !== undefined ? { textures: diagnostics.textures } : {})
|
|
2167
|
-
};
|
|
2168
|
-
const memoryBytes = {
|
|
2169
|
-
...(diagnostics.bufferBytes !== undefined ? { buffers: diagnostics.bufferBytes } : {}),
|
|
2170
|
-
...(diagnostics.textureBytes !== undefined ? { textures: diagnostics.textureBytes } : {}),
|
|
2171
|
-
...(diagnostics.approximateGpuMemoryBytes !== undefined ? { approximateGpu: diagnostics.approximateGpuMemoryBytes } : {})
|
|
2172
|
-
};
|
|
2173
|
-
const scene = {
|
|
2174
|
-
...(diagnostics.submittedObjects !== undefined ? { submittedObjects: diagnostics.submittedObjects } : {}),
|
|
2175
|
-
...(diagnostics.visibleObjects !== undefined ? { visibleObjects: diagnostics.visibleObjects } : {}),
|
|
2176
|
-
...(diagnostics.culledObjects !== undefined ? { culledObjects: diagnostics.culledObjects } : {}),
|
|
2177
|
-
...(diagnostics.frustumTestedObjects !== undefined ? { frustumTestedObjects: diagnostics.frustumTestedObjects } : {})
|
|
2178
|
-
};
|
|
2179
|
-
const postprocess = {
|
|
2180
|
-
...(diagnostics.postprocessPasses !== undefined ? { passes: diagnostics.postprocessPasses } : {}),
|
|
2181
|
-
...(diagnostics.postprocessPassNames !== undefined ? { passNames: diagnostics.postprocessPassNames } : {}),
|
|
2182
|
-
...(diagnostics.postprocessTargetFormat !== undefined ? { targetFormat: diagnostics.postprocessTargetFormat } : {}),
|
|
2183
|
-
...(diagnostics.postprocessRenderTargets !== undefined ? { renderTargets: diagnostics.postprocessRenderTargets } : {}),
|
|
2184
|
-
...(diagnostics.postprocessTextures !== undefined ? { textures: diagnostics.postprocessTextures } : {}),
|
|
2185
|
-
...(diagnostics.postprocessTargetWidth !== undefined ? { targetWidth: diagnostics.postprocessTargetWidth } : {}),
|
|
2186
|
-
...(diagnostics.postprocessTargetHeight !== undefined ? { targetHeight: diagnostics.postprocessTargetHeight } : {})
|
|
2187
|
-
};
|
|
2188
|
-
|
|
2189
|
-
return {
|
|
2190
|
-
backend: device.kind,
|
|
2191
|
-
renderer: device.info.renderer,
|
|
2192
|
-
vendor: device.info.vendor,
|
|
2193
|
-
capabilities: device.info.capabilities ?? [],
|
|
2194
|
-
limitations: device.info.limitations ?? [],
|
|
2195
|
-
drawCalls: diagnostics.drawCalls,
|
|
2196
|
-
resources,
|
|
2197
|
-
...(Object.keys(memoryBytes).length > 0 ? { memoryBytes } : {}),
|
|
2198
|
-
...(Object.keys(scene).length > 0 ? { scene } : {}),
|
|
2199
|
-
...(Object.keys(postprocess).length > 0 ? { postprocess } : {}),
|
|
2200
|
-
contextLost: diagnostics.contextLost,
|
|
2201
|
-
lastError: diagnostics.lastError
|
|
2202
|
-
};
|
|
2203
|
-
}
|
|
2204
|
-
|
|
2205
|
-
function captureRatio(count: number, total: number): number {
|
|
2206
|
-
return roundCaptureMetric(total > 0 ? count / total : 0);
|
|
2207
|
-
}
|
|
2208
|
-
|
|
2209
|
-
function roundCaptureMetric(value: number): number {
|
|
2210
|
-
if (!Number.isFinite(value)) return 0;
|
|
2211
|
-
return Math.round(value * 1000000) / 1000000;
|
|
2212
|
-
}
|
|
2213
|
-
|
|
2214
|
-
const SHA256_INITIAL_HASH = [
|
|
2215
|
-
0x6a09e667,
|
|
2216
|
-
0xbb67ae85,
|
|
2217
|
-
0x3c6ef372,
|
|
2218
|
-
0xa54ff53a,
|
|
2219
|
-
0x510e527f,
|
|
2220
|
-
0x9b05688c,
|
|
2221
|
-
0x1f83d9ab,
|
|
2222
|
-
0x5be0cd19
|
|
2223
|
-
] as const;
|
|
2224
|
-
|
|
2225
|
-
const SHA256_ROUND_CONSTANTS = [
|
|
2226
|
-
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
2227
|
-
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
2228
|
-
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
2229
|
-
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
2230
|
-
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
2231
|
-
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
2232
|
-
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
2233
|
-
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
|
2234
|
-
] as const;
|
|
2235
|
-
|
|
2236
|
-
function sha256Hex(bytes: Uint8Array): string {
|
|
2237
|
-
const paddedLength = Math.ceil((bytes.length + 9) / 64) * 64;
|
|
2238
|
-
const bitLengthHigh = Math.floor(bytes.length / 0x20000000);
|
|
2239
|
-
const bitLengthLow = (bytes.length % 0x20000000) * 8;
|
|
2240
|
-
const hash: number[] = [...SHA256_INITIAL_HASH];
|
|
2241
|
-
const words = new Uint32Array(64);
|
|
2242
|
-
|
|
2243
|
-
for (let block = 0; block < paddedLength; block += 64) {
|
|
2244
|
-
for (let index = 0; index < 16; index += 1) {
|
|
2245
|
-
const offset = block + index * 4;
|
|
2246
|
-
words[index] = (
|
|
2247
|
-
(sha256PaddedByte(bytes, offset, paddedLength, bitLengthHigh, bitLengthLow) << 24) |
|
|
2248
|
-
(sha256PaddedByte(bytes, offset + 1, paddedLength, bitLengthHigh, bitLengthLow) << 16) |
|
|
2249
|
-
(sha256PaddedByte(bytes, offset + 2, paddedLength, bitLengthHigh, bitLengthLow) << 8) |
|
|
2250
|
-
sha256PaddedByte(bytes, offset + 3, paddedLength, bitLengthHigh, bitLengthLow)
|
|
2251
|
-
) >>> 0;
|
|
2252
|
-
}
|
|
2253
|
-
|
|
2254
|
-
for (let index = 16; index < 64; index += 1) {
|
|
2255
|
-
const first = words[index - 15]!;
|
|
2256
|
-
const second = words[index - 2]!;
|
|
2257
|
-
const sigma0 = rightRotate32(first, 7) ^ rightRotate32(first, 18) ^ (first >>> 3);
|
|
2258
|
-
const sigma1 = rightRotate32(second, 17) ^ rightRotate32(second, 19) ^ (second >>> 10);
|
|
2259
|
-
words[index] = (words[index - 16]! + sigma0 + words[index - 7]! + sigma1) >>> 0;
|
|
2260
|
-
}
|
|
2261
|
-
|
|
2262
|
-
let a = hash[0]!;
|
|
2263
|
-
let b = hash[1]!;
|
|
2264
|
-
let c = hash[2]!;
|
|
2265
|
-
let d = hash[3]!;
|
|
2266
|
-
let e = hash[4]!;
|
|
2267
|
-
let f = hash[5]!;
|
|
2268
|
-
let g = hash[6]!;
|
|
2269
|
-
let h = hash[7]!;
|
|
2270
|
-
|
|
2271
|
-
for (let index = 0; index < 64; index += 1) {
|
|
2272
|
-
const sigma1 = rightRotate32(e, 6) ^ rightRotate32(e, 11) ^ rightRotate32(e, 25);
|
|
2273
|
-
const choice = (e & f) ^ (~e & g);
|
|
2274
|
-
const temp1 = (h + sigma1 + choice + SHA256_ROUND_CONSTANTS[index]! + words[index]!) >>> 0;
|
|
2275
|
-
const sigma0 = rightRotate32(a, 2) ^ rightRotate32(a, 13) ^ rightRotate32(a, 22);
|
|
2276
|
-
const majority = (a & b) ^ (a & c) ^ (b & c);
|
|
2277
|
-
const temp2 = (sigma0 + majority) >>> 0;
|
|
2278
|
-
|
|
2279
|
-
h = g;
|
|
2280
|
-
g = f;
|
|
2281
|
-
f = e;
|
|
2282
|
-
e = (d + temp1) >>> 0;
|
|
2283
|
-
d = c;
|
|
2284
|
-
c = b;
|
|
2285
|
-
b = a;
|
|
2286
|
-
a = (temp1 + temp2) >>> 0;
|
|
2287
|
-
}
|
|
2288
|
-
|
|
2289
|
-
hash[0] = (hash[0]! + a) >>> 0;
|
|
2290
|
-
hash[1] = (hash[1]! + b) >>> 0;
|
|
2291
|
-
hash[2] = (hash[2]! + c) >>> 0;
|
|
2292
|
-
hash[3] = (hash[3]! + d) >>> 0;
|
|
2293
|
-
hash[4] = (hash[4]! + e) >>> 0;
|
|
2294
|
-
hash[5] = (hash[5]! + f) >>> 0;
|
|
2295
|
-
hash[6] = (hash[6]! + g) >>> 0;
|
|
2296
|
-
hash[7] = (hash[7]! + h) >>> 0;
|
|
2297
|
-
}
|
|
2298
|
-
|
|
2299
|
-
return hash.map((word) => word.toString(16).padStart(8, "0")).join("");
|
|
2300
|
-
}
|
|
2301
|
-
|
|
2302
|
-
function sha256PaddedByte(bytes: Uint8Array, index: number, paddedLength: number, bitLengthHigh: number, bitLengthLow: number): number {
|
|
2303
|
-
if (index < bytes.length) return bytes[index]!;
|
|
2304
|
-
if (index === bytes.length) return 0x80;
|
|
2305
|
-
const lengthOffset = index - (paddedLength - 8);
|
|
2306
|
-
if (lengthOffset < 0) return 0;
|
|
2307
|
-
if (lengthOffset < 4) return (bitLengthHigh >>> ((3 - lengthOffset) * 8)) & 0xff;
|
|
2308
|
-
return (bitLengthLow >>> ((7 - lengthOffset) * 8)) & 0xff;
|
|
2309
|
-
}
|
|
2310
|
-
|
|
2311
|
-
function rightRotate32(value: number, bits: number): number {
|
|
2312
|
-
return ((value >>> bits) | (value << (32 - bits))) >>> 0;
|
|
2313
|
-
}
|
|
2314
|
-
|
|
2315
|
-
function withRendererFrameDiagnostics(
|
|
2316
|
-
diagnostics: RenderDeviceDiagnostics,
|
|
2317
|
-
collection: RenderCollectionDiagnostics,
|
|
2318
|
-
postprocess?: RendererPostprocessDiagnostics
|
|
2319
|
-
): RenderDeviceDiagnostics {
|
|
2320
|
-
return {
|
|
2321
|
-
...diagnostics,
|
|
2322
|
-
submittedObjects: collection.submittedObjects,
|
|
2323
|
-
visibleObjects: collection.visibleObjects,
|
|
2324
|
-
culledObjects: collection.culledObjects,
|
|
2325
|
-
frustumTestedObjects: collection.frustumTestedObjects,
|
|
2326
|
-
...(postprocess ?? {})
|
|
2327
|
-
};
|
|
2328
|
-
}
|
|
2329
|
-
|
|
2330
|
-
function renderableWorldBounds(
|
|
2331
|
-
geometry: Geometry,
|
|
2332
|
-
modelMatrix: Mat4,
|
|
2333
|
-
instanceTransforms?: Float32Array | readonly number[],
|
|
2334
|
-
morphTargets?: readonly MorphTargetDelta[],
|
|
2335
|
-
morphWeights?: readonly number[],
|
|
2336
|
-
skinning?: SkinningPaletteBinding
|
|
2337
|
-
): SceneBounds3 {
|
|
2338
|
-
const envelope = computeSkinnedMorphTargetWeightedBounds(geometry, skinning, morphTargets, morphWeights);
|
|
2339
|
-
const local = new SceneBounds3(
|
|
2340
|
-
[envelope.min[0], envelope.min[1], envelope.min[2]],
|
|
2341
|
-
[envelope.max[0], envelope.max[1], envelope.max[2]]
|
|
2342
|
-
);
|
|
2343
|
-
if (!instanceTransforms) {
|
|
2344
|
-
return local.transform(modelMatrix);
|
|
2345
|
-
}
|
|
2346
|
-
|
|
2347
|
-
let bounds = new SceneBounds3();
|
|
2348
|
-
for (let offset = 0; offset < instanceTransforms.length; offset += 16) {
|
|
2349
|
-
const instanceMatrix = toMat4(instanceTransforms.slice(offset, offset + 16), "instanceTransforms");
|
|
2350
|
-
bounds = bounds.union(local.transform(multiplyMat4(modelMatrix, instanceMatrix)));
|
|
2351
|
-
}
|
|
2352
|
-
return bounds;
|
|
2353
|
-
}
|
|
2354
|
-
|
|
2355
|
-
function applyViewProjection(items: readonly RenderItem[], cameraViewProjection?: Mat4): readonly RenderItem[] {
|
|
2356
|
-
const hasCameraViewProjection = cameraViewProjection !== undefined;
|
|
2357
|
-
const viewProjection = cameraViewProjection ?? identityMat4();
|
|
2358
|
-
return items.map((item) => {
|
|
2359
|
-
const modelMatrix = toMat4(item.modelMatrix ?? identityMat4(), "modelMatrix", item.label);
|
|
2360
|
-
const explicitModelViewProjection = item.modelViewProjectionMatrix
|
|
2361
|
-
? toMat4(item.modelViewProjectionMatrix, "modelViewProjectionMatrix", item.label)
|
|
2362
|
-
: undefined;
|
|
2363
|
-
return {
|
|
2364
|
-
...item,
|
|
2365
|
-
modelMatrix,
|
|
2366
|
-
normalMatrix: item.normalMatrix ? toMat4(item.normalMatrix, "normalMatrix", item.label) : normalMatrixFromModel(modelMatrix),
|
|
2367
|
-
modelViewProjectionMatrix: hasCameraViewProjection || !explicitModelViewProjection
|
|
2368
|
-
? multiplyMat4(viewProjection, modelMatrix)
|
|
2369
|
-
: explicitModelViewProjection
|
|
2370
|
-
};
|
|
2371
|
-
});
|
|
2372
|
-
}
|
|
2373
|
-
|
|
2374
|
-
function createAutoFrameCamera(
|
|
2375
|
-
source: RenderSource | Iterable<RenderItem> | Scene,
|
|
2376
|
-
items: readonly RenderItem[],
|
|
2377
|
-
width: number,
|
|
2378
|
-
height: number
|
|
2379
|
-
): { readonly viewProjectionMatrix: Mat4; readonly cameraPosition: readonly [number, number, number] } | undefined {
|
|
2380
|
-
const bounds = collectCameraFrameBounds(source) ?? collectRenderItemBounds(items);
|
|
2381
|
-
if (!bounds || bounds.isEmpty()) {
|
|
2382
|
-
return undefined;
|
|
2383
|
-
}
|
|
2384
|
-
const frame = computePerspectiveCameraFrame(bounds, { width, height }, {
|
|
2385
|
-
...DEFAULT_RENDERER_AUTO_FRAME_OPTIONS,
|
|
2386
|
-
...collectCameraFrameOptions(source)
|
|
2387
|
-
});
|
|
2388
|
-
return {
|
|
2389
|
-
viewProjectionMatrix: frame.viewProjectionMatrix,
|
|
2390
|
-
cameraPosition: frame.cameraPosition
|
|
2391
|
-
};
|
|
2392
|
-
}
|
|
2393
|
-
|
|
2394
|
-
function collectRenderItemBounds(items: readonly RenderItem[]): SceneBounds3 | undefined {
|
|
2395
|
-
let bounds: SceneBounds3 | undefined;
|
|
2396
|
-
for (const item of items) {
|
|
2397
|
-
if (item.includeInAutoFrame === false) continue;
|
|
2398
|
-
const itemBounds = renderableWorldBounds(item.geometry, toMat4(item.modelMatrix ?? identityMat4(), "modelMatrix", item.label), item.instanceTransforms, item.morphTargets, item.morphWeights, item.skinning);
|
|
2399
|
-
bounds = bounds ? bounds.union(itemBounds) : itemBounds;
|
|
2400
|
-
}
|
|
2401
|
-
return bounds;
|
|
2402
|
-
}
|
|
2403
|
-
|
|
2404
|
-
function collectCameraFrameOptions(source: RenderSource | Iterable<RenderItem> | Scene): PerspectiveCameraFrameOptions {
|
|
2405
|
-
if (source instanceof Scene || isIterable(source) || !source.cameraFrameOptions) return {};
|
|
2406
|
-
return source.cameraFrameOptions;
|
|
2407
|
-
}
|
|
2408
|
-
|
|
2409
|
-
function collectCameraFrameBounds(source: RenderSource | Iterable<RenderItem> | Scene): SceneBounds3 | undefined {
|
|
2410
|
-
if (source instanceof Scene || isIterable(source) || !source.cameraFrameBounds) return undefined;
|
|
2411
|
-
const bounds = source.cameraFrameBounds;
|
|
2412
|
-
const min = bounds.min;
|
|
2413
|
-
const max = bounds.max;
|
|
2414
|
-
const values = [...min, ...max];
|
|
2415
|
-
if (values.length !== 6 || values.some((value) => !Number.isFinite(value))) {
|
|
2416
|
-
throw new RenderDeviceError("RenderSource cameraFrameBounds must contain finite min/max vectors.", "CAMERA_FRAME_BOUNDS_INVALID", {
|
|
2417
|
-
min: [...min],
|
|
2418
|
-
max: [...max]
|
|
2419
|
-
});
|
|
2420
|
-
}
|
|
2421
|
-
if (max[0] < min[0] || max[1] < min[1] || max[2] < min[2]) {
|
|
2422
|
-
throw new RenderDeviceError("RenderSource cameraFrameBounds max must be greater than or equal to min.", "CAMERA_FRAME_BOUNDS_INVALID", {
|
|
2423
|
-
min: [...min],
|
|
2424
|
-
max: [...max]
|
|
2425
|
-
});
|
|
2426
|
-
}
|
|
2427
|
-
return new SceneBounds3([min[0], min[1], min[2]], [max[0], max[1], max[2]]);
|
|
2428
|
-
}
|
|
2429
|
-
|
|
2430
|
-
function resolveCamera(
|
|
2431
|
-
source: RenderSource | Iterable<RenderItem> | Scene,
|
|
2432
|
-
camera?: CameraLike,
|
|
2433
|
-
viewport?: { readonly width: number; readonly height: number },
|
|
2434
|
-
options: { readonly allowSceneCamera?: boolean } = {}
|
|
2435
|
-
): { readonly viewProjectionMatrix: Mat4; readonly camera?: Camera; readonly cameraPosition?: readonly [number, number, number] } | undefined {
|
|
2436
|
-
const resolved = camera ?? (options.allowSceneCamera === false ? undefined : resolveSceneCamera(source));
|
|
2437
|
-
if (!resolved) {
|
|
2438
|
-
return undefined;
|
|
2439
|
-
}
|
|
2440
|
-
const resize = maybeCameraResize(resolved);
|
|
2441
|
-
if (viewport && resize) {
|
|
2442
|
-
resize(viewport.width, viewport.height);
|
|
2443
|
-
}
|
|
2444
|
-
resolved.updateCameraMatrices?.();
|
|
2445
|
-
if (resolved.viewProjectionMatrix) {
|
|
2446
|
-
const viewMatrix = resolved.viewMatrix ? toMat4(resolved.viewMatrix, "viewMatrix") : undefined;
|
|
2447
|
-
return {
|
|
2448
|
-
viewProjectionMatrix: toMat4(resolved.viewProjectionMatrix, "viewProjectionMatrix"),
|
|
2449
|
-
...(resolved instanceof Camera
|
|
2450
|
-
? { camera: resolved, cameraPosition: cameraWorldPosition(resolved) }
|
|
2451
|
-
: viewMatrix
|
|
2452
|
-
? { cameraPosition: cameraPositionFromViewMatrix(viewMatrix) }
|
|
2453
|
-
: {})
|
|
2454
|
-
};
|
|
2455
|
-
}
|
|
2456
|
-
if (resolved.projectionMatrix && resolved.viewMatrix) {
|
|
2457
|
-
const viewMatrix = toMat4(resolved.viewMatrix, "viewMatrix");
|
|
2458
|
-
return {
|
|
2459
|
-
viewProjectionMatrix: multiplyMat4(toMat4(resolved.projectionMatrix, "projectionMatrix"), viewMatrix),
|
|
2460
|
-
...(resolved instanceof Camera
|
|
2461
|
-
? { camera: resolved, cameraPosition: cameraWorldPosition(resolved) }
|
|
2462
|
-
: { cameraPosition: cameraPositionFromViewMatrix(viewMatrix) })
|
|
2463
|
-
};
|
|
2464
|
-
}
|
|
2465
|
-
throw new RenderDeviceError(
|
|
2466
|
-
"Renderer camera must expose a viewProjectionMatrix or projectionMatrix plus viewMatrix",
|
|
2467
|
-
"CAMERA_VIEW_PROJECTION_MISSING"
|
|
2468
|
-
);
|
|
2469
|
-
}
|
|
2470
|
-
|
|
2471
|
-
function hasExplicitAutoFrameCameraPolicy(source: RenderSource | Iterable<RenderItem> | Scene): boolean {
|
|
2472
|
-
return !(source instanceof Scene) && !isIterable(source) && source.cameraPolicy === "auto-frame";
|
|
2473
|
-
}
|
|
2474
|
-
|
|
2475
|
-
function cameraWorldPosition(camera: Camera): readonly [number, number, number] {
|
|
2476
|
-
const matrix = camera.transform.worldMatrix;
|
|
2477
|
-
return [matrix[12] ?? 0, matrix[13] ?? 0, matrix[14] ?? 0];
|
|
2478
|
-
}
|
|
2479
|
-
|
|
2480
|
-
function cameraPositionFromViewMatrix(viewMatrix: Mat4): readonly [number, number, number] {
|
|
2481
|
-
const inverseView = invertMat4(viewMatrix);
|
|
2482
|
-
return [inverseView[12] ?? 0, inverseView[13] ?? 0, inverseView[14] ?? 0];
|
|
2483
|
-
}
|
|
2484
|
-
|
|
2485
|
-
function maybeCameraResize(camera: Camera | CameraLike): ((width: number, height: number) => void) | undefined {
|
|
2486
|
-
const candidate = (camera as { readonly resize?: unknown }).resize;
|
|
2487
|
-
return typeof candidate === "function" ? candidate.bind(camera) as (width: number, height: number) => void : undefined;
|
|
2488
|
-
}
|
|
2489
|
-
|
|
2490
|
-
function resolveSceneCamera(source: RenderSource | Iterable<RenderItem> | Scene): Camera | undefined {
|
|
2491
|
-
return sceneFromSource(source)?.collectCameras()[0];
|
|
2492
|
-
}
|
|
2493
|
-
|
|
2494
|
-
function sceneFromSource(source: RenderSource | Iterable<RenderItem> | Scene): Scene | undefined {
|
|
2495
|
-
return source instanceof Scene ? source : isIterable(source) ? undefined : source.scene;
|
|
2496
|
-
}
|
|
2497
|
-
|
|
2498
|
-
function normalMatrixFromModel(modelMatrix: Mat4): Mat4 {
|
|
2499
|
-
let matrix: Mat4;
|
|
2500
|
-
try {
|
|
2501
|
-
matrix = transposeMat4(invertMat4(modelMatrix));
|
|
2502
|
-
} catch {
|
|
2503
|
-
matrix = identityMat4();
|
|
2504
|
-
}
|
|
2505
|
-
return [
|
|
2506
|
-
matrix[0], matrix[1], matrix[2], 0,
|
|
2507
|
-
matrix[4], matrix[5], matrix[6], 0,
|
|
2508
|
-
matrix[8], matrix[9], matrix[10], 0,
|
|
2509
|
-
0, 0, 0, 1
|
|
2510
|
-
];
|
|
2511
|
-
}
|
|
2512
|
-
|
|
2513
|
-
function transposeMat4(matrix: Mat4): Mat4 {
|
|
2514
|
-
return [
|
|
2515
|
-
matrix[0], matrix[4], matrix[8], matrix[12],
|
|
2516
|
-
matrix[1], matrix[5], matrix[9], matrix[13],
|
|
2517
|
-
matrix[2], matrix[6], matrix[10], matrix[14],
|
|
2518
|
-
matrix[3], matrix[7], matrix[11], matrix[15]
|
|
2519
|
-
];
|
|
2520
|
-
}
|
|
2521
|
-
|
|
2522
|
-
function toMat4(value: Float32Array | readonly number[], field: string, label?: string): Mat4 {
|
|
2523
|
-
const values = Array.from(value);
|
|
2524
|
-
if (values.length !== 16 || !values.every(Number.isFinite)) {
|
|
2525
|
-
throw new RenderDeviceError("Renderer matrix inputs must be finite mat4 values", "RENDERER_MATRIX_CONTRACT", {
|
|
2526
|
-
field,
|
|
2527
|
-
label,
|
|
2528
|
-
scalars: values.length
|
|
2529
|
-
});
|
|
2530
|
-
}
|
|
2531
|
-
return values as Mat4;
|
|
2532
|
-
}
|
|
2533
|
-
|
|
2534
|
-
function lookupRenderResource<T>(lookup: RenderResourceLookup<T>, key: string): T | undefined {
|
|
2535
|
-
if (isReadonlyMap(lookup)) {
|
|
2536
|
-
return lookup.get(key);
|
|
2537
|
-
}
|
|
2538
|
-
return lookup[key];
|
|
2539
|
-
}
|
|
2540
|
-
|
|
2541
|
-
function isReadonlyMap<T>(lookup: RenderResourceLookup<T>): lookup is ReadonlyMap<string, T> {
|
|
2542
|
-
return typeof (lookup as ReadonlyMap<string, T>).get === "function";
|
|
2543
|
-
}
|
|
2544
|
-
|
|
2545
|
-
function collectRenderLights(source: RenderSource | Iterable<RenderItem> | Scene): readonly CollectedLight[] {
|
|
2546
|
-
if (source instanceof Scene) {
|
|
2547
|
-
return new LightCollector().collect(source);
|
|
2548
|
-
}
|
|
2549
|
-
if (isIterable(source)) {
|
|
2550
|
-
return [];
|
|
2551
|
-
}
|
|
2552
|
-
if (source.collectedLights !== undefined) {
|
|
2553
|
-
return [...source.collectedLights];
|
|
2554
|
-
}
|
|
2555
|
-
if (source.scene) {
|
|
2556
|
-
const collected = new LightCollector().collect(source.scene);
|
|
2557
|
-
if (collected.length > 0 || source.environmentLighting === false) {
|
|
2558
|
-
return collected;
|
|
2559
|
-
}
|
|
2560
|
-
return createDefaultRendererDirectLights();
|
|
2561
|
-
}
|
|
2562
|
-
if (source.environmentLighting !== false && (source.renderItems || source.collectRenderItems)) {
|
|
2563
|
-
return createDefaultRendererDirectLights();
|
|
2564
|
-
}
|
|
2565
|
-
return [];
|
|
2566
|
-
}
|
|
2567
|
-
|
|
2568
|
-
function createDefaultRendererDirectLights(): readonly CollectedLight[] {
|
|
2569
|
-
const key = new DirectionalLight("default-renderer-key-light");
|
|
2570
|
-
const fill = new DirectionalLight("default-renderer-fill-light");
|
|
2571
|
-
const lights = [
|
|
2572
|
-
{ source: key, ...DEFAULT_RENDERER_DIRECT_LIGHTING.key },
|
|
2573
|
-
{ source: fill, ...DEFAULT_RENDERER_DIRECT_LIGHTING.fill }
|
|
2574
|
-
] as const;
|
|
2575
|
-
return lights.map((light) => {
|
|
2576
|
-
light.source.color = [...light.color] as [number, number, number];
|
|
2577
|
-
light.source.intensity = light.intensity;
|
|
2578
|
-
return {
|
|
2579
|
-
kind: "directional" as const,
|
|
2580
|
-
color: [...light.color] as [number, number, number],
|
|
2581
|
-
intensity: light.intensity,
|
|
2582
|
-
position: [0, 0, 0] as [number, number, number],
|
|
2583
|
-
direction: normalizeDefaultLightDirection(light.direction),
|
|
2584
|
-
range: 0,
|
|
2585
|
-
spotAngle: 0,
|
|
2586
|
-
penumbra: 0,
|
|
2587
|
-
castsShadow: false,
|
|
2588
|
-
layerMask: 0xffffffff,
|
|
2589
|
-
source: light.source
|
|
2590
|
-
};
|
|
2591
|
-
});
|
|
2592
|
-
}
|
|
2593
|
-
|
|
2594
|
-
function normalizeDefaultLightDirection(direction: readonly [number, number, number]): readonly [number, number, number] {
|
|
2595
|
-
const length = Math.hypot(direction[0], direction[1], direction[2]);
|
|
2596
|
-
if (length <= 0 || !Number.isFinite(length)) {
|
|
2597
|
-
return [0, 0, -1];
|
|
2598
|
-
}
|
|
2599
|
-
return [direction[0] / length, direction[1] / length, direction[2] / length];
|
|
2600
|
-
}
|
|
2601
|
-
|
|
2602
|
-
function cloneEnvironmentLighting(environment: EnvironmentLightingOptions): EnvironmentLightingOptions {
|
|
2603
|
-
return {
|
|
2604
|
-
color: [...environment.color] as [number, number, number],
|
|
2605
|
-
intensity: environment.intensity,
|
|
2606
|
-
...(environment.proceduralMap
|
|
2607
|
-
? {
|
|
2608
|
-
proceduralMap: {
|
|
2609
|
-
skyColor: [...environment.proceduralMap.skyColor] as [number, number, number],
|
|
2610
|
-
horizonColor: [...environment.proceduralMap.horizonColor] as [number, number, number],
|
|
2611
|
-
groundColor: [...environment.proceduralMap.groundColor] as [number, number, number],
|
|
2612
|
-
specularColor: [...environment.proceduralMap.specularColor] as [number, number, number],
|
|
2613
|
-
intensity: environment.proceduralMap.intensity,
|
|
2614
|
-
specularIntensity: environment.proceduralMap.specularIntensity
|
|
2615
|
-
}
|
|
2616
|
-
}
|
|
2617
|
-
: {}),
|
|
2618
|
-
...(environment.environmentMapTexture ? { environmentMapTexture: environment.environmentMapTexture } : {}),
|
|
2619
|
-
...(environment.environmentCubeMapTexture ? { environmentCubeMapTexture: environment.environmentCubeMapTexture } : {}),
|
|
2620
|
-
...(environment.environmentMapIntensity !== undefined ? { environmentMapIntensity: environment.environmentMapIntensity } : {}),
|
|
2621
|
-
...(environment.environmentMapSpecularIntensity !== undefined ? { environmentMapSpecularIntensity: environment.environmentMapSpecularIntensity } : {}),
|
|
2622
|
-
...(environment.environmentMapRotation !== undefined ? { environmentMapRotation: environment.environmentMapRotation } : {}),
|
|
2623
|
-
...(environment.environmentMapMipCount !== undefined ? { environmentMapMipCount: environment.environmentMapMipCount } : {}),
|
|
2624
|
-
...(environment.environmentMapEncoding ? { environmentMapEncoding: environment.environmentMapEncoding } : {}),
|
|
2625
|
-
...(environment.environmentBrdfLutTexture ? { environmentBrdfLutTexture: environment.environmentBrdfLutTexture } : {})
|
|
2626
|
-
};
|
|
2627
|
-
}
|
|
2628
|
-
|
|
2629
|
-
function isIterable(value: unknown): value is Iterable<RenderItem> {
|
|
2630
|
-
return typeof (value as Iterable<RenderItem>)[Symbol.iterator] === "function";
|
|
2631
|
-
}
|