@ifc-lite/renderer 3.1.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/dist/aabb-edges.d.ts +10 -0
- package/dist/aabb-edges.d.ts.map +1 -1
- package/dist/aabb-edges.js +13 -0
- package/dist/aabb-edges.js.map +1 -1
- package/dist/ao-params.d.ts +85 -0
- package/dist/ao-params.d.ts.map +1 -0
- package/dist/ao-params.js +109 -0
- package/dist/ao-params.js.map +1 -0
- package/dist/ao-pass.d.ts +47 -0
- package/dist/ao-pass.d.ts.map +1 -0
- package/dist/ao-pass.js +157 -0
- package/dist/ao-pass.js.map +1 -0
- package/dist/appearance-preview.d.ts +2 -0
- package/dist/appearance-preview.d.ts.map +1 -1
- package/dist/appearance-preview.js +4 -0
- package/dist/appearance-preview.js.map +1 -1
- package/dist/camera-animation-immediate.d.ts +5 -0
- package/dist/camera-animation-immediate.d.ts.map +1 -0
- package/dist/camera-animation-immediate.js +16 -0
- package/dist/camera-animation-immediate.js.map +1 -0
- package/dist/camera-animation.d.ts +3 -9
- package/dist/camera-animation.d.ts.map +1 -1
- package/dist/camera-animation.js +14 -9
- package/dist/camera-animation.js.map +1 -1
- package/dist/camera-matrices.d.ts +3 -0
- package/dist/camera-matrices.d.ts.map +1 -1
- package/dist/camera-matrices.js +12 -1
- package/dist/camera-matrices.js.map +1 -1
- package/dist/camera-orientation.d.ts +62 -0
- package/dist/camera-orientation.d.ts.map +1 -0
- package/dist/camera-orientation.js +155 -0
- package/dist/camera-orientation.js.map +1 -0
- package/dist/camera-projection.d.ts +3 -1
- package/dist/camera-projection.d.ts.map +1 -1
- package/dist/camera-projection.js +26 -20
- package/dist/camera-projection.js.map +1 -1
- package/dist/camera-surface-zoom.d.ts +32 -0
- package/dist/camera-surface-zoom.d.ts.map +1 -0
- package/dist/camera-surface-zoom.js +37 -0
- package/dist/camera-surface-zoom.js.map +1 -0
- package/dist/camera.d.ts +32 -61
- package/dist/camera.d.ts.map +1 -1
- package/dist/camera.js +71 -167
- package/dist/camera.js.map +1 -1
- package/dist/clash-solid-pipeline.d.ts +9 -3
- package/dist/clash-solid-pipeline.d.ts.map +1 -1
- package/dist/clash-solid-pipeline.js +31 -8
- package/dist/clash-solid-pipeline.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +4 -3
- package/dist/constants.js.map +1 -1
- package/dist/depth-reconstruct.d.ts +43 -0
- package/dist/depth-reconstruct.d.ts.map +1 -0
- package/dist/depth-reconstruct.js +35 -0
- package/dist/depth-reconstruct.js.map +1 -0
- package/dist/device-recovery.d.ts +60 -0
- package/dist/device-recovery.d.ts.map +1 -0
- package/dist/device-recovery.js +153 -0
- package/dist/device-recovery.js.map +1 -0
- package/dist/device.d.ts +2 -0
- package/dist/device.d.ts.map +1 -1
- package/dist/device.js +13 -1
- package/dist/device.js.map +1 -1
- package/dist/environment.d.ts +26 -15
- package/dist/environment.d.ts.map +1 -1
- package/dist/environment.js +24 -13
- package/dist/environment.js.map +1 -1
- package/dist/federation-registry.d.ts +16 -0
- package/dist/federation-registry.d.ts.map +1 -1
- package/dist/federation-registry.js +80 -9
- package/dist/federation-registry.js.map +1 -1
- package/dist/gpu-static-upload.d.ts +39 -0
- package/dist/gpu-static-upload.d.ts.map +1 -0
- package/dist/gpu-static-upload.js +70 -0
- package/dist/gpu-static-upload.js.map +1 -0
- package/dist/gpu-upload-guard.d.ts +95 -0
- package/dist/gpu-upload-guard.d.ts.map +1 -0
- package/dist/gpu-upload-guard.js +88 -0
- package/dist/gpu-upload-guard.js.map +1 -0
- package/dist/index.d.ts +71 -41
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +340 -375
- package/dist/index.js.map +1 -1
- package/dist/instanced-override-color.d.ts +8 -0
- package/dist/instanced-override-color.d.ts.map +1 -1
- package/dist/instanced-override-color.js +10 -0
- package/dist/instanced-override-color.js.map +1 -1
- package/dist/instanced-render.d.ts +22 -2
- package/dist/instanced-render.d.ts.map +1 -1
- package/dist/instanced-render.js +47 -10
- package/dist/instanced-render.js.map +1 -1
- package/dist/instanced-rte.d.ts +17 -0
- package/dist/instanced-rte.d.ts.map +1 -0
- package/dist/instanced-rte.js +32 -0
- package/dist/instanced-rte.js.map +1 -0
- package/dist/merged-mesh-extract.d.ts.map +1 -1
- package/dist/merged-mesh-extract.js +5 -0
- package/dist/merged-mesh-extract.js.map +1 -1
- package/dist/mesh-rte-uniforms.d.ts +46 -0
- package/dist/mesh-rte-uniforms.d.ts.map +1 -0
- package/dist/mesh-rte-uniforms.js +59 -0
- package/dist/mesh-rte-uniforms.js.map +1 -0
- package/dist/model-bounds-tracker.d.ts +7 -0
- package/dist/model-bounds-tracker.d.ts.map +1 -1
- package/dist/model-bounds-tracker.js +16 -5
- package/dist/model-bounds-tracker.js.map +1 -1
- package/dist/model-translation.d.ts +40 -2
- package/dist/model-translation.d.ts.map +1 -1
- package/dist/model-translation.js +152 -17
- package/dist/model-translation.js.map +1 -1
- package/dist/pick-rte-snapshot.d.ts +7 -0
- package/dist/pick-rte-snapshot.d.ts.map +1 -0
- package/dist/pick-rte-snapshot.js +15 -0
- package/dist/pick-rte-snapshot.js.map +1 -0
- package/dist/pick-types.d.ts +17 -0
- package/dist/pick-types.d.ts.map +1 -0
- package/dist/pick-types.js +5 -0
- package/dist/pick-types.js.map +1 -0
- package/dist/pick-uniforms.d.ts +6 -0
- package/dist/pick-uniforms.d.ts.map +1 -1
- package/dist/pick-uniforms.js +22 -18
- package/dist/pick-uniforms.js.map +1 -1
- package/dist/pick-world-position.d.ts +18 -0
- package/dist/pick-world-position.d.ts.map +1 -0
- package/dist/pick-world-position.js +21 -0
- package/dist/pick-world-position.js.map +1 -0
- package/dist/picker-readbacks.d.ts +6 -0
- package/dist/picker-readbacks.d.ts.map +1 -0
- package/dist/picker-readbacks.js +20 -0
- package/dist/picker-readbacks.js.map +1 -0
- package/dist/picker-rte-uniforms.d.ts +6 -0
- package/dist/picker-rte-uniforms.d.ts.map +1 -0
- package/dist/picker-rte-uniforms.js +41 -0
- package/dist/picker-rte-uniforms.js.map +1 -0
- package/dist/picker.d.ts +6 -7
- package/dist/picker.d.ts.map +1 -1
- package/dist/picker.js +88 -115
- package/dist/picker.js.map +1 -1
- package/dist/picking-manager.d.ts +10 -1
- package/dist/picking-manager.d.ts.map +1 -1
- package/dist/picking-manager.js +57 -21
- package/dist/picking-manager.js.map +1 -1
- package/dist/pipeline.d.ts +1 -7
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +17 -20
- package/dist/pipeline.js.map +1 -1
- package/dist/point-picker.d.ts +5 -1
- package/dist/point-picker.d.ts.map +1 -1
- package/dist/point-picker.js +49 -9
- package/dist/point-picker.js.map +1 -1
- package/dist/pointcloud/point-cloud-handle-ids.d.ts +22 -0
- package/dist/pointcloud/point-cloud-handle-ids.d.ts.map +1 -0
- package/dist/pointcloud/point-cloud-handle-ids.js +31 -0
- package/dist/pointcloud/point-cloud-handle-ids.js.map +1 -0
- package/dist/pointcloud/point-cloud-node.d.ts +14 -6
- package/dist/pointcloud/point-cloud-node.d.ts.map +1 -1
- package/dist/pointcloud/point-cloud-node.js.map +1 -1
- package/dist/pointcloud/point-cloud-pick-sources.d.ts +47 -0
- package/dist/pointcloud/point-cloud-pick-sources.d.ts.map +1 -0
- package/dist/pointcloud/point-cloud-pick-sources.js +32 -0
- package/dist/pointcloud/point-cloud-pick-sources.js.map +1 -0
- package/dist/pointcloud/point-cloud-placement.d.ts.map +1 -1
- package/dist/pointcloud/point-cloud-placement.js +20 -5
- package/dist/pointcloud/point-cloud-placement.js.map +1 -1
- package/dist/pointcloud/point-cloud-ray-transform.d.ts +78 -4
- package/dist/pointcloud/point-cloud-ray-transform.d.ts.map +1 -1
- package/dist/pointcloud/point-cloud-ray-transform.js +31 -84
- package/dist/pointcloud/point-cloud-ray-transform.js.map +1 -1
- package/dist/pointcloud/point-cloud-renderer.d.ts +21 -19
- package/dist/pointcloud/point-cloud-renderer.d.ts.map +1 -1
- package/dist/pointcloud/point-cloud-renderer.js +46 -52
- package/dist/pointcloud/point-cloud-renderer.js.map +1 -1
- package/dist/pointcloud/point-cloud-stream-lifecycle.d.ts +44 -0
- package/dist/pointcloud/point-cloud-stream-lifecycle.d.ts.map +1 -0
- package/dist/pointcloud/point-cloud-stream-lifecycle.js +44 -0
- package/dist/pointcloud/point-cloud-stream-lifecycle.js.map +1 -0
- package/dist/pointcloud/point-cloud-uniforms.d.ts +5 -0
- package/dist/pointcloud/point-cloud-uniforms.d.ts.map +1 -1
- package/dist/pointcloud/point-cloud-uniforms.js +55 -44
- package/dist/pointcloud/point-cloud-uniforms.js.map +1 -1
- package/dist/pointcloud/point-cloud-visibility.d.ts +10 -0
- package/dist/pointcloud/point-cloud-visibility.d.ts.map +1 -0
- package/dist/pointcloud/point-cloud-visibility.js +18 -0
- package/dist/pointcloud/point-cloud-visibility.js.map +1 -0
- package/dist/pointcloud/point-pipeline.d.ts +4 -9
- package/dist/pointcloud/point-pipeline.d.ts.map +1 -1
- package/dist/pointcloud/point-pipeline.js +4 -9
- package/dist/pointcloud/point-pipeline.js.map +1 -1
- package/dist/pointcloud/point-shader.wgsl.d.ts +1 -21
- package/dist/pointcloud/point-shader.wgsl.d.ts.map +1 -1
- package/dist/pointcloud/point-shader.wgsl.js +25 -5
- package/dist/pointcloud/point-shader.wgsl.js.map +1 -1
- package/dist/post-pass-chain.d.ts +38 -0
- package/dist/post-pass-chain.d.ts.map +1 -0
- package/dist/post-pass-chain.js +99 -0
- package/dist/post-pass-chain.js.map +1 -0
- package/dist/post-processor.d.ts +12 -26
- package/dist/post-processor.d.ts.map +1 -1
- package/dist/post-processor.js +14 -72
- package/dist/post-processor.js.map +1 -1
- package/dist/raycast-engine.d.ts +7 -5
- package/dist/raycast-engine.d.ts.map +1 -1
- package/dist/raycast-engine.js +17 -20
- package/dist/raycast-engine.js.map +1 -1
- package/dist/raycast-point-cloud-query.d.ts +1 -1
- package/dist/raycast-point-cloud-query.d.ts.map +1 -1
- package/dist/reference-image-pipeline.d.ts +1 -1
- package/dist/reference-image-pipeline.d.ts.map +1 -1
- package/dist/reference-image-pipeline.js +25 -7
- package/dist/reference-image-pipeline.js.map +1 -1
- package/dist/reference-images.d.ts +3 -1
- package/dist/reference-images.d.ts.map +1 -1
- package/dist/reference-images.js +15 -5
- package/dist/reference-images.js.map +1 -1
- package/dist/relative-to-eye.d.ts +146 -0
- package/dist/relative-to-eye.d.ts.map +1 -0
- package/dist/relative-to-eye.js +326 -0
- package/dist/relative-to-eye.js.map +1 -0
- package/dist/render-section-draw.d.ts +11 -0
- package/dist/render-section-draw.d.ts.map +1 -1
- package/dist/render-section-draw.js +6 -3
- package/dist/render-section-draw.js.map +1 -1
- package/dist/renderer-color-readback.d.ts +50 -0
- package/dist/renderer-color-readback.d.ts.map +1 -0
- package/dist/renderer-color-readback.js +145 -0
- package/dist/renderer-color-readback.js.map +1 -0
- package/dist/renderer-overlays.d.ts +15 -3
- package/dist/renderer-overlays.d.ts.map +1 -1
- package/dist/renderer-overlays.js +46 -20
- package/dist/renderer-overlays.js.map +1 -1
- package/dist/renderer-screenshot.d.ts +4 -0
- package/dist/renderer-screenshot.d.ts.map +1 -0
- package/dist/renderer-screenshot.js +17 -0
- package/dist/renderer-screenshot.js.map +1 -0
- package/dist/renderer-symbolic-overlays.d.ts +5 -2
- package/dist/renderer-symbolic-overlays.d.ts.map +1 -1
- package/dist/renderer-symbolic-overlays.js +36 -13
- package/dist/renderer-symbolic-overlays.js.map +1 -1
- package/dist/renderer-viewport.d.ts +45 -0
- package/dist/renderer-viewport.d.ts.map +1 -1
- package/dist/renderer-viewport.js +44 -0
- package/dist/renderer-viewport.js.map +1 -1
- package/dist/rte-clip-space.d.ts +12 -0
- package/dist/rte-clip-space.d.ts.map +1 -0
- package/dist/rte-clip-space.js +22 -0
- package/dist/rte-clip-space.js.map +1 -0
- package/dist/rte-frustum.d.ts +24 -0
- package/dist/rte-frustum.d.ts.map +1 -0
- package/dist/rte-frustum.js +45 -0
- package/dist/rte-frustum.js.map +1 -0
- package/dist/scene-appearance-preview.d.ts +2 -0
- package/dist/scene-appearance-preview.d.ts.map +1 -1
- package/dist/scene-appearance-preview.js +9 -0
- package/dist/scene-appearance-preview.js.map +1 -1
- package/dist/scene-authored-owner.d.ts +10 -0
- package/dist/scene-authored-owner.d.ts.map +1 -1
- package/dist/scene-authored-owner.js +44 -5
- package/dist/scene-authored-owner.js.map +1 -1
- package/dist/scene-batch-rebuild.d.ts +18 -0
- package/dist/scene-batch-rebuild.d.ts.map +1 -0
- package/dist/scene-batch-rebuild.js +49 -0
- package/dist/scene-batch-rebuild.js.map +1 -0
- package/dist/scene-batch-upload.d.ts +4 -1
- package/dist/scene-batch-upload.d.ts.map +1 -1
- package/dist/scene-batch-upload.js +31 -38
- package/dist/scene-batch-upload.js.map +1 -1
- package/dist/scene-bucket-routing.d.ts +21 -0
- package/dist/scene-bucket-routing.d.ts.map +1 -0
- package/dist/scene-bucket-routing.js +44 -0
- package/dist/scene-bucket-routing.js.map +1 -0
- package/dist/scene-contents.d.ts +8 -0
- package/dist/scene-contents.d.ts.map +1 -1
- package/dist/scene-derived-mesh-provenance.d.ts +13 -0
- package/dist/scene-derived-mesh-provenance.d.ts.map +1 -0
- package/dist/scene-derived-mesh-provenance.js +30 -0
- package/dist/scene-derived-mesh-provenance.js.map +1 -0
- package/dist/scene-device-recovery.d.ts +103 -0
- package/dist/scene-device-recovery.d.ts.map +1 -0
- package/dist/scene-device-recovery.js +295 -0
- package/dist/scene-device-recovery.js.map +1 -0
- package/dist/scene-finalize-regroup.d.ts +54 -0
- package/dist/scene-finalize-regroup.d.ts.map +1 -0
- package/dist/scene-finalize-regroup.js +136 -0
- package/dist/scene-finalize-regroup.js.map +1 -0
- package/dist/scene-geometry.d.ts +1 -0
- package/dist/scene-geometry.d.ts.map +1 -1
- package/dist/scene-geometry.js +3 -23
- package/dist/scene-geometry.js.map +1 -1
- package/dist/scene-instance-bounds.d.ts +12 -0
- package/dist/scene-instance-bounds.d.ts.map +1 -0
- package/dist/scene-instance-bounds.js +48 -0
- package/dist/scene-instance-bounds.js.map +1 -0
- package/dist/scene-instance-materialization.d.ts +4 -1
- package/dist/scene-instance-materialization.d.ts.map +1 -1
- package/dist/scene-instance-materialization.js +21 -6
- package/dist/scene-instance-materialization.js.map +1 -1
- package/dist/scene-instance-translation.d.ts +17 -0
- package/dist/scene-instance-translation.d.ts.map +1 -0
- package/dist/scene-instance-translation.js +50 -0
- package/dist/scene-instance-translation.js.map +1 -0
- package/dist/scene-instance-types.d.ts +6 -0
- package/dist/scene-instance-types.d.ts.map +1 -1
- package/dist/scene-model-translation.d.ts +13 -1
- package/dist/scene-model-translation.d.ts.map +1 -1
- package/dist/scene-model-translation.js +23 -1
- package/dist/scene-model-translation.js.map +1 -1
- package/dist/scene-precision.d.ts +16 -0
- package/dist/scene-precision.d.ts.map +1 -0
- package/dist/scene-precision.js +73 -0
- package/dist/scene-precision.js.map +1 -0
- package/dist/scene-rect-select.d.ts +11 -1
- package/dist/scene-rect-select.d.ts.map +1 -1
- package/dist/scene-rect-select.js +7 -4
- package/dist/scene-rect-select.js.map +1 -1
- package/dist/scene.d.ts +42 -39
- package/dist/scene.d.ts.map +1 -1
- package/dist/scene.js +258 -375
- package/dist/scene.js.map +1 -1
- package/dist/section-2d-lift.d.ts +10 -2
- package/dist/section-2d-lift.d.ts.map +1 -1
- package/dist/section-2d-lift.js +14 -6
- package/dist/section-2d-lift.js.map +1 -1
- package/dist/section-2d-line-buffer.d.ts +12 -4
- package/dist/section-2d-line-buffer.d.ts.map +1 -1
- package/dist/section-2d-line-buffer.js +59 -33
- package/dist/section-2d-line-buffer.js.map +1 -1
- package/dist/section-2d-overlay.d.ts +18 -72
- package/dist/section-2d-overlay.d.ts.map +1 -1
- package/dist/section-2d-overlay.js +72 -103
- package/dist/section-2d-overlay.js.map +1 -1
- package/dist/section-2d-types.d.ts +38 -0
- package/dist/section-2d-types.d.ts.map +1 -0
- package/dist/section-2d-types.js +6 -0
- package/dist/section-2d-types.js.map +1 -0
- package/dist/section-cap-pipelines.d.ts +6 -0
- package/dist/section-cap-pipelines.d.ts.map +1 -0
- package/dist/section-cap-pipelines.js +87 -0
- package/dist/section-cap-pipelines.js.map +1 -0
- package/dist/section-plane-geometry.d.ts +24 -0
- package/dist/section-plane-geometry.d.ts.map +1 -0
- package/dist/section-plane-geometry.js +93 -0
- package/dist/section-plane-geometry.js.map +1 -0
- package/dist/section-plane.d.ts +23 -24
- package/dist/section-plane.d.ts.map +1 -1
- package/dist/section-plane.js +58 -174
- package/dist/section-plane.js.map +1 -1
- package/dist/shaders/ao.wgsl.d.ts +2 -0
- package/dist/shaders/ao.wgsl.d.ts.map +1 -0
- package/dist/shaders/ao.wgsl.js +234 -0
- package/dist/shaders/ao.wgsl.js.map +1 -0
- package/dist/shaders/color-transfer.wgsl.d.ts +26 -0
- package/dist/shaders/color-transfer.wgsl.d.ts.map +1 -0
- package/dist/shaders/color-transfer.wgsl.js +53 -0
- package/dist/shaders/color-transfer.wgsl.js.map +1 -0
- package/dist/shaders/depth-reconstruct.wgsl.d.ts +16 -0
- package/dist/shaders/depth-reconstruct.wgsl.d.ts.map +1 -0
- package/dist/shaders/depth-reconstruct.wgsl.js +69 -0
- package/dist/shaders/depth-reconstruct.wgsl.js.map +1 -0
- package/dist/shaders/main-rte-shadow.wgsl.d.ts +4 -0
- package/dist/shaders/main-rte-shadow.wgsl.d.ts.map +1 -0
- package/dist/shaders/main-rte-shadow.wgsl.js +54 -0
- package/dist/shaders/main-rte-shadow.wgsl.js.map +1 -0
- package/dist/shaders/main.wgsl.d.ts +1 -6
- package/dist/shaders/main.wgsl.d.ts.map +1 -1
- package/dist/shaders/main.wgsl.js +110 -159
- package/dist/shaders/main.wgsl.js.map +1 -1
- package/dist/shaders/relative-to-eye.wgsl.d.ts +12 -0
- package/dist/shaders/relative-to-eye.wgsl.d.ts.map +1 -0
- package/dist/shaders/relative-to-eye.wgsl.js +40 -0
- package/dist/shaders/relative-to-eye.wgsl.js.map +1 -0
- package/dist/shaders/section-2d-overlay.wgsl.d.ts +28 -12
- package/dist/shaders/section-2d-overlay.wgsl.d.ts.map +1 -1
- package/dist/shaders/section-2d-overlay.wgsl.js +40 -13
- package/dist/shaders/section-2d-overlay.wgsl.js.map +1 -1
- package/dist/shaders/shadow.wgsl.d.ts +1 -1
- package/dist/shaders/shadow.wgsl.d.ts.map +1 -1
- package/dist/shaders/shadow.wgsl.js +25 -7
- package/dist/shaders/shadow.wgsl.js.map +1 -1
- package/dist/shaders/sky.wgsl.d.ts +1 -11
- package/dist/shaders/sky.wgsl.d.ts.map +1 -1
- package/dist/shaders/sky.wgsl.js +17 -19
- package/dist/shaders/sky.wgsl.js.map +1 -1
- package/dist/shaders/symbolic-overlay.wgsl.d.ts +2 -9
- package/dist/shaders/symbolic-overlay.wgsl.d.ts.map +1 -1
- package/dist/shaders/symbolic-overlay.wgsl.js +88 -17
- package/dist/shaders/symbolic-overlay.wgsl.js.map +1 -1
- package/dist/shaders/textured.wgsl.d.ts.map +1 -1
- package/dist/shaders/textured.wgsl.js +11 -9
- package/dist/shaders/textured.wgsl.js.map +1 -1
- package/dist/shadow-light-matrix.d.ts +7 -0
- package/dist/shadow-light-matrix.d.ts.map +1 -1
- package/dist/shadow-light-matrix.js +11 -0
- package/dist/shadow-light-matrix.js.map +1 -1
- package/dist/shadow-occluder-batches.d.ts +10 -0
- package/dist/shadow-occluder-batches.d.ts.map +1 -0
- package/dist/shadow-occluder-batches.js +39 -0
- package/dist/shadow-occluder-batches.js.map +1 -0
- package/dist/shadow-occluders.d.ts.map +1 -1
- package/dist/shadow-occluders.js +4 -0
- package/dist/shadow-occluders.js.map +1 -1
- package/dist/shadow-pass.d.ts +5 -51
- package/dist/shadow-pass.d.ts.map +1 -1
- package/dist/shadow-pass.js +49 -45
- package/dist/shadow-pass.js.map +1 -1
- package/dist/shadow-types.d.ts +39 -0
- package/dist/shadow-types.d.ts.map +1 -0
- package/dist/shadow-types.js +15 -0
- package/dist/shadow-types.js.map +1 -0
- package/dist/symbolic-overlay-geometry.d.ts +7 -0
- package/dist/symbolic-overlay-geometry.d.ts.map +1 -0
- package/dist/symbolic-overlay-geometry.js +43 -0
- package/dist/symbolic-overlay-geometry.js.map +1 -0
- package/dist/symbolic-overlay-pipelines.d.ts +24 -36
- package/dist/symbolic-overlay-pipelines.d.ts.map +1 -1
- package/dist/symbolic-overlay-pipelines.js +175 -182
- package/dist/symbolic-overlay-pipelines.js.map +1 -1
- package/dist/symbolic-text-atlas.d.ts +21 -0
- package/dist/symbolic-text-atlas.d.ts.map +1 -1
- package/dist/symbolic-text-atlas.js +29 -1
- package/dist/symbolic-text-atlas.js.map +1 -1
- package/dist/types.d.ts +20 -26
- package/dist/types.d.ts.map +1 -1
- package/dist/visual-enhancement.d.ts +23 -1
- package/dist/visual-enhancement.d.ts.map +1 -1
- package/dist/visual-enhancement.js +38 -12
- package/dist/visual-enhancement.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ export { appearanceSourceTriangle, expandAppearanceCorners, equivalentAppearance
|
|
|
11
11
|
export { sameCompanionParts } from './appearance-companions.js';
|
|
12
12
|
export { invertAppearancePartition, validateAppearancePartition } from './appearance-preview.js';
|
|
13
13
|
import { createReferenceImageManager } from './reference-image-host.js';
|
|
14
|
-
import { resizeRendererViewport } from './renderer-viewport.js';
|
|
14
|
+
import { measureDrawingBuffer, resizeRendererViewport } from './renderer-viewport.js';
|
|
15
15
|
export { pickFitPolicy } from './camera-fit-policy.js';
|
|
16
16
|
// `Scene` is NOT exported: an exported class republishes every method `SceneContents` above just froze.
|
|
17
17
|
export { Picker } from './picker.js';
|
|
@@ -79,71 +79,42 @@ import { Scene } from './scene.js';
|
|
|
79
79
|
import { Picker } from './picker.js';
|
|
80
80
|
import { reportableItemId } from './pick-resolve.js';
|
|
81
81
|
import { MathUtils, viewBasis } from './math.js';
|
|
82
|
-
import {
|
|
83
|
-
import { VisualEnhancementResolver } from './visual-enhancement.js';
|
|
82
|
+
import { isRteAabbVisible, rteFrustum, sourceFrustumFromRte } from './rte-frustum.js';
|
|
83
|
+
import { VisualEnhancementResolver, livePostEffects } from './visual-enhancement.js';
|
|
84
84
|
import { packClipBox } from './clip-box.js';
|
|
85
|
+
import { MESH_FLAGS_BYTE_OFFSET, MESH_FLAG_RTE_DRAWABLE, MESH_UNIFORM_FLOATS, MESH_UNIFORM_OFFSET, packRteCameraOrigin, packRteFragmentSpace, } from './mesh-rte-uniforms.js';
|
|
85
86
|
import { RendererOverlays } from './renderer-overlays.js';
|
|
86
87
|
import { resolveSectionPlaneFrame } from './render-section-plane.js';
|
|
87
88
|
import { PickingManager } from './picking-manager.js';
|
|
88
89
|
import { RaycastEngine } from './raycast-engine.js';
|
|
89
90
|
import { RenderDegradationMonitor } from './render-degradation.js';
|
|
90
|
-
import {
|
|
91
|
+
import { PostPassChain } from './post-pass-chain.js';
|
|
91
92
|
import { InteractionEffectsGovernor } from './interaction-effects-governor.js';
|
|
92
93
|
import { VisibilityEpochTracker } from './visibility-epoch.js';
|
|
93
94
|
import { isEntityVisible } from './entity-visibility.js';
|
|
94
95
|
import { ModelBoundsTracker } from './model-bounds-tracker.js';
|
|
95
96
|
import { resolveContributionThresholdPx, projectedAabbRadiusPx, projectedInstancedRadiusPx } from './contribution-cull.js';
|
|
96
|
-
import { EdlPass } from './edl-pass.js';
|
|
97
97
|
import { SkyPass } from './sky-pass.js';
|
|
98
98
|
import { skyShaderSource } from './shaders/sky.wgsl.js';
|
|
99
99
|
import { resolveEnvironment } from './environment.js';
|
|
100
100
|
import { ShadowPass, resolveShadowMapResolution } from './shadow-pass.js';
|
|
101
|
-
import { fitSunLightMatrix, cameraFrustumFocusCorners } from './shadow-light-matrix.js';
|
|
102
|
-
import { collectShadowOccluders
|
|
101
|
+
import { fitSunLightMatrix, cameraFrustumFocusCorners, resolveShadowNormalBiasMetres } from './shadow-light-matrix.js';
|
|
102
|
+
import { collectShadowOccluders } from './shadow-occluders.js';
|
|
103
|
+
import { uploadInstancedRteDeltas } from './instanced-rte.js';
|
|
104
|
+
import { shadowOccluderBatches } from './shadow-occluder-batches.js';
|
|
105
|
+
import { captureRendererScreenshot } from './renderer-screenshot.js';
|
|
106
|
+
import { beginRendererColorFrameCapture, cancelRendererColorFrame, discardRendererColorFrameReadback, encodeRendererColorFrameCapture, requestRendererColorFrame, retryRendererColorFrame, settleRendererColorFrameCapture } from './renderer-color-readback.js';
|
|
103
107
|
import { shouldRouteMeshTransparent, shouldRouteBatchTransparent, splitVisibleIdsByPromotion, DEFAULT_GHOST_ALPHA } from './overlay-routing.js';
|
|
104
108
|
import { XRayAlpha, XRayEpochTracker } from './xray-alpha.js';
|
|
105
109
|
import { PartialBatchRequests } from './partial-batch-requests.js';
|
|
106
110
|
import { colorSaltByte, packEntityLane } from './scene-geometry.js';
|
|
107
111
|
import { PointCloudRenderer } from './pointcloud/point-cloud-renderer.js';
|
|
112
|
+
import { appendPointCloudChunk as appendPointCloudChunkImpl, beginPointCloudStream as beginPointCloudStreamImpl, endPointCloudStream as endPointCloudStreamImpl, removePointCloudAsset as removePointCloudAssetImpl, } from './pointcloud/point-cloud-stream-lifecycle.js';
|
|
108
113
|
import { DeviationComputer } from './deviation/deviation-computer.js';
|
|
114
|
+
import { runGuardedGpuUpload, isDeviceLossThrow } from './gpu-upload-guard.js';
|
|
115
|
+
import { recoverRendererDevice, rendererDeviceLostError } from './device-recovery.js';
|
|
109
116
|
const MAX_ENCODED_ENTITY_ID = 0xFFFFFF;
|
|
110
117
|
let warnedEntityIdRange = false;
|
|
111
|
-
/**
|
|
112
|
-
* Is this throw the GPU device telling us it is gone?
|
|
113
|
-
*
|
|
114
|
-
* The discriminator is the exception TYPE, not its message, because WebGPU
|
|
115
|
-
* draws exactly that line:
|
|
116
|
-
* - a call on a dead / invalid-state device throws a `DOMException`
|
|
117
|
-
* (`InvalidStateError` in Safari 26.5 — the whole of issue #2229);
|
|
118
|
-
* - a buffer allocation the host cannot back throws a plain `RangeError`
|
|
119
|
-
* ("createBuffer failed, size (…) is too large … when mappedAtCreation ==
|
|
120
|
-
* true"), which `gpu-upload-guard` documents happening on a HEALTHY device
|
|
121
|
-
* under memory pressure.
|
|
122
|
-
*
|
|
123
|
-
* Treating the second as a device loss is a false positive that costs the whole
|
|
124
|
-
* session, so only the first latches; everything else degrades one frame.
|
|
125
|
-
*
|
|
126
|
-
* There is deliberately NO consecutive-failure threshold as a middle ground.
|
|
127
|
-
* Not because failures necessarily arrive back-to-back — between two BCF / IDS
|
|
128
|
-
* capture frames the awaited `camera.frameBounds` normally does let an ordinary
|
|
129
|
-
* rAF frame through, which would reset a counter — but because those ordinary
|
|
130
|
-
* frames are not guaranteed to SUCCEED: they allocate too (`ensureMeshResources`
|
|
131
|
-
* creates a buffer per unresourced mesh, and the queued-mesh flush allocates),
|
|
132
|
-
* so under sustained host memory pressure any finite budget is still reachable.
|
|
133
|
-
* A latch whose safety depends on incidental animation timing is the wrong
|
|
134
|
-
* shape of guarantee for "never kill the viewport by mistake".
|
|
135
|
-
*
|
|
136
|
-
* Real losses on browsers that do not throw are still caught by the async
|
|
137
|
-
* `device.lost` promise — which the WebGPU spec makes the sole loss channel
|
|
138
|
-
* anyway (on a conformant engine, calls against a lost device are no-ops, not
|
|
139
|
-
* throws; Safari 26.5's synchronous throw is the deviation being handled here).
|
|
140
|
-
*
|
|
141
|
-
* `typeof` guarded because non-DOM hosts (Node before 17, some workers) have no
|
|
142
|
-
* `DOMException` global; there, no throw can be a WebGPU device signal anyway.
|
|
143
|
-
*/
|
|
144
|
-
function isDeviceLossThrow(error) {
|
|
145
|
-
return typeof DOMException !== 'undefined' && error instanceof DOMException;
|
|
146
|
-
}
|
|
147
118
|
/**
|
|
148
119
|
* The reason `whenReady()` rejects when the renderer is destroyed.
|
|
149
120
|
*
|
|
@@ -157,20 +128,6 @@ function rendererDestroyedError() {
|
|
|
157
128
|
error.name = 'RendererDestroyedError';
|
|
158
129
|
return error;
|
|
159
130
|
}
|
|
160
|
-
/**
|
|
161
|
-
* The reason `whenReady()` rejects once the GPU device has been lost.
|
|
162
|
-
*
|
|
163
|
-
* Deliberately NOT `RendererDestroyedError`: a destroyed renderer is finished,
|
|
164
|
-
* while a lost one is dead only until the host re-initialises it (`init()`
|
|
165
|
-
* clears the latch and readiness is published again). A caller that wants to
|
|
166
|
-
* retry needs to tell those apart, so the loss carries its own `name` — same
|
|
167
|
-
* plain-`Error` shape, for the same reasons.
|
|
168
|
-
*/
|
|
169
|
-
function rendererDeviceLostError() {
|
|
170
|
-
const error = new Error('GPU device was lost before the renderer became ready');
|
|
171
|
-
error.name = 'RendererDeviceLostError';
|
|
172
|
-
return error;
|
|
173
|
-
}
|
|
174
131
|
/**
|
|
175
132
|
* Main renderer class
|
|
176
133
|
*/
|
|
@@ -190,6 +147,7 @@ export class Renderer {
|
|
|
190
147
|
overlays = new RendererOverlays({
|
|
191
148
|
getModelBounds: () => this.getModelBounds(),
|
|
192
149
|
expandModelBoundsWithFlatVertices: (positions, stride) => this.modelBoundsTracker.expandWithFlatVertices(positions, stride),
|
|
150
|
+
expandModelBoundsWithAnchoredLineVertices: (positions, origin, stride) => this.modelBoundsTracker.expandWithAnchoredVertices(positions, origin, stride),
|
|
193
151
|
syncCameraSceneBounds: () => {
|
|
194
152
|
if (this.modelBounds)
|
|
195
153
|
this.camera.setSceneBounds(this.modelBounds);
|
|
@@ -198,9 +156,12 @@ export class Renderer {
|
|
|
198
156
|
});
|
|
199
157
|
referenceImages = createReferenceImageManager(this);
|
|
200
158
|
getReferenceImages() { return this.referenceImages; }
|
|
201
|
-
|
|
159
|
+
// Ambient occlusion, separation lines and eye-dome lighting (post-pass-chain.ts),
|
|
160
|
+
// created on the first rendered frame.
|
|
161
|
+
postPasses = null;
|
|
162
|
+
/** Device px per CSS px in the drawing buffer; CSS-px sizes scale by it (#5383). */
|
|
163
|
+
pixelRatio = 1;
|
|
202
164
|
interactionEffects = new InteractionEffectsGovernor();
|
|
203
|
-
edlPass = null;
|
|
204
165
|
// Procedural sky background — created lazily on the first frame that
|
|
205
166
|
// enables it (most sessions never do).
|
|
206
167
|
skyPass = null;
|
|
@@ -219,8 +180,10 @@ export class Renderer {
|
|
|
219
180
|
highQuality: true,
|
|
220
181
|
};
|
|
221
182
|
pointCloudRenderer = null;
|
|
222
|
-
|
|
223
|
-
|
|
183
|
+
pointCloudStreamEpoch = 0;
|
|
184
|
+
pointCloudStreamEpochs = new Map(); // handle id → epoch; cleanup rebuilds `{ id }`, cleared per epoch
|
|
185
|
+
pointCloudNextHandleId = 1; // watermark carried across teardown() so a replacement never reissues an id
|
|
186
|
+
/** Set true at the end of the LATEST `init()`; gates `whenReady()` and
|
|
224
187
|
* `isReady()`. Revoked synchronously by `init()` and by `destroy()`, and
|
|
225
188
|
* overridden (not cleared) by a device loss — see `deviceLost`, which the
|
|
226
189
|
* two readiness methods consult alongside this flag because the loss can
|
|
@@ -242,19 +205,18 @@ export class Renderer {
|
|
|
242
205
|
*/
|
|
243
206
|
destroyed = false;
|
|
244
207
|
/**
|
|
245
|
-
* The tail of the `init()`
|
|
246
|
-
* running immediately, so
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
* caller) so one failure does not deadlock every later call.
|
|
208
|
+
* The tail of the GPU lifecycle queue. `init()` and `recoverDevice()` chain
|
|
209
|
+
* onto this rather than running immediately, so overlapping calls cannot
|
|
210
|
+
* both mutate the shared device/pipeline fields while one is awaiting its
|
|
211
|
+
* adapter. Always settled fulfilled — a rejected operation is swallowed
|
|
212
|
+
* HERE (never for its caller) so one failure cannot deadlock later work.
|
|
251
213
|
*/
|
|
252
214
|
initChain = Promise.resolve();
|
|
253
215
|
/**
|
|
254
|
-
* Incremented synchronously by every `init()`
|
|
255
|
-
* `destroy()`. It stamps
|
|
256
|
-
*
|
|
257
|
-
*
|
|
216
|
+
* Incremented synchronously by every `init()` / `recoverDevice()` call and
|
|
217
|
+
* by every public `destroy()`. It stamps the lifecycle event an in-flight
|
|
218
|
+
* operation belongs to: stale work must neither allocate nor publish
|
|
219
|
+
* readiness.
|
|
258
220
|
*
|
|
259
221
|
* Both bumps are load-bearing, for the same reason. Because the queue above
|
|
260
222
|
* defers the body, an init can finish while a later one is still waiting its
|
|
@@ -312,6 +274,10 @@ export class Renderer {
|
|
|
312
274
|
/** Retained so a listener registered AFTER the loss still learns of it. */
|
|
313
275
|
deviceLostInfo = null;
|
|
314
276
|
deviceLostListeners = new Set();
|
|
277
|
+
deviceLossSequence = 0;
|
|
278
|
+
recovery = {
|
|
279
|
+
inFlight: null, lostReferenceImages: false, quantizedBatchesRequested: false, omissions: new Set(), pointCloudOptions: null,
|
|
280
|
+
};
|
|
315
281
|
/** BIM ↔ scan deviation: owns the compute pipeline + its BVH cache. */
|
|
316
282
|
deviationComputer = new DeviationComputer();
|
|
317
283
|
visualEnhancementResolver = new VisualEnhancementResolver();
|
|
@@ -425,13 +391,9 @@ export class Renderer {
|
|
|
425
391
|
// One-shot log guard — prints Y-up clip bounds on first section-enable so
|
|
426
392
|
// users can confirm the slider is operating on the intended range.
|
|
427
393
|
_loggedSectionBounds = false;
|
|
428
|
-
//
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
// 60 floats = the WGSL Uniforms struct incl. quantParams (see
|
|
432
|
-
// pipeline.getUniformBufferSize).
|
|
433
|
-
uniformScratch = new Float32Array(60);
|
|
434
|
-
uniformScratchU32 = new Uint32Array(this.uniformScratch.buffer, 176, 4);
|
|
394
|
+
// One 336-byte buffer serves all frame batches/meshes (84 floats including RTE lanes).
|
|
395
|
+
uniformScratch = new Float32Array(MESH_UNIFORM_FLOATS);
|
|
396
|
+
uniformScratchU32 = new Uint32Array(this.uniformScratch.buffer, MESH_FLAGS_BYTE_OFFSET, 4);
|
|
435
397
|
// What the last render() actually clipped, so the GPU picker can mirror it and
|
|
436
398
|
// section/crop-clipped geometry stays unpickable, not just invisible. Updated
|
|
437
399
|
// every render; read by pick()/pickRect(). null = nothing clipped that frame.
|
|
@@ -453,8 +415,8 @@ export class Renderer {
|
|
|
453
415
|
* are released first. The comment below advertises a `destroy()` + `init()`
|
|
454
416
|
* re-init flow, and the obvious device-loss auto-recovery is to call
|
|
455
417
|
* `init()` on the live instance — which, without this, silently orphaned
|
|
456
|
-
* two render pipelines, the picker, the post
|
|
457
|
-
* deviation pipelines
|
|
418
|
+
* two render pipelines, the picker, the post passes, the point-cloud and
|
|
419
|
+
* deviation pipelines and the overlay layer's glyph atlas, per
|
|
458
420
|
* recovery (#2448). Making the method self-safe is cheaper than trusting
|
|
459
421
|
* every future caller to remember.
|
|
460
422
|
*
|
|
@@ -486,7 +448,7 @@ export class Renderer {
|
|
|
486
448
|
this.initChain = run.then(() => undefined, () => undefined);
|
|
487
449
|
return run;
|
|
488
450
|
}
|
|
489
|
-
async initOnce(generation) {
|
|
451
|
+
async initOnce(generation, options = {}) {
|
|
490
452
|
// `pipeline` is the marker for "a previous init() completed": it is
|
|
491
453
|
// assigned unconditionally there and nulled by destroy().
|
|
492
454
|
if (this.pipeline !== null) {
|
|
@@ -504,16 +466,21 @@ export class Renderer {
|
|
|
504
466
|
// the generation and this body running is stamped with the generation
|
|
505
467
|
// that is clearing it. `deviceLostGeneration` deliberately keeps its
|
|
506
468
|
// stale value — it is only ever read alongside this flag.
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
//
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
469
|
+
if (options.clearDeviceLost !== false)
|
|
470
|
+
this.deviceLost = false;
|
|
471
|
+
// Subscribe before init so a loss during the first frames is not missed.
|
|
472
|
+
const initializingDevice = this.device;
|
|
473
|
+
initializingDevice.onDeviceLost((info) => {
|
|
474
|
+
// Ignore a delayed loss from a wrapper recovery already replaced.
|
|
475
|
+
if (this.device !== initializingDevice)
|
|
476
|
+
return;
|
|
477
|
+
this.handleDeviceLost(info, { fromDevicePromise: true });
|
|
478
|
+
});
|
|
479
|
+
await initializingDevice.init(this.canvas);
|
|
513
480
|
// A `destroy()` (or a newer `init()`) landed while we were parked on the
|
|
514
481
|
// device. Everything below allocates a full GPU stack — two pipelines,
|
|
515
|
-
// the picker, the
|
|
516
|
-
//
|
|
482
|
+
// the picker, the point-cloud and deviation pipelines, the overlay
|
|
483
|
+
// glyph atlas — and this aborted
|
|
517
484
|
// path runs no second teardown, so all of it would be orphaned outright
|
|
518
485
|
// (#2465). `markReady()`'s generation check is not enough on its own: it
|
|
519
486
|
// withholds the readiness PUBLICATION, not the allocation. Release the
|
|
@@ -523,15 +490,15 @@ export class Renderer {
|
|
|
523
490
|
this.device.destroy();
|
|
524
491
|
return;
|
|
525
492
|
}
|
|
526
|
-
//
|
|
527
|
-
//
|
|
528
|
-
//
|
|
529
|
-
|
|
493
|
+
// Size the buffer to the element's device pixels, the same sizing every
|
|
494
|
+
// frame applies (#5383); an unlaid-out canvas keeps its attributes. Both
|
|
495
|
+
// are clamped to the GPU's max 2D texture dimension so the initial
|
|
496
|
+
// pipeline allocations can't overflow on tall/wide layouts.
|
|
530
497
|
const maxDim = this.device.getMaxTextureDimension();
|
|
531
|
-
const
|
|
532
|
-
|
|
533
|
-
const width = Math.min(
|
|
534
|
-
const height = Math.min(
|
|
498
|
+
const measured = measureDrawingBuffer(this.canvas, maxDim);
|
|
499
|
+
this.pixelRatio = measured?.pixelRatio ?? 1;
|
|
500
|
+
const width = Math.max(1, Math.min(measured?.width ?? this.canvas.width, maxDim));
|
|
501
|
+
const height = Math.max(1, Math.min(measured?.height ?? this.canvas.height, maxDim));
|
|
535
502
|
// Set pixel dimensions if not already set, or if we clamped them down
|
|
536
503
|
if (!this.canvas.width || !this.canvas.height || this.canvas.width !== width || this.canvas.height !== height) {
|
|
537
504
|
this.canvas.width = width;
|
|
@@ -541,25 +508,11 @@ export class Renderer {
|
|
|
541
508
|
this.picker = new Picker(this.device, width, height);
|
|
542
509
|
this.overlays.init(this.device.getDevice(), this.device.getFormat(), this.pipeline.getSampleCount());
|
|
543
510
|
this.referenceImages.init(this.device.getDevice(), this.device.getFormat(), this.pipeline.getSampleCount());
|
|
544
|
-
|
|
545
|
-
// depth TEXTURE_BINDING), rendering still works without post-processing.
|
|
546
|
-
try {
|
|
547
|
-
this.postProcessor = new PostProcessor(this.device, {
|
|
548
|
-
enableContactShading: true,
|
|
549
|
-
contactRadius: 1.0,
|
|
550
|
-
contactIntensity: 0.3,
|
|
551
|
-
}, this.pipeline.getSampleCount());
|
|
552
|
-
}
|
|
553
|
-
catch (e) {
|
|
554
|
-
console.warn('[Renderer] PostProcessor init failed (post-processing disabled):', e);
|
|
555
|
-
this.postProcessor = null;
|
|
556
|
-
}
|
|
557
|
-
this.pointCloudRenderer = new PointCloudRenderer(this.device.getDevice(), this.device.getFormat(), 'depth24plus-stencil8', this.pipeline.getSampleCount());
|
|
511
|
+
this.pointCloudRenderer = new PointCloudRenderer(this.device.getDevice(), this.device.getFormat(), 'depth24plus-stencil8', this.pipeline.getSampleCount(), this.pointCloudNextHandleId);
|
|
558
512
|
// Compute pipeline for the BIM↔scan deviation heatmap. Lazily
|
|
559
513
|
// owns the per-triangle BVH GPU buffers; idle until the first
|
|
560
514
|
// `computeDeviations` call.
|
|
561
515
|
this.deviationComputer.init(this.device.getDevice());
|
|
562
|
-
this.edlPass = new EdlPass(this.device, this.pipeline.getSampleCount());
|
|
563
516
|
this.camera.setAspect(width / height);
|
|
564
517
|
// Update picking manager with initialized picker
|
|
565
518
|
this.pickingManager.setPicker(this.picker);
|
|
@@ -587,7 +540,8 @@ export class Renderer {
|
|
|
587
540
|
// from the GPU click-pick provider above, since raycastScene* is
|
|
588
541
|
// synchronous CPU code while pick() is an async GPU readback.
|
|
589
542
|
this.raycastEngine.setPointCloudProvider(() => this.pointCloudRenderer?.getRayQuerySources() ?? []);
|
|
590
|
-
|
|
543
|
+
if (options.publishReady !== false)
|
|
544
|
+
this.markReady(generation);
|
|
591
545
|
}
|
|
592
546
|
/**
|
|
593
547
|
* Resolves once `init()` has finished and the GPU device + point-cloud
|
|
@@ -719,10 +673,34 @@ export class Renderer {
|
|
|
719
673
|
isDeviceLost() {
|
|
720
674
|
return this.deviceLost;
|
|
721
675
|
}
|
|
722
|
-
|
|
723
|
-
|
|
676
|
+
recoverDevice() {
|
|
677
|
+
return recoverRendererDevice(this);
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* `onLossDetected` for every `runGuardedGpuUpload` call below (#4885):
|
|
681
|
+
* a synchronous Safari-style `DOMException` (issue #2229) caught OUTSIDE
|
|
682
|
+
* `render()` never otherwise reaches `handleDeviceLost` — only the async
|
|
683
|
+
* `device.lost` promise and `render()`'s own catch do — so without this,
|
|
684
|
+
* `isDeviceLost()` would keep answering false and every later upload on
|
|
685
|
+
* this path would keep failing against the same dead device.
|
|
686
|
+
*/
|
|
687
|
+
reportUploadLoss(error) {
|
|
688
|
+
this.handleDeviceLost({
|
|
689
|
+
message: error instanceof Error ? error.message : String(error),
|
|
690
|
+
reason: 'gpu-upload-exception',
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
handleDeviceLost(info, options = {}) {
|
|
694
|
+
// Latched: only the current device's `device.lost` signal is a replacement loss; anything else is a duplicate report.
|
|
695
|
+
if (this.deviceLost) {
|
|
696
|
+
if (options.fromDevicePromise)
|
|
697
|
+
this.deviceLossSequence++;
|
|
724
698
|
return;
|
|
699
|
+
}
|
|
700
|
+
this.deviceLossSequence++;
|
|
725
701
|
this.deviceLost = true;
|
|
702
|
+
cancelRendererColorFrame(this);
|
|
703
|
+
this.recovery.lostReferenceImages = this.referenceImages.hasImages();
|
|
726
704
|
this.referenceImages.destroy();
|
|
727
705
|
this.deviceLostGeneration = this.initGeneration;
|
|
728
706
|
this.deviceLostInfo = info;
|
|
@@ -781,11 +759,13 @@ export class Renderer {
|
|
|
781
759
|
// regions really do have such a source on a HEALTHY device: the outer
|
|
782
760
|
// one runs `scene.restoreAllEvicted()` for capture frames, the encode
|
|
783
761
|
// one builds visibility sub-batches through
|
|
784
|
-
// `scene.getOrCreatePartialBatch()`, and both
|
|
785
|
-
//
|
|
786
|
-
//
|
|
787
|
-
//
|
|
788
|
-
//
|
|
762
|
+
// `scene.getOrCreatePartialBatch()`, and both merge geometry into
|
|
763
|
+
// fresh typed arrays, whose allocation throws a plain `RangeError`
|
|
764
|
+
// ("Array buffer allocation failed") under host memory pressure.
|
|
765
|
+
// (Before #5429 the GPU upload itself added a second source, the
|
|
766
|
+
// mapped-at-creation `createBuffer` RangeError; uploads now go through
|
|
767
|
+
// `createStaticGpuBuffer`, which cannot raise it.) Latching there
|
|
768
|
+
// would kill the viewport for a failure whose blast radius should be one frame, and
|
|
789
769
|
// would raise a false "graphics device was lost" toast plus false
|
|
790
770
|
// `device_lost` telemetry on top.
|
|
791
771
|
//
|
|
@@ -894,28 +874,16 @@ export class Renderer {
|
|
|
894
874
|
* chunks will arrive (currently a no-op but kept for symmetry).
|
|
895
875
|
*/
|
|
896
876
|
beginPointCloudStream(meta) {
|
|
897
|
-
|
|
898
|
-
throw new Error('Renderer not initialized. Call init() first.');
|
|
899
|
-
}
|
|
900
|
-
return this.pointCloudRenderer.beginAsset(meta);
|
|
877
|
+
return beginPointCloudStreamImpl(this, meta);
|
|
901
878
|
}
|
|
902
879
|
appendPointCloudChunk(handle, chunk) {
|
|
903
|
-
|
|
904
|
-
return;
|
|
905
|
-
this.pointCloudRenderer.appendChunk(handle, chunk);
|
|
906
|
-
this.modelBoundsTracker.expandForPointClouds();
|
|
907
|
-
this.camera.setSceneBounds(this.modelBounds);
|
|
908
|
-
this.requestRender();
|
|
880
|
+
appendPointCloudChunkImpl(this, handle, chunk);
|
|
909
881
|
}
|
|
910
882
|
endPointCloudStream(handle) {
|
|
911
|
-
this
|
|
912
|
-
this.requestRender();
|
|
883
|
+
endPointCloudStreamImpl(this, handle);
|
|
913
884
|
}
|
|
914
885
|
removePointCloudAsset(handle) {
|
|
915
|
-
this
|
|
916
|
-
// Bounds may have shrunk — recompute from scratch so fit-to-view
|
|
917
|
-
// and section-plane sliders see fresh extents.
|
|
918
|
-
this.refreshPlacementBounds();
|
|
886
|
+
removePointCloudAssetImpl(this, handle);
|
|
919
887
|
}
|
|
920
888
|
/**
|
|
921
889
|
* Reassign a streamed point-cloud's expressId after upload. Use
|
|
@@ -948,12 +916,28 @@ export class Renderer {
|
|
|
948
916
|
this.clearCaches();
|
|
949
917
|
this.refreshPlacementBounds();
|
|
950
918
|
}
|
|
919
|
+
/** Absolute workspace yaw about the render-frame pivot; GPU-instanced only (#4890). Skips invalidation on a no-op (#4890 review). */
|
|
920
|
+
setModelRotation(modelIndex, angle, pivot) {
|
|
921
|
+
const changed = this.scene.setModelRotation(modelIndex, angle, pivot);
|
|
922
|
+
if (!changed)
|
|
923
|
+
return false;
|
|
924
|
+
this.clearCaches();
|
|
925
|
+
this.refreshPlacementBounds();
|
|
926
|
+
return true;
|
|
927
|
+
}
|
|
951
928
|
/** Streamed clouds are addressed by durable asset handle. */
|
|
952
929
|
setPointCloudTranslation(handle, translation) {
|
|
953
930
|
this.pointCloudRenderer?.setAssetTranslation(handle, translation);
|
|
954
931
|
this.clearCaches();
|
|
955
932
|
this.refreshPlacementBounds();
|
|
956
933
|
}
|
|
934
|
+
/** Toggle one resident streamed scan without releasing its GPU buffers. */
|
|
935
|
+
setPointCloudVisibility(handle, visible) {
|
|
936
|
+
if (!this.pointCloudRenderer?.setAssetVisible(handle, visible))
|
|
937
|
+
return;
|
|
938
|
+
this.clearCaches();
|
|
939
|
+
this.refreshPlacementBounds();
|
|
940
|
+
}
|
|
957
941
|
refreshPlacementBounds() {
|
|
958
942
|
this.modelBoundsTracker.recompute();
|
|
959
943
|
this.camera.setSceneBounds(this.modelBounds);
|
|
@@ -1012,22 +996,26 @@ export class Renderer {
|
|
|
1012
996
|
* @param geometry - Either a GeometryResult from geometry.process() or an array of MeshData
|
|
1013
997
|
*/
|
|
1014
998
|
loadGeometry(geometry) {
|
|
1015
|
-
if (
|
|
999
|
+
if (this.isDeviceLost())
|
|
1000
|
+
return { ok: false, reason: 'device-lost' }; // #4885: zombie device stays "initialized"
|
|
1001
|
+
if (!this.device.isInitialized() || !this.pipeline)
|
|
1016
1002
|
throw new Error('Renderer not initialized. Call init() first.');
|
|
1017
|
-
}
|
|
1018
1003
|
const meshes = Array.isArray(geometry) ? geometry : geometry.meshes;
|
|
1019
1004
|
if (meshes.length === 0) {
|
|
1020
1005
|
console.warn('[Renderer] loadGeometry called with empty mesh array');
|
|
1021
|
-
return;
|
|
1006
|
+
return { ok: true, value: undefined };
|
|
1022
1007
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1008
|
+
const pipeline = this.pipeline;
|
|
1009
|
+
return runGuardedGpuUpload(() => this.isDeviceLost(), () => {
|
|
1010
|
+
// Use batched rendering for optimal performance
|
|
1011
|
+
const device = this.device.getDevice();
|
|
1012
|
+
this.scene.appendToBatches(meshes, device, pipeline, false);
|
|
1013
|
+
// Calculate and store model bounds for fitToView
|
|
1014
|
+
this.modelBoundsTracker.updateFromMeshes(meshes, (index) => this.scene.getModelTranslation(index));
|
|
1015
|
+
console.log(`[Renderer] Loaded ${meshes.length} meshes`);
|
|
1016
|
+
// Update camera scene bounds for tight orthographic near/far planes
|
|
1017
|
+
this.camera.setSceneBounds(this.modelBounds);
|
|
1018
|
+
}, (error) => this.reportUploadLoss(error));
|
|
1031
1019
|
}
|
|
1032
1020
|
/**
|
|
1033
1021
|
* Add multiple meshes to the scene (convenience method for streaming)
|
|
@@ -1036,17 +1024,20 @@ export class Renderer {
|
|
|
1036
1024
|
* @param isStreaming - If true, throttles batch rebuilding for better streaming performance
|
|
1037
1025
|
*/
|
|
1038
1026
|
addMeshes(meshes, isStreaming = false) {
|
|
1027
|
+
if (this.isDeviceLost())
|
|
1028
|
+
return { ok: false, reason: 'device-lost' };
|
|
1039
1029
|
if (!this.device.isInitialized() || !this.pipeline) {
|
|
1040
1030
|
throw new Error('Renderer not initialized. Call init() first.');
|
|
1041
1031
|
}
|
|
1042
1032
|
if (meshes.length === 0)
|
|
1043
|
-
return;
|
|
1044
|
-
const
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1033
|
+
return { ok: true, value: undefined };
|
|
1034
|
+
const pipeline = this.pipeline;
|
|
1035
|
+
return runGuardedGpuUpload(() => this.isDeviceLost(), () => {
|
|
1036
|
+
const device = this.device.getDevice();
|
|
1037
|
+
this.scene.appendToBatches(meshes, device, pipeline, isStreaming);
|
|
1038
|
+
this.modelBoundsTracker.updateFromMeshes(meshes, (index) => this.scene.getModelTranslation(index));
|
|
1039
|
+
this.camera.setSceneBounds(this.modelBounds);
|
|
1040
|
+
}, (error) => this.reportUploadLoss(error));
|
|
1050
1041
|
}
|
|
1051
1042
|
/**
|
|
1052
1043
|
* Fit camera to view all loaded geometry
|
|
@@ -1074,50 +1065,46 @@ export class Renderer {
|
|
|
1074
1065
|
*/
|
|
1075
1066
|
addMesh(mesh) {
|
|
1076
1067
|
if (!this.pipeline)
|
|
1077
|
-
return;
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
resource: { buffer: mesh.uniformBuffer },
|
|
1093
|
-
},
|
|
1094
|
-
],
|
|
1095
|
-
});
|
|
1096
|
-
}
|
|
1068
|
+
return { ok: true, value: undefined };
|
|
1069
|
+
const pipeline = this.pipeline;
|
|
1070
|
+
const outcome = (!mesh.uniformBuffer && !this.isDeviceLost() && this.device.isInitialized())
|
|
1071
|
+
? runGuardedGpuUpload(() => this.isDeviceLost(), () => {
|
|
1072
|
+
const device = this.device.getDevice();
|
|
1073
|
+
mesh.uniformBuffer = device.createBuffer({
|
|
1074
|
+
size: pipeline.getUniformBufferSize(),
|
|
1075
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
1076
|
+
});
|
|
1077
|
+
mesh.bindGroup = device.createBindGroup({
|
|
1078
|
+
layout: pipeline.getBindGroupLayout(),
|
|
1079
|
+
entries: [{ binding: 0, resource: { buffer: mesh.uniformBuffer } }],
|
|
1080
|
+
});
|
|
1081
|
+
}, (error) => this.reportUploadLoss(error))
|
|
1082
|
+
: { ok: true, value: undefined };
|
|
1097
1083
|
this.scene.addMesh(mesh);
|
|
1084
|
+
return outcome;
|
|
1098
1085
|
}
|
|
1099
1086
|
/**
|
|
1100
1087
|
* Ensure all meshes have GPU resources (call after adding meshes if pipeline wasn't ready)
|
|
1101
1088
|
*/
|
|
1102
1089
|
ensureMeshResources() {
|
|
1103
1090
|
if (!this.pipeline || !this.device.isInitialized())
|
|
1104
|
-
return;
|
|
1105
|
-
const
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
}
|
|
1091
|
+
return { ok: true, value: undefined };
|
|
1092
|
+
const pipeline = this.pipeline;
|
|
1093
|
+
return runGuardedGpuUpload(() => this.isDeviceLost(), () => {
|
|
1094
|
+
const device = this.device.getDevice();
|
|
1095
|
+
for (const mesh of this.scene.getMeshes()) {
|
|
1096
|
+
if (!mesh.uniformBuffer) {
|
|
1097
|
+
mesh.uniformBuffer = device.createBuffer({
|
|
1098
|
+
size: pipeline.getUniformBufferSize(),
|
|
1099
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
1100
|
+
});
|
|
1101
|
+
mesh.bindGroup = device.createBindGroup({
|
|
1102
|
+
layout: pipeline.getBindGroupLayout(),
|
|
1103
|
+
entries: [{ binding: 0, resource: { buffer: mesh.uniformBuffer } }],
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1119
1106
|
}
|
|
1120
|
-
}
|
|
1107
|
+
}, (error) => this.reportUploadLoss(error));
|
|
1121
1108
|
}
|
|
1122
1109
|
/**
|
|
1123
1110
|
* Get model bounds (used for section planes, fitToView, etc.)
|
|
@@ -1150,71 +1137,17 @@ export class Renderer {
|
|
|
1150
1137
|
* collector drops them.
|
|
1151
1138
|
*/
|
|
1152
1139
|
shadowOccluderBatches(options, device, hasVisibilityFiltering) {
|
|
1153
|
-
|
|
1154
|
-
if (!hasVisibilityFiltering) {
|
|
1155
|
-
for (const batch of all)
|
|
1156
|
-
this.noteShadowOccluderResidency(batch);
|
|
1157
|
-
return all;
|
|
1158
|
-
}
|
|
1159
|
-
const pipeline = this.pipeline;
|
|
1160
|
-
const out = [];
|
|
1161
|
-
for (const batch of all) {
|
|
1162
|
-
const vis = classifyBatchVisibility(batch.expressIds, options.hiddenIds, options.isolatedIds);
|
|
1163
|
-
if (vis.kind === 'none')
|
|
1164
|
-
continue; // fully hidden → does not cast
|
|
1165
|
-
if (vis.kind === 'all') {
|
|
1166
|
-
this.noteShadowOccluderResidency(batch);
|
|
1167
|
-
out.push(batch); // fully visible → its own buffers
|
|
1168
|
-
continue;
|
|
1169
|
-
}
|
|
1170
|
-
// Partially hidden. Transparent parents don't cast (collector's alpha
|
|
1171
|
-
// filter) — skip rather than build a wasted, divergent-key clone.
|
|
1172
|
-
if (batch.color[3] < DEFAULT_MIN_CAST_ALPHA)
|
|
1173
|
-
continue;
|
|
1174
|
-
// The partial sub-batch is built from the PARENT's CPU meshData, so a
|
|
1175
|
-
// cold parent yields nothing until it is restored. Queue that restore
|
|
1176
|
-
// here too: the colour pass only queues it for parents inside its own
|
|
1177
|
-
// frustum, but an up-sun occluder behind the camera still has to cast.
|
|
1178
|
-
this.noteShadowOccluderResidency(batch);
|
|
1179
|
-
// Opaque partial: reuse the colour pass's cached sub-batch. The key is
|
|
1180
|
-
// visibility-content-independent; `_partialBatchEpoch` invalidates it on
|
|
1181
|
-
// any hide/isolate or override change, so the clone is always current.
|
|
1182
|
-
// Without a pipeline the renderer isn't drawing, so skip (the shadow
|
|
1183
|
-
// pass won't run either); casting the whole parent would be wrong.
|
|
1184
|
-
if (!pipeline)
|
|
1185
|
-
continue;
|
|
1186
|
-
const sub = this.scene.getOrCreatePartialBatch(`${batch.colorKey}:${batch.id}`, batch.colorKey, vis.visibleIds, device, pipeline, this._partialBatchEpoch);
|
|
1187
|
-
// A cold parent yields an empty partial (its residency restore is queued
|
|
1188
|
-
// above); skip this frame — the collector drops zero-index draws anyway,
|
|
1189
|
-
// and the subset casts once resident.
|
|
1190
|
-
if (sub && sub.indexCount > 0)
|
|
1191
|
-
out.push(sub);
|
|
1192
|
-
}
|
|
1193
|
-
return out;
|
|
1140
|
+
return shadowOccluderBatches(this.scene, options, device, hasVisibilityFiltering, this.pipeline, this._partialBatchEpoch);
|
|
1194
1141
|
}
|
|
1195
|
-
/**
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
* reads these batches' buffers, but that read did not count as usage, so an
|
|
1199
|
-
* up-sun occluder outside the colour frustum aged into an eviction candidate.
|
|
1200
|
-
* Transparent batches never cast (the collector's alpha filter), so their
|
|
1201
|
-
* residency is irrelevant here.
|
|
1202
|
-
*/
|
|
1203
|
-
noteShadowOccluderResidency(batch) {
|
|
1204
|
-
if (batch.color[3] < DEFAULT_MIN_CAST_ALPHA)
|
|
1205
|
-
return;
|
|
1206
|
-
if (batch.gpuResident === false) {
|
|
1207
|
-
this.scene.requestBatchResidency(batch);
|
|
1208
|
-
}
|
|
1209
|
-
else {
|
|
1210
|
-
this.scene.recordBatchDrawn(batch);
|
|
1211
|
-
}
|
|
1142
|
+
/** Guarded entry point (#4885) for the unguarded body below. */
|
|
1143
|
+
createMeshFromData(meshData) {
|
|
1144
|
+
return runGuardedGpuUpload(() => this.isDeviceLost(), () => this.createMeshFromDataUnguarded(meshData), (error) => this.reportUploadLoss(error));
|
|
1212
1145
|
}
|
|
1213
1146
|
/**
|
|
1214
1147
|
* Create a GPU Mesh from MeshData (lazy creation for selection highlighting)
|
|
1215
1148
|
* This is called on-demand when a mesh is selected, avoiding 2x buffer creation during streaming
|
|
1216
1149
|
*/
|
|
1217
|
-
|
|
1150
|
+
createMeshFromDataUnguarded(meshData) {
|
|
1218
1151
|
if (!this.device.isInitialized())
|
|
1219
1152
|
return;
|
|
1220
1153
|
const device = this.device.getDevice();
|
|
@@ -1223,18 +1156,17 @@ export class Renderer {
|
|
|
1223
1156
|
const interleaved = new Float32Array(interleavedRaw);
|
|
1224
1157
|
const interleavedU32 = new Uint32Array(interleavedRaw);
|
|
1225
1158
|
// Build this individual mesh (selection highlight + GPU object-id picker)
|
|
1226
|
-
// in
|
|
1159
|
+
// in the same small local frame as its source batch.
|
|
1227
1160
|
// CRITICAL: replicate the BATCH's exact two-step f32 path so the highlight
|
|
1228
1161
|
// is bit-coincident with its source surface (no z-fight, no depth bias):
|
|
1229
1162
|
// batch stores s = f32(local + (origin - sharedOrigin)) [merge]
|
|
1230
|
-
// batch shader
|
|
1231
|
-
// We
|
|
1232
|
-
//
|
|
1163
|
+
// batch shader RTE = sharedOrigin - eye + s [draw]
|
|
1164
|
+
// We retain `s` and the canonical origin separately. When there is no
|
|
1165
|
+
// shared origin, retain the piece origin instead of folding it in.
|
|
1233
1166
|
const o = meshData.origin;
|
|
1234
|
-
const so = this.scene.getSharedFrameOrigin(meshData.modelIndex);
|
|
1167
|
+
const so = this.scene.getSharedFrameOrigin(meshData.modelIndex, meshData);
|
|
1235
1168
|
const ox = o ? o[0] : 0, oy = o ? o[1] : 0, oz = o ? o[2] : 0;
|
|
1236
1169
|
const fr = Math.fround;
|
|
1237
|
-
const sox = so ? fr(so[0]) : null, soy = so ? fr(so[1]) : 0, soz = so ? fr(so[2]) : 0;
|
|
1238
1170
|
const dx = so ? (ox - so[0]) : ox, dy = so ? (oy - so[1]) : oy, dz = so ? (oz - so[2]) : oz;
|
|
1239
1171
|
// Quantized batches (issue #1682 phase 6) render lattice-snapped
|
|
1240
1172
|
// positions: the shader's quantMin + q*step is exactly the lattice
|
|
@@ -1251,9 +1183,9 @@ export class Renderer {
|
|
|
1251
1183
|
for (let i = 0; i < vertexCount; i++) {
|
|
1252
1184
|
const base = i * 7;
|
|
1253
1185
|
const posBase = i * 3;
|
|
1254
|
-
interleaved[base] = so ?
|
|
1255
|
-
interleaved[base + 1] = so ?
|
|
1256
|
-
interleaved[base + 2] = so ?
|
|
1186
|
+
interleaved[base] = so ? snap(fr(p[posBase] + dx)) : snap(p[posBase]);
|
|
1187
|
+
interleaved[base + 1] = so ? snap(fr(p[posBase + 1] + dy)) : snap(p[posBase + 1]);
|
|
1188
|
+
interleaved[base + 2] = so ? snap(fr(p[posBase + 2] + dz)) : snap(p[posBase + 2]);
|
|
1257
1189
|
const hasNormals = meshData.normals.length > 0;
|
|
1258
1190
|
interleaved[base + 3] = hasNormals ? meshData.normals[posBase] : 0;
|
|
1259
1191
|
interleaved[base + 4] = hasNormals ? meshData.normals[posBase + 1] : 0;
|
|
@@ -1283,7 +1215,9 @@ export class Renderer {
|
|
|
1283
1215
|
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
|
|
1284
1216
|
});
|
|
1285
1217
|
device.queue.writeBuffer(indexBuffer, 0, meshData.indices);
|
|
1286
|
-
//
|
|
1218
|
+
// Keep the hydrated mesh in its decoded local frame. Folding this
|
|
1219
|
+
// origin back into f32 vertices used to make selection/highlight the
|
|
1220
|
+
// only mesh path that lost centimetres at national-grid coordinates.
|
|
1287
1221
|
// Flagged `hydrated` so it can be freed when its entity leaves the
|
|
1288
1222
|
// selection — these duplicate geometry already drawn by a batch and would
|
|
1289
1223
|
// otherwise accumulate + double-draw (see Scene.disposeHydratedMeshesExcept).
|
|
@@ -1298,7 +1232,14 @@ export class Renderer {
|
|
|
1298
1232
|
vertexBuffer,
|
|
1299
1233
|
indexBuffer,
|
|
1300
1234
|
indexCount: meshData.indices.length,
|
|
1301
|
-
transform:
|
|
1235
|
+
transform: (() => {
|
|
1236
|
+
const transform = MathUtils.identity();
|
|
1237
|
+
transform.m[12] = so ? so[0] : ox;
|
|
1238
|
+
transform.m[13] = so ? so[1] : oy;
|
|
1239
|
+
transform.m[14] = so ? so[2] : oz;
|
|
1240
|
+
return transform;
|
|
1241
|
+
})(),
|
|
1242
|
+
rteOrigin: so ? [so[0], so[1], so[2]] : [ox, oy, oz],
|
|
1302
1243
|
color: meshData.color,
|
|
1303
1244
|
hydrated: true,
|
|
1304
1245
|
});
|
|
@@ -1416,6 +1357,7 @@ export class Renderer {
|
|
|
1416
1357
|
* rejecting pipeline creation) batches stay on the f32 path.
|
|
1417
1358
|
*/
|
|
1418
1359
|
async enableQuantizedBatches() {
|
|
1360
|
+
this.recovery.quantizedBatchesRequested = true;
|
|
1419
1361
|
if (!this.pipeline)
|
|
1420
1362
|
return false;
|
|
1421
1363
|
const ok = await this.pipeline.ensureQuantizedPipelines();
|
|
@@ -1457,6 +1399,7 @@ export class Renderer {
|
|
|
1457
1399
|
// emit a stream of validation errors. Stay quiet until re-init.
|
|
1458
1400
|
if (this.deviceLost) {
|
|
1459
1401
|
this._renderSkipCount++;
|
|
1402
|
+
cancelRendererColorFrame(this);
|
|
1460
1403
|
return;
|
|
1461
1404
|
}
|
|
1462
1405
|
try {
|
|
@@ -1468,6 +1411,7 @@ export class Renderer {
|
|
|
1468
1411
|
// as "did not fail" — see `frameContainedThrow`.
|
|
1469
1412
|
if (!this.frameContainedThrow)
|
|
1470
1413
|
this.consecutiveDegradedFrames = 0;
|
|
1414
|
+
retryRendererColorFrame(this, () => this.requestRender());
|
|
1471
1415
|
}
|
|
1472
1416
|
catch (error) {
|
|
1473
1417
|
// Safari (26.5) reports device loss SYNCHRONOUSLY: a call against a
|
|
@@ -1483,6 +1427,7 @@ export class Renderer {
|
|
|
1483
1427
|
// host down with us".
|
|
1484
1428
|
this._renderSkipCount++;
|
|
1485
1429
|
this.containFrameThrow(error, 'frame');
|
|
1430
|
+
cancelRendererColorFrame(this);
|
|
1486
1431
|
}
|
|
1487
1432
|
}
|
|
1488
1433
|
/**
|
|
@@ -1494,23 +1439,16 @@ export class Renderer {
|
|
|
1494
1439
|
this._renderSkipCount++;
|
|
1495
1440
|
return;
|
|
1496
1441
|
}
|
|
1497
|
-
//
|
|
1498
|
-
//
|
|
1499
|
-
|
|
1500
|
-
//
|
|
1501
|
-
|
|
1502
|
-
const rect = this.canvas.getBoundingClientRect();
|
|
1503
|
-
const maxDim = this.device.getMaxTextureDimension();
|
|
1504
|
-
const rawWidth = Math.max(1, Math.floor(rect.width));
|
|
1505
|
-
const widthAligned = Math.max(64, Math.floor(rawWidth / 64) * 64);
|
|
1506
|
-
const width = Math.min(widthAligned, Math.floor(maxDim / 64) * 64);
|
|
1507
|
-
const rawHeight = Math.max(1, Math.floor(rect.height));
|
|
1508
|
-
const height = Math.min(rawHeight, maxDim);
|
|
1509
|
-
// Skip rendering if canvas is too small
|
|
1510
|
-
if (width < 64 || height < 10) {
|
|
1442
|
+
// Drawing buffer = the element's device-pixel size (capped ratio, clamped
|
|
1443
|
+
// to the GPU's max texture dimension); see computeDrawingBufferSize (#5383).
|
|
1444
|
+
const measured = measureDrawingBuffer(this.canvas, this.device.getMaxTextureDimension());
|
|
1445
|
+
// Skip rendering while the canvas is collapsed or too small.
|
|
1446
|
+
if (!measured || measured.height < 10) {
|
|
1511
1447
|
this._renderSkipCount++;
|
|
1512
1448
|
return;
|
|
1513
1449
|
}
|
|
1450
|
+
const { width, height } = measured;
|
|
1451
|
+
this.pixelRatio = measured.pixelRatio;
|
|
1514
1452
|
// Update canvas pixel dimensions if needed
|
|
1515
1453
|
const dimensionsChanged = this.canvas.width !== width || this.canvas.height !== height;
|
|
1516
1454
|
if (dimensionsChanged) {
|
|
@@ -1534,6 +1472,13 @@ export class Renderer {
|
|
|
1534
1472
|
}
|
|
1535
1473
|
const device = this.device.getDevice();
|
|
1536
1474
|
const viewProj = this.camera.getViewProjMatrix().m;
|
|
1475
|
+
const relativeToEyeFrame = this.camera.getRelativeToEyeFrame();
|
|
1476
|
+
// Culling must share the translation-free RTE frame used by every GPU
|
|
1477
|
+
// pass. `sourceFrustum` is the f64 plane equivalent for the immutable
|
|
1478
|
+
// source-space BVH; render-time boxes stay eye-relative below.
|
|
1479
|
+
const rteCullFrustum = rteFrustum(relativeToEyeFrame);
|
|
1480
|
+
const rteCullCamera = relativeToEyeFrame.getCameraWorld();
|
|
1481
|
+
const sourceCullFrustum = sourceFrustumFromRte(rteCullFrustum, rteCullCamera);
|
|
1537
1482
|
// Frame stats (issue #1682): geometry draw calls + per-frame cull
|
|
1538
1483
|
// outcomes, snapshotted into _lastFrameStats before queue.submit.
|
|
1539
1484
|
let frameDrawCalls = 0;
|
|
@@ -1573,11 +1518,8 @@ export class Renderer {
|
|
|
1573
1518
|
const edgeIntensity = Math.min(3.0, Math.max(0.0, visualEnhancement.edgeContrast.intensity));
|
|
1574
1519
|
const edgeEnabledU32 = edgeEnabled ? 1 : 0;
|
|
1575
1520
|
const edgeIntensityMilliU32 = Math.round(edgeIntensity * 1000);
|
|
1576
|
-
|
|
1577
|
-
const
|
|
1578
|
-
&& visualEnhancement.separationLines.enabled
|
|
1579
|
-
&& visualEnhancement.separationLines.quality !== 'off';
|
|
1580
|
-
const needsObjectIdPass = contactEnabled || separationEnabled;
|
|
1521
|
+
// Only the separation lines read the object-id attachment after the pass.
|
|
1522
|
+
const needsObjectIdPass = livePostEffects(visualEnhancement, effectsLive).separationLines;
|
|
1581
1523
|
// Check if visibility filtering is active
|
|
1582
1524
|
const hasHiddenFilter = options.hiddenIds && options.hiddenIds.size > 0;
|
|
1583
1525
|
const hasIsolatedFilter = options.isolatedIds !== null && options.isolatedIds !== undefined;
|
|
@@ -1685,8 +1627,7 @@ export class Renderer {
|
|
|
1685
1627
|
// Frustum culling (if enabled and spatial index available)
|
|
1686
1628
|
if (options.enableFrustumCulling && options.spatialIndex) {
|
|
1687
1629
|
try {
|
|
1688
|
-
const
|
|
1689
|
-
const visibleIds = new Set(options.spatialIndex.queryFrustum(frustum));
|
|
1630
|
+
const visibleIds = new Set(options.spatialIndex.queryFrustum(sourceCullFrustum));
|
|
1690
1631
|
meshes = meshes.filter(mesh => visibleIds.has(mesh.expressId));
|
|
1691
1632
|
}
|
|
1692
1633
|
catch (error) {
|
|
@@ -1727,12 +1668,15 @@ export class Renderer {
|
|
|
1727
1668
|
}
|
|
1728
1669
|
return; // Skip this frame, context will be reconfigured next frame
|
|
1729
1670
|
}
|
|
1671
|
+
let colorCapture = null;
|
|
1672
|
+
let colorReadback = null;
|
|
1730
1673
|
try {
|
|
1731
1674
|
const clearColor = options.clearColor
|
|
1732
1675
|
? (Array.isArray(options.clearColor)
|
|
1733
1676
|
? { r: options.clearColor[0], g: options.clearColor[1], b: options.clearColor[2], a: options.clearColor[3] }
|
|
1734
1677
|
: options.clearColor)
|
|
1735
1678
|
: { r: 0.1, g: 0.1, b: 0.1, a: 1 };
|
|
1679
|
+
colorCapture = beginRendererColorFrameCapture(this, currentTexture, this.canvas.width, this.canvas.height, this.device.getFormat());
|
|
1736
1680
|
const textureView = currentTexture.createView();
|
|
1737
1681
|
const objectIdView = this.pipeline.getObjectIdTextureView();
|
|
1738
1682
|
// Separate meshes into opaque and transparent
|
|
@@ -1806,6 +1750,8 @@ export class Renderer {
|
|
|
1806
1750
|
for (const mesh of allMeshes) {
|
|
1807
1751
|
if (mesh.uniformBuffer) {
|
|
1808
1752
|
meshBuf.set(viewProj, 0);
|
|
1753
|
+
relativeToEyeFrame.packUniforms(meshBuf, MESH_UNIFORM_OFFSET.rteViewProj);
|
|
1754
|
+
packRteCameraOrigin(relativeToEyeFrame, meshBuf);
|
|
1809
1755
|
meshBuf.set(mesh.transform.m, 16);
|
|
1810
1756
|
// Check if mesh is selected (single or multi-selection)
|
|
1811
1757
|
// For multi-model support: also check modelIndex if provided
|
|
@@ -1846,6 +1792,13 @@ export class Renderer {
|
|
|
1846
1792
|
clipBit;
|
|
1847
1793
|
meshFlags[2] = edgeEnabledU32;
|
|
1848
1794
|
meshFlags[3] = edgeIntensityMilliU32;
|
|
1795
|
+
// Individual meshes retain their origin and share the RTE
|
|
1796
|
+
// fragment contract with colour batches.
|
|
1797
|
+
packRteFragmentSpace(relativeToEyeFrame, sectionPlaneData, options.clipBox, meshBuf);
|
|
1798
|
+
const meshOrigin = mesh.rteOrigin
|
|
1799
|
+
?? [mesh.transform.m[12], mesh.transform.m[13], mesh.transform.m[14]];
|
|
1800
|
+
relativeToEyeFrame.packDrawableOrigin(meshOrigin, meshBuf, MESH_UNIFORM_OFFSET.drawableDelta);
|
|
1801
|
+
meshFlags[0] |= MESH_FLAG_RTE_DRAWABLE;
|
|
1849
1802
|
device.queue.writeBuffer(mesh.uniformBuffer, 0, meshBuf);
|
|
1850
1803
|
}
|
|
1851
1804
|
}
|
|
@@ -1875,8 +1828,16 @@ export class Renderer {
|
|
|
1875
1828
|
else {
|
|
1876
1829
|
this.shadowPass.setResolution(resolution);
|
|
1877
1830
|
}
|
|
1878
|
-
|
|
1879
|
-
const
|
|
1831
|
+
// Fit shadows in the same eye-relative frame as vertices.
|
|
1832
|
+
const shadowEye = relativeToEyeFrame.getCameraWorld();
|
|
1833
|
+
const sourceBoundsMin = [bounds.min.x, bounds.min.y, bounds.min.z];
|
|
1834
|
+
const sourceBoundsMax = [bounds.max.x, bounds.max.y, bounds.max.z];
|
|
1835
|
+
const boundsMin = [
|
|
1836
|
+
bounds.min.x - shadowEye[0], bounds.min.y - shadowEye[1], bounds.min.z - shadowEye[2],
|
|
1837
|
+
];
|
|
1838
|
+
const boundsMax = [
|
|
1839
|
+
bounds.max.x - shadowEye[0], bounds.max.y - shadowEye[1], bounds.max.z - shadowEye[2],
|
|
1840
|
+
];
|
|
1880
1841
|
// Lateral shadow fit. AT REST: fit to the camera frustum
|
|
1881
1842
|
// clipped to the model (maintainer #1) so a small building on
|
|
1882
1843
|
// a large site keeps sharp shadows instead of spending the
|
|
@@ -1899,9 +1860,13 @@ export class Renderer {
|
|
|
1899
1860
|
aspect: this.canvas.height > 0 ? this.canvas.width / this.canvas.height : 1,
|
|
1900
1861
|
ortho: this.camera.getProjectionMode() === 'orthographic',
|
|
1901
1862
|
orthoHalfHeight: this.camera.getOrthoSize(),
|
|
1902
|
-
boundsMin,
|
|
1903
|
-
boundsMax,
|
|
1904
|
-
})
|
|
1863
|
+
boundsMin: sourceBoundsMin,
|
|
1864
|
+
boundsMax: sourceBoundsMax,
|
|
1865
|
+
})?.map((corner) => ({
|
|
1866
|
+
x: corner.x - shadowEye[0],
|
|
1867
|
+
y: corner.y - shadowEye[1],
|
|
1868
|
+
z: corner.z - shadowEye[2],
|
|
1869
|
+
}));
|
|
1905
1870
|
}
|
|
1906
1871
|
const sun = resolveEnvironment(options.environment).sunDirection;
|
|
1907
1872
|
const fit = fitSunLightMatrix({ sunDirection: sun, boundsMin, boundsMax, focusCorners });
|
|
@@ -1936,7 +1901,7 @@ export class Renderer {
|
|
|
1936
1901
|
box: options.clipBox?.enabled
|
|
1937
1902
|
? { min: options.clipBox.min, max: options.clipBox.max }
|
|
1938
1903
|
: null,
|
|
1939
|
-
});
|
|
1904
|
+
}, { cameraWorld: shadowEye });
|
|
1940
1905
|
// Shadow uniform: light matrix + sampling params. The kernel
|
|
1941
1906
|
// width follows the sun's angular size (physical, ~0.53°
|
|
1942
1907
|
// like Blender's Sun lamp). Bias scales with the kernel: a
|
|
@@ -1946,7 +1911,7 @@ export class Renderer {
|
|
|
1946
1911
|
const texelWorld = (2 * fit.orthoHalfWidth) / resolution;
|
|
1947
1912
|
const sunAngleDeg = shadowOpts.sunAngleDeg ?? 0.53;
|
|
1948
1913
|
const pcfRadius = Math.min(Math.max(sunAngleDeg * 3.0, 0.75), 8.0);
|
|
1949
|
-
const normalBias = texelWorld
|
|
1914
|
+
const normalBias = resolveShadowNormalBiasMetres(texelWorld, pcfRadius);
|
|
1950
1915
|
const s = this.shadowScratch;
|
|
1951
1916
|
s.set(fit.lightViewProj.m, 0);
|
|
1952
1917
|
s[16] = 1 / resolution; // texelSize
|
|
@@ -2082,7 +2047,7 @@ export class Renderer {
|
|
|
2082
2047
|
if (allBatchedMeshes.length > 0 || this.scene.getTexturedMeshes().length > 0 || this.scene.getInstancedTemplates().length > 0) {
|
|
2083
2048
|
// Frustum culling for batched meshes - skip entire batches outside the camera view
|
|
2084
2049
|
// This is the primary performance optimization for large models (200K+ meshes)
|
|
2085
|
-
const frustum =
|
|
2050
|
+
const frustum = rteCullFrustum;
|
|
2086
2051
|
// Contribution culling (issue #1682): skip batches whose world
|
|
2087
2052
|
// AABB projects below a pixel threshold. Disabled unless the
|
|
2088
2053
|
// caller opts in via options.contributionCull; the threshold is
|
|
@@ -2109,7 +2074,7 @@ export class Renderer {
|
|
|
2109
2074
|
mode: this.camera.getProjectionMode(),
|
|
2110
2075
|
fovYRadians: this.camera.getFOV(),
|
|
2111
2076
|
orthoHalfHeight: this.camera.getOrthoSize(),
|
|
2112
|
-
viewportHeightPx: this.canvas.height,
|
|
2077
|
+
viewportHeightPx: this.canvas.height / this.pixelRatio, // CSS px, like the thresholds
|
|
2113
2078
|
};
|
|
2114
2079
|
}
|
|
2115
2080
|
// Per-batch visibility (only meaningful while filtering is active).
|
|
@@ -2164,7 +2129,7 @@ export class Renderer {
|
|
|
2164
2129
|
// Frustum culling: skip batches entirely outside the camera view
|
|
2165
2130
|
if (batch.bounds) {
|
|
2166
2131
|
const batchAABB = { min: batch.bounds.min, max: batch.bounds.max };
|
|
2167
|
-
if (!
|
|
2132
|
+
if (!isRteAabbVisible(frustum, batchAABB, rteCullCamera)) {
|
|
2168
2133
|
frameBatchesFrustumCulled++;
|
|
2169
2134
|
continue; // Entire batch is off-screen
|
|
2170
2135
|
}
|
|
@@ -2260,6 +2225,8 @@ export class Renderer {
|
|
|
2260
2225
|
const tpl = this.uniformScratch;
|
|
2261
2226
|
const tplFlags = this.uniformScratchU32;
|
|
2262
2227
|
tpl.set(viewProj, 0);
|
|
2228
|
+
relativeToEyeFrame.packUniforms(tpl, MESH_UNIFORM_OFFSET.rteViewProj);
|
|
2229
|
+
packRteCameraOrigin(relativeToEyeFrame, tpl);
|
|
2263
2230
|
// Identity model matrix (positions already in world space)
|
|
2264
2231
|
tpl[16] = 1;
|
|
2265
2232
|
tpl[17] = 0;
|
|
@@ -2310,6 +2277,11 @@ export class Renderer {
|
|
|
2310
2277
|
tplClipBit;
|
|
2311
2278
|
tplFlags[2] = edgeEnabledU32;
|
|
2312
2279
|
tplFlags[3] = edgeIntensityMilliU32;
|
|
2280
|
+
// Flat/quantized/textured batches enter WGSL in the single
|
|
2281
|
+
// camera-relative frame. Their fragment clip inputs must use
|
|
2282
|
+
// that same frame; mixing a local vertex with a 5,000 km f32
|
|
2283
|
+
// world plane loses centimetre cuts before the comparison.
|
|
2284
|
+
packRteFragmentSpace(relativeToEyeFrame, sectionPlaneData, options.clipBox, tpl);
|
|
2313
2285
|
// Helper function to render a batch — patches color into the shared template
|
|
2314
2286
|
const renderBatch = (batch) => {
|
|
2315
2287
|
if (!batch.bindGroup || !batch.uniformBuffer)
|
|
@@ -2329,6 +2301,11 @@ export class Renderer {
|
|
|
2329
2301
|
tpl[28] = o ? o[0] : 0;
|
|
2330
2302
|
tpl[29] = o ? o[1] : 0;
|
|
2331
2303
|
tpl[30] = o ? o[2] : 0;
|
|
2304
|
+
// The regular model matrix remains for absolute-space
|
|
2305
|
+
// fragment work (section/shadow); vertex projection uses
|
|
2306
|
+
// this f64-subtracted high/low origin instead.
|
|
2307
|
+
relativeToEyeFrame.packDrawableOrigin(o ?? [0, 0, 0], tpl, MESH_UNIFORM_OFFSET.drawableDelta);
|
|
2308
|
+
tplFlags[0] |= MESH_FLAG_RTE_DRAWABLE;
|
|
2332
2309
|
// Quantized dequantization params (issue #1682 phase 6);
|
|
2333
2310
|
// zeroed for f32 batches (their pipelines ignore them).
|
|
2334
2311
|
const qz = batch.quantized;
|
|
@@ -2408,7 +2385,7 @@ export class Renderer {
|
|
|
2408
2385
|
const kept = [];
|
|
2409
2386
|
for (const it of instancedTemplates) {
|
|
2410
2387
|
if (it.bounds) {
|
|
2411
|
-
if (!
|
|
2388
|
+
if (!isRteAabbVisible(frustum, it.bounds, rteCullCamera)) {
|
|
2412
2389
|
frameInstancedFrustumCulled++;
|
|
2413
2390
|
continue;
|
|
2414
2391
|
}
|
|
@@ -2426,11 +2403,12 @@ export class Renderer {
|
|
|
2426
2403
|
visibleInstanced = kept;
|
|
2427
2404
|
}
|
|
2428
2405
|
if (visibleInstanced.length > 0) {
|
|
2406
|
+
uploadInstancedRteDeltas(device, visibleInstanced, relativeToEyeFrame.getCameraWorld());
|
|
2429
2407
|
// Opaque instanced pass. flags.x bit 2 marks "instanced pass" so the
|
|
2430
2408
|
// shader routes per-instance opacity: opaque (or selected) occurrences
|
|
2431
2409
|
// draw here; translucent ones (lens/x-ray/compare overrides) are
|
|
2432
2410
|
// discarded and drawn in the transparent sub-pass below.
|
|
2433
|
-
this.pipeline.writeRawUniforms(tpl, 0x4);
|
|
2411
|
+
this.pipeline.writeRawUniforms(tpl, MESH_FLAG_RTE_DRAWABLE | 0x4);
|
|
2434
2412
|
pass.setPipeline(this.pipeline.getInstancedPipeline());
|
|
2435
2413
|
pass.setBindGroup(0, this.pipeline.getBindGroup());
|
|
2436
2414
|
pass.setBindGroup(1, this.pipeline.getEnvironmentBindGroup());
|
|
@@ -2490,6 +2468,8 @@ export class Renderer {
|
|
|
2490
2468
|
tpl[28] = tm.origin[0];
|
|
2491
2469
|
tpl[29] = tm.origin[1];
|
|
2492
2470
|
tpl[30] = tm.origin[2];
|
|
2471
|
+
relativeToEyeFrame.packDrawableOrigin(tm.origin, tpl, MESH_UNIFORM_OFFSET.drawableDelta);
|
|
2472
|
+
tplFlags[0] |= MESH_FLAG_RTE_DRAWABLE;
|
|
2493
2473
|
tpl[32] = txOverride ? txOverride[0] : tm.color[0];
|
|
2494
2474
|
tpl[33] = txOverride ? txOverride[1] : tm.color[1];
|
|
2495
2475
|
tpl[34] = txOverride ? txOverride[2] : tm.color[2];
|
|
@@ -2636,7 +2616,7 @@ export class Renderer {
|
|
|
2636
2616
|
if (visibleInstanced.length > 0 &&
|
|
2637
2617
|
this.scene.hasTransparentInstances() &&
|
|
2638
2618
|
instancedTransparentPipeline !== null) {
|
|
2639
|
-
this.pipeline.writeRawUniforms(tpl, 0x4 | 0x8);
|
|
2619
|
+
this.pipeline.writeRawUniforms(tpl, MESH_FLAG_RTE_DRAWABLE | 0x4 | 0x8);
|
|
2640
2620
|
pass.setPipeline(instancedTransparentPipeline);
|
|
2641
2621
|
pass.setBindGroup(0, this.pipeline.getBindGroup());
|
|
2642
2622
|
pass.setBindGroup(1, this.pipeline.getEnvironmentBindGroup());
|
|
@@ -2693,6 +2673,10 @@ export class Renderer {
|
|
|
2693
2673
|
tplClipBit;
|
|
2694
2674
|
tplFlags[2] = edgeEnabledU32;
|
|
2695
2675
|
tplFlags[3] = edgeIntensityMilliU32;
|
|
2676
|
+
packRteFragmentSpace(relativeToEyeFrame, sectionPlaneData, options.clipBox, tpl);
|
|
2677
|
+
const transparentOrigin = mesh.rteOrigin ?? [mesh.transform.m[12], mesh.transform.m[13], mesh.transform.m[14]];
|
|
2678
|
+
relativeToEyeFrame.packDrawableOrigin(transparentOrigin, tpl, MESH_UNIFORM_OFFSET.drawableDelta);
|
|
2679
|
+
tplFlags[0] |= MESH_FLAG_RTE_DRAWABLE;
|
|
2696
2680
|
device.queue.writeBuffer(mesh.uniformBuffer, 0, tpl);
|
|
2697
2681
|
pass.setBindGroup(0, mesh.bindGroup);
|
|
2698
2682
|
pass.setVertexBuffer(0, mesh.vertexBuffer);
|
|
@@ -2753,6 +2737,10 @@ export class Renderer {
|
|
|
2753
2737
|
tplClipBit;
|
|
2754
2738
|
tplFlags[2] = edgeEnabledU32;
|
|
2755
2739
|
tplFlags[3] = edgeIntensityMilliU32;
|
|
2740
|
+
packRteFragmentSpace(relativeToEyeFrame, sectionPlaneData, options.clipBox, tpl);
|
|
2741
|
+
const selectedOrigin = mesh.rteOrigin ?? [mesh.transform.m[12], mesh.transform.m[13], mesh.transform.m[14]];
|
|
2742
|
+
relativeToEyeFrame.packDrawableOrigin(selectedOrigin, tpl, MESH_UNIFORM_OFFSET.drawableDelta);
|
|
2743
|
+
tplFlags[0] |= MESH_FLAG_RTE_DRAWABLE;
|
|
2756
2744
|
device.queue.writeBuffer(mesh.uniformBuffer, 0, tpl);
|
|
2757
2745
|
pass.setPipeline(this.pipeline.getSelectionPipeline());
|
|
2758
2746
|
pass.setBindGroup(0, mesh.bindGroup);
|
|
@@ -2800,65 +2788,55 @@ export class Renderer {
|
|
|
2800
2788
|
// viewport size to convert pixel sizes into clip-space offsets.
|
|
2801
2789
|
if (this.pointCloudRenderer && this.pointCloudRenderer.hasAssets()) {
|
|
2802
2790
|
this.pointCloudRenderer.draw(pass, {
|
|
2803
|
-
viewProj,
|
|
2791
|
+
viewProj: relativeToEyeFrame.getViewProjection().m,
|
|
2792
|
+
relativeToEyeFrame,
|
|
2804
2793
|
sectionPlane: sectionPlaneData
|
|
2805
2794
|
? { ...sectionPlaneData, flipped: options.sectionPlane?.flipped === true }
|
|
2806
2795
|
: null,
|
|
2807
|
-
|
|
2796
|
+
clipBox: options.clipBox,
|
|
2797
|
+
viewport: { width: this.canvas.width / this.pixelRatio, height: this.canvas.height / this.pixelRatio },
|
|
2808
2798
|
});
|
|
2809
2799
|
}
|
|
2810
2800
|
// Section-plane gizmo, 2D section cap and every standalone 3D
|
|
2811
2801
|
// overlay (annotation / alignment / grid / DXF / clash / symbolic
|
|
2812
2802
|
// text). One draw call into the pass — see RendererOverlays.draw().
|
|
2813
|
-
this.referenceImages.draw(pass, viewProj);
|
|
2803
|
+
this.referenceImages.draw(pass, viewProj, relativeToEyeFrame.getViewProjection().m, relativeToEyeFrame.getCameraWorld());
|
|
2814
2804
|
this.overlays.draw(pass, {
|
|
2815
2805
|
options,
|
|
2816
2806
|
viewProj,
|
|
2817
2807
|
modelBounds: this.getModelBounds(),
|
|
2818
2808
|
camera: this.camera,
|
|
2819
|
-
canvasWidth: this.canvas.width,
|
|
2820
|
-
canvasHeight: this.canvas.height,
|
|
2809
|
+
canvasWidth: this.canvas.width / this.pixelRatio, // CSS px: glyph sizes are CSS px
|
|
2810
|
+
canvasHeight: this.canvas.height / this.pixelRatio, pixelRatio: this.pixelRatio,
|
|
2811
|
+
relativeToEyeFrame,
|
|
2812
|
+
rteViewProj: relativeToEyeFrame.getViewProjection().m,
|
|
2813
|
+
rteCamera: relativeToEyeFrame.getCameraWorld(),
|
|
2821
2814
|
});
|
|
2822
2815
|
pass.end();
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
this.
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
separationRadius: Math.min(2.0, Math.max(1.0, visualEnhancement.separationLines.radius)),
|
|
2842
|
-
separationIntensity: separationEnabled ? Math.min(1.0, Math.max(0.0, visualEnhancement.separationLines.intensity)) : 0.0,
|
|
2843
|
-
enableSeparationLines: separationEnabled,
|
|
2844
|
-
});
|
|
2845
|
-
}
|
|
2846
|
-
// Eye-Dome Lighting — runs AFTER contact/separation so it darkens
|
|
2847
|
-
// every layer uniformly. Cheap (~9 depth taps), only active when
|
|
2848
|
-
// there are point clouds in the scene and the user has enabled it.
|
|
2849
|
-
if (this.edlPass
|
|
2850
|
-
&& this.edlOptions.enabled
|
|
2851
|
-
&& this.pointCloudRenderer?.hasAssets()) {
|
|
2852
|
-
this.edlPass.apply(encoder, {
|
|
2853
|
-
targetView: textureView,
|
|
2854
|
-
depthView: this.pipeline.getDepthOnlyTextureView(),
|
|
2855
|
-
}, {
|
|
2856
|
-
strength: this.edlOptions.strength,
|
|
2857
|
-
radiusPx: this.edlOptions.radiusPx,
|
|
2858
|
-
highQuality: this.edlOptions.highQuality,
|
|
2859
|
-
});
|
|
2860
|
-
}
|
|
2816
|
+
// Created lazily like the sky/shadow passes; each pass inside is too.
|
|
2817
|
+
this.postPasses ??= new PostPassChain(this.device, this.pipeline.getSampleCount());
|
|
2818
|
+
this.postPasses.encode({
|
|
2819
|
+
encoder,
|
|
2820
|
+
targetView: textureView,
|
|
2821
|
+
width: this.canvas.width,
|
|
2822
|
+
height: this.canvas.height,
|
|
2823
|
+
// Depth-only: depth24plus-stencil8 cannot be sampled as texture_depth_* with aspect 'all'.
|
|
2824
|
+
depthView: this.pipeline.getDepthOnlyTextureView(),
|
|
2825
|
+
objectIdView,
|
|
2826
|
+
projection: this.camera.getProjMatrix(),
|
|
2827
|
+
enhancement: visualEnhancement,
|
|
2828
|
+
effectsLive,
|
|
2829
|
+
pixelRatio: this.pixelRatio,
|
|
2830
|
+
// EDL only when point clouds are loaded and the user enabled it.
|
|
2831
|
+
edl: this.edlOptions.enabled && this.pointCloudRenderer?.hasAssets() ? this.edlOptions : null,
|
|
2832
|
+
});
|
|
2833
|
+
colorReadback = colorCapture && encodeRendererColorFrameCapture(device, encoder, colorCapture);
|
|
2861
2834
|
device.queue.submit([encoder.finish()]);
|
|
2835
|
+
if (colorReadback && colorCapture) {
|
|
2836
|
+
settleRendererColorFrameCapture(this, colorCapture, colorReadback);
|
|
2837
|
+
colorCapture = null;
|
|
2838
|
+
colorReadback = null;
|
|
2839
|
+
}
|
|
2862
2840
|
this._lastFrameStats = {
|
|
2863
2841
|
drawCalls: frameDrawCalls,
|
|
2864
2842
|
batchesDrawn: frameBatchesDrawn,
|
|
@@ -2882,6 +2860,7 @@ export class Renderer {
|
|
|
2882
2860
|
}
|
|
2883
2861
|
}
|
|
2884
2862
|
catch (error) {
|
|
2863
|
+
discardRendererColorFrameReadback(colorReadback);
|
|
2885
2864
|
// Balance the validation scope if we threw before popping it above —
|
|
2886
2865
|
// an unpopped scope would capture every later frame's errors silently.
|
|
2887
2866
|
// drainErrorScope logs a pop rejection (device loss) rather than
|
|
@@ -2901,7 +2880,9 @@ export class Renderer {
|
|
|
2901
2880
|
// typed-array views — plus one 5-argument call in
|
|
2902
2881
|
// `point-cloud-uniforms.ts` whose offset and size are compile-time
|
|
2903
2882
|
// constants matching its scratch array — so the spec's
|
|
2904
|
-
// `OperationError` preconditions are unreachable
|
|
2883
|
+
// `OperationError` preconditions are unreachable (the partial-batch
|
|
2884
|
+
// upload's `createStaticGpuBuffer` also pads its whole-view payload
|
|
2885
|
+
// to the 4-byte multiple the spec demands, #5429); the one
|
|
2905
2886
|
// `copyExternalImageToTexture` copies the glyph atlas's own
|
|
2906
2887
|
// never-externally-drawn canvas at its full fixed size, so neither
|
|
2907
2888
|
// `SecurityError` nor a zero-size `OperationError` can arise; and
|
|
@@ -2910,10 +2891,11 @@ export class Renderer {
|
|
|
2910
2891
|
// draws, `finish`, `submit`, `createBindGroup`) reports failure as
|
|
2911
2892
|
// an asynchronous `GPUValidationError` through the error scope, not
|
|
2912
2893
|
// as a throw. The region's real healthy-device failure is
|
|
2913
|
-
// `getOrCreatePartialBatch`'s
|
|
2914
|
-
//
|
|
2894
|
+
// `getOrCreatePartialBatch`'s CPU-side geometry merge running out of
|
|
2895
|
+
// host memory, and that throws a `RangeError` — which is exactly why
|
|
2915
2896
|
// the discriminator keys on the TYPE and not on "a frame threw".
|
|
2916
2897
|
this.containFrameThrow(error, 'encode');
|
|
2898
|
+
cancelRendererColorFrame(this);
|
|
2917
2899
|
}
|
|
2918
2900
|
}
|
|
2919
2901
|
/**
|
|
@@ -3044,8 +3026,8 @@ export class Renderer {
|
|
|
3044
3026
|
}
|
|
3045
3027
|
/** Stage one new owner; borrowed mesh buffers must remain immutable until disposal. */
|
|
3046
3028
|
prepareAuthoredOwner(parts) {
|
|
3047
|
-
if (!this.device.isInitialized() || !this.pipeline)
|
|
3048
|
-
throw new Error('Renderer is not initialized.');
|
|
3029
|
+
if (this.deviceLost || !this.device.isInitialized() || !this.pipeline)
|
|
3030
|
+
throw this.deviceLost ? rendererDeviceLostError() : new Error('Renderer is not initialized.');
|
|
3049
3031
|
const prepared = this.scene.prepareAuthoredOwner(parts, this.device.getDevice(), this.pipeline);
|
|
3050
3032
|
return { commit: () => { prepared.commit(); this.refreshPlacementBounds(); this.invalidateBVHCache(); this.requestRender(); }, dispose: prepared.dispose };
|
|
3051
3033
|
}
|
|
@@ -3240,30 +3222,9 @@ export class Renderer {
|
|
|
3240
3222
|
}
|
|
3241
3223
|
return this.device.getDevice();
|
|
3242
3224
|
}
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
* @returns PNG data URL or null if capture failed
|
|
3247
|
-
*/
|
|
3248
|
-
async captureScreenshot() {
|
|
3249
|
-
if (!this.device.isInitialized()) {
|
|
3250
|
-
console.warn('[Renderer] Cannot capture screenshot: not initialized');
|
|
3251
|
-
return null;
|
|
3252
|
-
}
|
|
3253
|
-
try {
|
|
3254
|
-
// Wait for any pending GPU work to complete before capturing
|
|
3255
|
-
// This ensures we capture the fully rendered frame
|
|
3256
|
-
const device = this.device.getDevice();
|
|
3257
|
-
await device.queue.onSubmittedWorkDone();
|
|
3258
|
-
// Capture exactly what's displayed on the canvas
|
|
3259
|
-
const dataUrl = this.canvas.toDataURL('image/png');
|
|
3260
|
-
return dataUrl;
|
|
3261
|
-
}
|
|
3262
|
-
catch (error) {
|
|
3263
|
-
console.error('[Renderer] Screenshot capture failed:', error);
|
|
3264
|
-
return null;
|
|
3265
|
-
}
|
|
3266
|
-
}
|
|
3225
|
+
captureScreenshot() { return captureRendererScreenshot(this.device, this.canvas); }
|
|
3226
|
+
/** Capture the next submitted frame's bounded color pixels, not compositor state. */
|
|
3227
|
+
captureColorFrame() { return requestRendererColorFrame(this, !this.destroyed && !this.deviceLost && this.device.isInitialized(), () => this.requestRender()); }
|
|
3267
3228
|
/**
|
|
3268
3229
|
* Destroy the renderer and release all GPU resources.
|
|
3269
3230
|
*
|
|
@@ -3298,6 +3259,7 @@ export class Renderer {
|
|
|
3298
3259
|
destroy() {
|
|
3299
3260
|
this.initGeneration++;
|
|
3300
3261
|
this.destroyed = true;
|
|
3262
|
+
cancelRendererColorFrame(this);
|
|
3301
3263
|
this.teardown();
|
|
3302
3264
|
this.rejectReadyWaiters(rendererDestroyedError());
|
|
3303
3265
|
}
|
|
@@ -3306,15 +3268,18 @@ export class Renderer {
|
|
|
3306
3268
|
* in-flight init. Callers: the public `destroy()` (which invalidates first)
|
|
3307
3269
|
* and `initOnce()`, tearing down the previous init before building its own.
|
|
3308
3270
|
*/
|
|
3309
|
-
teardown() {
|
|
3271
|
+
teardown(clearScene = true) {
|
|
3310
3272
|
// Nothing below survives this call, so `whenReady()` / `isReady()` must
|
|
3311
3273
|
// go back to waiting. Set first: every release below is synchronous, but
|
|
3312
3274
|
// the flag is what a caller holding a live reference actually reads.
|
|
3313
3275
|
this.ready = false;
|
|
3276
|
+
cancelRendererColorFrame(this);
|
|
3314
3277
|
// Scene mesh GPU buffers
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3278
|
+
if (clearScene) {
|
|
3279
|
+
this.scene.clear();
|
|
3280
|
+
// Re-arm the section-bounds diagnostic log for the next model.
|
|
3281
|
+
this._loggedSectionBounds = false;
|
|
3282
|
+
}
|
|
3318
3283
|
// Render pipelines (textures + uniform buffers)
|
|
3319
3284
|
this.pipeline?.destroy();
|
|
3320
3285
|
this.pipeline = null;
|
|
@@ -3325,11 +3290,8 @@ export class Renderer {
|
|
|
3325
3290
|
this.picker?.destroy();
|
|
3326
3291
|
this.picker = null;
|
|
3327
3292
|
this.pickingManager.setPicker(null);
|
|
3328
|
-
|
|
3329
|
-
this.
|
|
3330
|
-
this.postProcessor = null;
|
|
3331
|
-
this.edlPass?.destroy();
|
|
3332
|
-
this.edlPass = null;
|
|
3293
|
+
this.postPasses?.destroy();
|
|
3294
|
+
this.postPasses = null;
|
|
3333
3295
|
this.skyPass?.destroy();
|
|
3334
3296
|
this.skyPass = null;
|
|
3335
3297
|
this.shadowPass?.destroy();
|
|
@@ -3339,6 +3301,9 @@ export class Renderer {
|
|
|
3339
3301
|
this.overlays.destroy();
|
|
3340
3302
|
this.referenceImages.destroy();
|
|
3341
3303
|
// Point cloud GPU resources
|
|
3304
|
+
this.pointCloudNextHandleId = this.pointCloudRenderer?.handleIds?.current() ?? this.pointCloudNextHandleId;
|
|
3305
|
+
this.pointCloudStreamEpoch++;
|
|
3306
|
+
this.pointCloudStreamEpochs.clear();
|
|
3342
3307
|
this.pointCloudRenderer?.clear();
|
|
3343
3308
|
this.pointCloudRenderer = null;
|
|
3344
3309
|
// BIM ↔ scan deviation pipeline + cached BVH GPU buffers.
|