@ifc-lite/renderer 3.2.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.
Files changed (371) hide show
  1. package/dist/aabb-edges.d.ts +10 -0
  2. package/dist/aabb-edges.d.ts.map +1 -1
  3. package/dist/aabb-edges.js +13 -0
  4. package/dist/aabb-edges.js.map +1 -1
  5. package/dist/ao-params.d.ts +85 -0
  6. package/dist/ao-params.d.ts.map +1 -0
  7. package/dist/ao-params.js +109 -0
  8. package/dist/ao-params.js.map +1 -0
  9. package/dist/ao-pass.d.ts +47 -0
  10. package/dist/ao-pass.d.ts.map +1 -0
  11. package/dist/ao-pass.js +157 -0
  12. package/dist/ao-pass.js.map +1 -0
  13. package/dist/camera-matrices.d.ts +3 -0
  14. package/dist/camera-matrices.d.ts.map +1 -1
  15. package/dist/camera-matrices.js +12 -1
  16. package/dist/camera-matrices.js.map +1 -1
  17. package/dist/camera-orientation.d.ts +62 -0
  18. package/dist/camera-orientation.d.ts.map +1 -0
  19. package/dist/camera-orientation.js +155 -0
  20. package/dist/camera-orientation.js.map +1 -0
  21. package/dist/camera-projection.d.ts +3 -1
  22. package/dist/camera-projection.d.ts.map +1 -1
  23. package/dist/camera-projection.js +26 -20
  24. package/dist/camera-projection.js.map +1 -1
  25. package/dist/camera-surface-zoom.d.ts +32 -0
  26. package/dist/camera-surface-zoom.d.ts.map +1 -0
  27. package/dist/camera-surface-zoom.js +37 -0
  28. package/dist/camera-surface-zoom.js.map +1 -0
  29. package/dist/camera.d.ts +32 -61
  30. package/dist/camera.d.ts.map +1 -1
  31. package/dist/camera.js +71 -167
  32. package/dist/camera.js.map +1 -1
  33. package/dist/clash-solid-pipeline.d.ts +9 -3
  34. package/dist/clash-solid-pipeline.d.ts.map +1 -1
  35. package/dist/clash-solid-pipeline.js +31 -8
  36. package/dist/clash-solid-pipeline.js.map +1 -1
  37. package/dist/constants.d.ts +1 -1
  38. package/dist/constants.d.ts.map +1 -1
  39. package/dist/constants.js +4 -3
  40. package/dist/constants.js.map +1 -1
  41. package/dist/depth-reconstruct.d.ts +43 -0
  42. package/dist/depth-reconstruct.d.ts.map +1 -0
  43. package/dist/depth-reconstruct.js +35 -0
  44. package/dist/depth-reconstruct.js.map +1 -0
  45. package/dist/device.js +1 -1
  46. package/dist/device.js.map +1 -1
  47. package/dist/environment.d.ts +26 -15
  48. package/dist/environment.d.ts.map +1 -1
  49. package/dist/environment.js +24 -13
  50. package/dist/environment.js.map +1 -1
  51. package/dist/federation-registry.d.ts +16 -0
  52. package/dist/federation-registry.d.ts.map +1 -1
  53. package/dist/federation-registry.js +80 -9
  54. package/dist/federation-registry.js.map +1 -1
  55. package/dist/gpu-static-upload.d.ts +39 -0
  56. package/dist/gpu-static-upload.d.ts.map +1 -0
  57. package/dist/gpu-static-upload.js +70 -0
  58. package/dist/gpu-static-upload.js.map +1 -0
  59. package/dist/gpu-upload-guard.d.ts +12 -23
  60. package/dist/gpu-upload-guard.d.ts.map +1 -1
  61. package/dist/gpu-upload-guard.js +12 -26
  62. package/dist/gpu-upload-guard.js.map +1 -1
  63. package/dist/index.d.ts +29 -21
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +201 -252
  66. package/dist/index.js.map +1 -1
  67. package/dist/instanced-render.d.ts +22 -2
  68. package/dist/instanced-render.d.ts.map +1 -1
  69. package/dist/instanced-render.js +47 -10
  70. package/dist/instanced-render.js.map +1 -1
  71. package/dist/instanced-rte.d.ts +17 -0
  72. package/dist/instanced-rte.d.ts.map +1 -0
  73. package/dist/instanced-rte.js +32 -0
  74. package/dist/instanced-rte.js.map +1 -0
  75. package/dist/mesh-rte-uniforms.d.ts +46 -0
  76. package/dist/mesh-rte-uniforms.d.ts.map +1 -0
  77. package/dist/mesh-rte-uniforms.js +59 -0
  78. package/dist/mesh-rte-uniforms.js.map +1 -0
  79. package/dist/model-bounds-tracker.d.ts +7 -0
  80. package/dist/model-bounds-tracker.d.ts.map +1 -1
  81. package/dist/model-bounds-tracker.js +16 -5
  82. package/dist/model-bounds-tracker.js.map +1 -1
  83. package/dist/model-translation.d.ts +1 -1
  84. package/dist/model-translation.d.ts.map +1 -1
  85. package/dist/model-translation.js +50 -2
  86. package/dist/model-translation.js.map +1 -1
  87. package/dist/pick-rte-snapshot.d.ts +7 -0
  88. package/dist/pick-rte-snapshot.d.ts.map +1 -0
  89. package/dist/pick-rte-snapshot.js +15 -0
  90. package/dist/pick-rte-snapshot.js.map +1 -0
  91. package/dist/pick-types.d.ts +17 -0
  92. package/dist/pick-types.d.ts.map +1 -0
  93. package/dist/pick-types.js +5 -0
  94. package/dist/pick-types.js.map +1 -0
  95. package/dist/pick-uniforms.d.ts +6 -0
  96. package/dist/pick-uniforms.d.ts.map +1 -1
  97. package/dist/pick-uniforms.js +22 -18
  98. package/dist/pick-uniforms.js.map +1 -1
  99. package/dist/pick-world-position.d.ts +18 -0
  100. package/dist/pick-world-position.d.ts.map +1 -0
  101. package/dist/pick-world-position.js +21 -0
  102. package/dist/pick-world-position.js.map +1 -0
  103. package/dist/picker-readbacks.d.ts +6 -0
  104. package/dist/picker-readbacks.d.ts.map +1 -0
  105. package/dist/picker-readbacks.js +20 -0
  106. package/dist/picker-readbacks.js.map +1 -0
  107. package/dist/picker-rte-uniforms.d.ts +6 -0
  108. package/dist/picker-rte-uniforms.d.ts.map +1 -0
  109. package/dist/picker-rte-uniforms.js +41 -0
  110. package/dist/picker-rte-uniforms.js.map +1 -0
  111. package/dist/picker.d.ts +6 -7
  112. package/dist/picker.d.ts.map +1 -1
  113. package/dist/picker.js +88 -115
  114. package/dist/picker.js.map +1 -1
  115. package/dist/picking-manager.d.ts +8 -0
  116. package/dist/picking-manager.d.ts.map +1 -1
  117. package/dist/picking-manager.js +46 -19
  118. package/dist/picking-manager.js.map +1 -1
  119. package/dist/pipeline.d.ts +1 -7
  120. package/dist/pipeline.d.ts.map +1 -1
  121. package/dist/pipeline.js +17 -20
  122. package/dist/pipeline.js.map +1 -1
  123. package/dist/point-picker.d.ts +5 -1
  124. package/dist/point-picker.d.ts.map +1 -1
  125. package/dist/point-picker.js +49 -9
  126. package/dist/point-picker.js.map +1 -1
  127. package/dist/pointcloud/point-cloud-handle-ids.d.ts +22 -0
  128. package/dist/pointcloud/point-cloud-handle-ids.d.ts.map +1 -0
  129. package/dist/pointcloud/point-cloud-handle-ids.js +31 -0
  130. package/dist/pointcloud/point-cloud-handle-ids.js.map +1 -0
  131. package/dist/pointcloud/point-cloud-node.d.ts +14 -6
  132. package/dist/pointcloud/point-cloud-node.d.ts.map +1 -1
  133. package/dist/pointcloud/point-cloud-node.js.map +1 -1
  134. package/dist/pointcloud/point-cloud-pick-sources.d.ts +47 -0
  135. package/dist/pointcloud/point-cloud-pick-sources.d.ts.map +1 -0
  136. package/dist/pointcloud/point-cloud-pick-sources.js +32 -0
  137. package/dist/pointcloud/point-cloud-pick-sources.js.map +1 -0
  138. package/dist/pointcloud/point-cloud-placement.d.ts.map +1 -1
  139. package/dist/pointcloud/point-cloud-placement.js +20 -5
  140. package/dist/pointcloud/point-cloud-placement.js.map +1 -1
  141. package/dist/pointcloud/point-cloud-ray-transform.d.ts +78 -4
  142. package/dist/pointcloud/point-cloud-ray-transform.d.ts.map +1 -1
  143. package/dist/pointcloud/point-cloud-ray-transform.js +31 -84
  144. package/dist/pointcloud/point-cloud-ray-transform.js.map +1 -1
  145. package/dist/pointcloud/point-cloud-renderer.d.ts +21 -19
  146. package/dist/pointcloud/point-cloud-renderer.d.ts.map +1 -1
  147. package/dist/pointcloud/point-cloud-renderer.js +46 -52
  148. package/dist/pointcloud/point-cloud-renderer.js.map +1 -1
  149. package/dist/pointcloud/point-cloud-stream-lifecycle.d.ts +44 -0
  150. package/dist/pointcloud/point-cloud-stream-lifecycle.d.ts.map +1 -0
  151. package/dist/pointcloud/point-cloud-stream-lifecycle.js +44 -0
  152. package/dist/pointcloud/point-cloud-stream-lifecycle.js.map +1 -0
  153. package/dist/pointcloud/point-cloud-uniforms.d.ts +5 -0
  154. package/dist/pointcloud/point-cloud-uniforms.d.ts.map +1 -1
  155. package/dist/pointcloud/point-cloud-uniforms.js +55 -44
  156. package/dist/pointcloud/point-cloud-uniforms.js.map +1 -1
  157. package/dist/pointcloud/point-cloud-visibility.d.ts +10 -0
  158. package/dist/pointcloud/point-cloud-visibility.d.ts.map +1 -0
  159. package/dist/pointcloud/point-cloud-visibility.js +18 -0
  160. package/dist/pointcloud/point-cloud-visibility.js.map +1 -0
  161. package/dist/pointcloud/point-pipeline.d.ts +4 -9
  162. package/dist/pointcloud/point-pipeline.d.ts.map +1 -1
  163. package/dist/pointcloud/point-pipeline.js +4 -9
  164. package/dist/pointcloud/point-pipeline.js.map +1 -1
  165. package/dist/pointcloud/point-shader.wgsl.d.ts +1 -21
  166. package/dist/pointcloud/point-shader.wgsl.d.ts.map +1 -1
  167. package/dist/pointcloud/point-shader.wgsl.js +25 -5
  168. package/dist/pointcloud/point-shader.wgsl.js.map +1 -1
  169. package/dist/post-pass-chain.d.ts +38 -0
  170. package/dist/post-pass-chain.d.ts.map +1 -0
  171. package/dist/post-pass-chain.js +99 -0
  172. package/dist/post-pass-chain.js.map +1 -0
  173. package/dist/post-processor.d.ts +12 -26
  174. package/dist/post-processor.d.ts.map +1 -1
  175. package/dist/post-processor.js +14 -72
  176. package/dist/post-processor.js.map +1 -1
  177. package/dist/raycast-engine.d.ts +7 -5
  178. package/dist/raycast-engine.d.ts.map +1 -1
  179. package/dist/raycast-engine.js +17 -20
  180. package/dist/raycast-engine.js.map +1 -1
  181. package/dist/raycast-point-cloud-query.d.ts +1 -1
  182. package/dist/raycast-point-cloud-query.d.ts.map +1 -1
  183. package/dist/reference-image-pipeline.d.ts +1 -1
  184. package/dist/reference-image-pipeline.d.ts.map +1 -1
  185. package/dist/reference-image-pipeline.js +25 -7
  186. package/dist/reference-image-pipeline.js.map +1 -1
  187. package/dist/reference-images.d.ts +1 -1
  188. package/dist/reference-images.d.ts.map +1 -1
  189. package/dist/reference-images.js +13 -5
  190. package/dist/reference-images.js.map +1 -1
  191. package/dist/relative-to-eye.d.ts +146 -0
  192. package/dist/relative-to-eye.d.ts.map +1 -0
  193. package/dist/relative-to-eye.js +326 -0
  194. package/dist/relative-to-eye.js.map +1 -0
  195. package/dist/render-section-draw.d.ts +11 -0
  196. package/dist/render-section-draw.d.ts.map +1 -1
  197. package/dist/render-section-draw.js +6 -3
  198. package/dist/render-section-draw.js.map +1 -1
  199. package/dist/renderer-color-readback.d.ts +50 -0
  200. package/dist/renderer-color-readback.d.ts.map +1 -0
  201. package/dist/renderer-color-readback.js +145 -0
  202. package/dist/renderer-color-readback.js.map +1 -0
  203. package/dist/renderer-overlays.d.ts +12 -3
  204. package/dist/renderer-overlays.d.ts.map +1 -1
  205. package/dist/renderer-overlays.js +33 -20
  206. package/dist/renderer-overlays.js.map +1 -1
  207. package/dist/renderer-screenshot.d.ts +4 -0
  208. package/dist/renderer-screenshot.d.ts.map +1 -0
  209. package/dist/renderer-screenshot.js +17 -0
  210. package/dist/renderer-screenshot.js.map +1 -0
  211. package/dist/renderer-symbolic-overlays.d.ts +4 -2
  212. package/dist/renderer-symbolic-overlays.d.ts.map +1 -1
  213. package/dist/renderer-symbolic-overlays.js +33 -13
  214. package/dist/renderer-symbolic-overlays.js.map +1 -1
  215. package/dist/renderer-viewport.d.ts +45 -0
  216. package/dist/renderer-viewport.d.ts.map +1 -1
  217. package/dist/renderer-viewport.js +44 -0
  218. package/dist/renderer-viewport.js.map +1 -1
  219. package/dist/rte-clip-space.d.ts +12 -0
  220. package/dist/rte-clip-space.d.ts.map +1 -0
  221. package/dist/rte-clip-space.js +22 -0
  222. package/dist/rte-clip-space.js.map +1 -0
  223. package/dist/rte-frustum.d.ts +24 -0
  224. package/dist/rte-frustum.d.ts.map +1 -0
  225. package/dist/rte-frustum.js +45 -0
  226. package/dist/rte-frustum.js.map +1 -0
  227. package/dist/scene-batch-upload.d.ts.map +1 -1
  228. package/dist/scene-batch-upload.js +14 -38
  229. package/dist/scene-batch-upload.js.map +1 -1
  230. package/dist/scene-device-recovery.d.ts.map +1 -1
  231. package/dist/scene-device-recovery.js +5 -9
  232. package/dist/scene-device-recovery.js.map +1 -1
  233. package/dist/scene-finalize-regroup.d.ts +54 -0
  234. package/dist/scene-finalize-regroup.d.ts.map +1 -0
  235. package/dist/scene-finalize-regroup.js +136 -0
  236. package/dist/scene-finalize-regroup.js.map +1 -0
  237. package/dist/scene-instance-bounds.d.ts +12 -0
  238. package/dist/scene-instance-bounds.d.ts.map +1 -0
  239. package/dist/scene-instance-bounds.js +48 -0
  240. package/dist/scene-instance-bounds.js.map +1 -0
  241. package/dist/scene-instance-materialization.d.ts +4 -1
  242. package/dist/scene-instance-materialization.d.ts.map +1 -1
  243. package/dist/scene-instance-materialization.js +21 -6
  244. package/dist/scene-instance-materialization.js.map +1 -1
  245. package/dist/scene-instance-translation.d.ts +17 -0
  246. package/dist/scene-instance-translation.d.ts.map +1 -0
  247. package/dist/scene-instance-translation.js +50 -0
  248. package/dist/scene-instance-translation.js.map +1 -0
  249. package/dist/scene-instance-types.d.ts +6 -0
  250. package/dist/scene-instance-types.d.ts.map +1 -1
  251. package/dist/scene-model-translation.d.ts +2 -0
  252. package/dist/scene-model-translation.d.ts.map +1 -1
  253. package/dist/scene-model-translation.js +1 -1
  254. package/dist/scene-model-translation.js.map +1 -1
  255. package/dist/scene-rect-select.d.ts +11 -1
  256. package/dist/scene-rect-select.d.ts.map +1 -1
  257. package/dist/scene-rect-select.js +7 -4
  258. package/dist/scene-rect-select.js.map +1 -1
  259. package/dist/scene.d.ts +17 -19
  260. package/dist/scene.d.ts.map +1 -1
  261. package/dist/scene.js +109 -238
  262. package/dist/scene.js.map +1 -1
  263. package/dist/section-2d-lift.d.ts +10 -2
  264. package/dist/section-2d-lift.d.ts.map +1 -1
  265. package/dist/section-2d-lift.js +14 -6
  266. package/dist/section-2d-lift.js.map +1 -1
  267. package/dist/section-2d-line-buffer.d.ts +12 -4
  268. package/dist/section-2d-line-buffer.d.ts.map +1 -1
  269. package/dist/section-2d-line-buffer.js +59 -33
  270. package/dist/section-2d-line-buffer.js.map +1 -1
  271. package/dist/section-2d-overlay.d.ts +18 -72
  272. package/dist/section-2d-overlay.d.ts.map +1 -1
  273. package/dist/section-2d-overlay.js +72 -103
  274. package/dist/section-2d-overlay.js.map +1 -1
  275. package/dist/section-2d-types.d.ts +38 -0
  276. package/dist/section-2d-types.d.ts.map +1 -0
  277. package/dist/section-2d-types.js +6 -0
  278. package/dist/section-2d-types.js.map +1 -0
  279. package/dist/section-cap-pipelines.d.ts +6 -0
  280. package/dist/section-cap-pipelines.d.ts.map +1 -0
  281. package/dist/section-cap-pipelines.js +87 -0
  282. package/dist/section-cap-pipelines.js.map +1 -0
  283. package/dist/section-plane-geometry.d.ts +24 -0
  284. package/dist/section-plane-geometry.d.ts.map +1 -0
  285. package/dist/section-plane-geometry.js +93 -0
  286. package/dist/section-plane-geometry.js.map +1 -0
  287. package/dist/section-plane.d.ts +23 -24
  288. package/dist/section-plane.d.ts.map +1 -1
  289. package/dist/section-plane.js +58 -174
  290. package/dist/section-plane.js.map +1 -1
  291. package/dist/shaders/ao.wgsl.d.ts +2 -0
  292. package/dist/shaders/ao.wgsl.d.ts.map +1 -0
  293. package/dist/shaders/ao.wgsl.js +234 -0
  294. package/dist/shaders/ao.wgsl.js.map +1 -0
  295. package/dist/shaders/color-transfer.wgsl.d.ts +26 -0
  296. package/dist/shaders/color-transfer.wgsl.d.ts.map +1 -0
  297. package/dist/shaders/color-transfer.wgsl.js +53 -0
  298. package/dist/shaders/color-transfer.wgsl.js.map +1 -0
  299. package/dist/shaders/depth-reconstruct.wgsl.d.ts +16 -0
  300. package/dist/shaders/depth-reconstruct.wgsl.d.ts.map +1 -0
  301. package/dist/shaders/depth-reconstruct.wgsl.js +69 -0
  302. package/dist/shaders/depth-reconstruct.wgsl.js.map +1 -0
  303. package/dist/shaders/main-rte-shadow.wgsl.d.ts +4 -0
  304. package/dist/shaders/main-rte-shadow.wgsl.d.ts.map +1 -0
  305. package/dist/shaders/main-rte-shadow.wgsl.js +54 -0
  306. package/dist/shaders/main-rte-shadow.wgsl.js.map +1 -0
  307. package/dist/shaders/main.wgsl.d.ts +1 -6
  308. package/dist/shaders/main.wgsl.d.ts.map +1 -1
  309. package/dist/shaders/main.wgsl.js +110 -159
  310. package/dist/shaders/main.wgsl.js.map +1 -1
  311. package/dist/shaders/relative-to-eye.wgsl.d.ts +12 -0
  312. package/dist/shaders/relative-to-eye.wgsl.d.ts.map +1 -0
  313. package/dist/shaders/relative-to-eye.wgsl.js +40 -0
  314. package/dist/shaders/relative-to-eye.wgsl.js.map +1 -0
  315. package/dist/shaders/section-2d-overlay.wgsl.d.ts +28 -12
  316. package/dist/shaders/section-2d-overlay.wgsl.d.ts.map +1 -1
  317. package/dist/shaders/section-2d-overlay.wgsl.js +40 -13
  318. package/dist/shaders/section-2d-overlay.wgsl.js.map +1 -1
  319. package/dist/shaders/shadow.wgsl.d.ts +1 -1
  320. package/dist/shaders/shadow.wgsl.d.ts.map +1 -1
  321. package/dist/shaders/shadow.wgsl.js +25 -7
  322. package/dist/shaders/shadow.wgsl.js.map +1 -1
  323. package/dist/shaders/sky.wgsl.d.ts +1 -11
  324. package/dist/shaders/sky.wgsl.d.ts.map +1 -1
  325. package/dist/shaders/sky.wgsl.js +17 -19
  326. package/dist/shaders/sky.wgsl.js.map +1 -1
  327. package/dist/shaders/symbolic-overlay.wgsl.d.ts +2 -9
  328. package/dist/shaders/symbolic-overlay.wgsl.d.ts.map +1 -1
  329. package/dist/shaders/symbolic-overlay.wgsl.js +88 -17
  330. package/dist/shaders/symbolic-overlay.wgsl.js.map +1 -1
  331. package/dist/shaders/textured.wgsl.d.ts.map +1 -1
  332. package/dist/shaders/textured.wgsl.js +11 -9
  333. package/dist/shaders/textured.wgsl.js.map +1 -1
  334. package/dist/shadow-light-matrix.d.ts +7 -0
  335. package/dist/shadow-light-matrix.d.ts.map +1 -1
  336. package/dist/shadow-light-matrix.js +11 -0
  337. package/dist/shadow-light-matrix.js.map +1 -1
  338. package/dist/shadow-occluder-batches.d.ts +10 -0
  339. package/dist/shadow-occluder-batches.d.ts.map +1 -0
  340. package/dist/shadow-occluder-batches.js +39 -0
  341. package/dist/shadow-occluder-batches.js.map +1 -0
  342. package/dist/shadow-occluders.d.ts.map +1 -1
  343. package/dist/shadow-occluders.js +4 -0
  344. package/dist/shadow-occluders.js.map +1 -1
  345. package/dist/shadow-pass.d.ts +5 -51
  346. package/dist/shadow-pass.d.ts.map +1 -1
  347. package/dist/shadow-pass.js +49 -45
  348. package/dist/shadow-pass.js.map +1 -1
  349. package/dist/shadow-types.d.ts +39 -0
  350. package/dist/shadow-types.d.ts.map +1 -0
  351. package/dist/shadow-types.js +15 -0
  352. package/dist/shadow-types.js.map +1 -0
  353. package/dist/symbolic-overlay-geometry.d.ts +7 -0
  354. package/dist/symbolic-overlay-geometry.d.ts.map +1 -0
  355. package/dist/symbolic-overlay-geometry.js +43 -0
  356. package/dist/symbolic-overlay-geometry.js.map +1 -0
  357. package/dist/symbolic-overlay-pipelines.d.ts +24 -36
  358. package/dist/symbolic-overlay-pipelines.d.ts.map +1 -1
  359. package/dist/symbolic-overlay-pipelines.js +175 -182
  360. package/dist/symbolic-overlay-pipelines.js.map +1 -1
  361. package/dist/symbolic-text-atlas.d.ts +21 -0
  362. package/dist/symbolic-text-atlas.d.ts.map +1 -1
  363. package/dist/symbolic-text-atlas.js +29 -1
  364. package/dist/symbolic-text-atlas.js.map +1 -1
  365. package/dist/types.d.ts +20 -26
  366. package/dist/types.d.ts.map +1 -1
  367. package/dist/visual-enhancement.d.ts +23 -1
  368. package/dist/visual-enhancement.d.ts.map +1 -1
  369. package/dist/visual-enhancement.js +38 -12
  370. package/dist/visual-enhancement.js.map +1 -1
  371. 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,32 +79,37 @@ 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 { FrustumUtils } from '@ifc-lite/spatial';
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 { PostProcessor } from './post-processor.js';
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, classifyBatchVisibility, DEFAULT_MIN_CAST_ALPHA } from './shadow-occluders.js';
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';
109
114
  import { runGuardedGpuUpload, isDeviceLossThrow } from './gpu-upload-guard.js';
110
115
  import { recoverRendererDevice, rendererDeviceLostError } from './device-recovery.js';
@@ -142,6 +147,7 @@ export class Renderer {
142
147
  overlays = new RendererOverlays({
143
148
  getModelBounds: () => this.getModelBounds(),
144
149
  expandModelBoundsWithFlatVertices: (positions, stride) => this.modelBoundsTracker.expandWithFlatVertices(positions, stride),
150
+ expandModelBoundsWithAnchoredLineVertices: (positions, origin, stride) => this.modelBoundsTracker.expandWithAnchoredVertices(positions, origin, stride),
145
151
  syncCameraSceneBounds: () => {
146
152
  if (this.modelBounds)
147
153
  this.camera.setSceneBounds(this.modelBounds);
@@ -150,9 +156,12 @@ export class Renderer {
150
156
  });
151
157
  referenceImages = createReferenceImageManager(this);
152
158
  getReferenceImages() { return this.referenceImages; }
153
- postProcessor = null;
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;
154
164
  interactionEffects = new InteractionEffectsGovernor();
155
- edlPass = null;
156
165
  // Procedural sky background — created lazily on the first frame that
157
166
  // enables it (most sessions never do).
158
167
  skyPass = null;
@@ -173,6 +182,7 @@ export class Renderer {
173
182
  pointCloudRenderer = null;
174
183
  pointCloudStreamEpoch = 0;
175
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
176
186
  /** Set true at the end of the LATEST `init()`; gates `whenReady()` and
177
187
  * `isReady()`. Revoked synchronously by `init()` and by `destroy()`, and
178
188
  * overridden (not cleared) by a device loss — see `deviceLost`, which the
@@ -381,13 +391,9 @@ export class Renderer {
381
391
  // One-shot log guard — prints Y-up clip bounds on first section-enable so
382
392
  // users can confirm the slider is operating on the intended range.
383
393
  _loggedSectionBounds = false;
384
- // Pooled per-frame buffers to avoid GC pressure from per-batch Float32Array allocations
385
- // A single 224-byte uniform buffer (56 floats) is reused for all batches/meshes within a frame
386
- // (48 floats viewProj…flags + 8 floats clipBoxMin/clipBoxMax)
387
- // 60 floats = the WGSL Uniforms struct incl. quantParams (see
388
- // pipeline.getUniformBufferSize).
389
- uniformScratch = new Float32Array(60);
390
- 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);
391
397
  // What the last render() actually clipped, so the GPU picker can mirror it and
392
398
  // section/crop-clipped geometry stays unpickable, not just invisible. Updated
393
399
  // every render; read by pick()/pickRect(). null = nothing clipped that frame.
@@ -409,8 +415,8 @@ export class Renderer {
409
415
  * are released first. The comment below advertises a `destroy()` + `init()`
410
416
  * re-init flow, and the obvious device-loss auto-recovery is to call
411
417
  * `init()` on the live instance — which, without this, silently orphaned
412
- * two render pipelines, the picker, the post-processor, the point-cloud and
413
- * deviation pipelines, the EDL pass and the overlay layer's glyph atlas, per
418
+ * two render pipelines, the picker, the post passes, the point-cloud and
419
+ * deviation pipelines and the overlay layer's glyph atlas, per
414
420
  * recovery (#2448). Making the method self-safe is cheaper than trusting
415
421
  * every future caller to remember.
416
422
  *
@@ -473,8 +479,8 @@ export class Renderer {
473
479
  await initializingDevice.init(this.canvas);
474
480
  // A `destroy()` (or a newer `init()`) landed while we were parked on the
475
481
  // device. Everything below allocates a full GPU stack — two pipelines,
476
- // the picker, the post-processor, the point-cloud and deviation
477
- // pipelines, the EDL pass, the overlay glyph atlas — and this aborted
482
+ // the picker, the point-cloud and deviation pipelines, the overlay
483
+ // glyph atlas — and this aborted
478
484
  // path runs no second teardown, so all of it would be orphaned outright
479
485
  // (#2465). `markReady()`'s generation check is not enough on its own: it
480
486
  // withholds the readiness PUBLICATION, not the allocation. Release the
@@ -484,15 +490,15 @@ export class Renderer {
484
490
  this.device.destroy();
485
491
  return;
486
492
  }
487
- // Get canvas dimensions (use pixel dimensions if set, otherwise use CSS dimensions)
488
- // and clamp to the GPU's max 2D texture dimension so the initial pipeline allocations
489
- // can't overflow on tall/wide layouts (see render() for the per-frame clamp).
490
- const rect = this.canvas.getBoundingClientRect();
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.
491
497
  const maxDim = this.device.getMaxTextureDimension();
492
- const rawWidth = this.canvas.width || Math.max(1, Math.floor(rect.width));
493
- const rawHeight = this.canvas.height || Math.max(1, Math.floor(rect.height));
494
- const width = Math.min(rawWidth, maxDim);
495
- const height = Math.min(rawHeight, maxDim);
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));
496
502
  // Set pixel dimensions if not already set, or if we clamped them down
497
503
  if (!this.canvas.width || !this.canvas.height || this.canvas.width !== width || this.canvas.height !== height) {
498
504
  this.canvas.width = width;
@@ -502,25 +508,11 @@ export class Renderer {
502
508
  this.picker = new Picker(this.device, width, height);
503
509
  this.overlays.init(this.device.getDevice(), this.device.getFormat(), this.pipeline.getSampleCount());
504
510
  this.referenceImages.init(this.device.getDevice(), this.device.getFormat(), this.pipeline.getSampleCount());
505
- // PostProcessor is optional — if it fails (e.g. mobile GPU lacking
506
- // depth TEXTURE_BINDING), rendering still works without post-processing.
507
- try {
508
- this.postProcessor = new PostProcessor(this.device, {
509
- enableContactShading: true,
510
- contactRadius: 1.0,
511
- contactIntensity: 0.3,
512
- }, this.pipeline.getSampleCount());
513
- }
514
- catch (e) {
515
- console.warn('[Renderer] PostProcessor init failed (post-processing disabled):', e);
516
- this.postProcessor = null;
517
- }
518
- 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);
519
512
  // Compute pipeline for the BIM↔scan deviation heatmap. Lazily
520
513
  // owns the per-triangle BVH GPU buffers; idle until the first
521
514
  // `computeDeviations` call.
522
515
  this.deviationComputer.init(this.device.getDevice());
523
- this.edlPass = new EdlPass(this.device, this.pipeline.getSampleCount());
524
516
  this.camera.setAspect(width / height);
525
517
  // Update picking manager with initialized picker
526
518
  this.pickingManager.setPicker(this.picker);
@@ -707,6 +699,7 @@ export class Renderer {
707
699
  }
708
700
  this.deviceLossSequence++;
709
701
  this.deviceLost = true;
702
+ cancelRendererColorFrame(this);
710
703
  this.recovery.lostReferenceImages = this.referenceImages.hasImages();
711
704
  this.referenceImages.destroy();
712
705
  this.deviceLostGeneration = this.initGeneration;
@@ -766,11 +759,13 @@ export class Renderer {
766
759
  // regions really do have such a source on a HEALTHY device: the outer
767
760
  // one runs `scene.restoreAllEvicted()` for capture frames, the encode
768
761
  // one builds visibility sub-batches through
769
- // `scene.getOrCreatePartialBatch()`, and both allocate via
770
- // `createBuffer({ mappedAtCreation: true })`, which throws a plain
771
- // `RangeError` under host memory pressure — the failure
772
- // `gpu-upload-guard` documents verbatim. Latching there would kill the
773
- // viewport for a failure whose blast radius should be one frame, and
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
774
769
  // would raise a false "graphics device was lost" toast plus false
775
770
  // `device_lost` telemetry on top.
776
771
  //
@@ -879,38 +874,16 @@ export class Renderer {
879
874
  * chunks will arrive (currently a no-op but kept for symmetry).
880
875
  */
881
876
  beginPointCloudStream(meta) {
882
- if (this.deviceLost)
883
- throw rendererDeviceLostError();
884
- if (!this.pointCloudRenderer) {
885
- throw new Error('Renderer not initialized. Call init() first.');
886
- }
887
- const handle = this.pointCloudRenderer.beginAsset(meta);
888
- this.pointCloudStreamEpochs.set(handle.id, this.pointCloudStreamEpoch);
889
- return handle;
890
- }
891
- currentPointCloudStreamRenderer(handle) {
892
- if (this.deviceLost || !this.pointCloudRenderer
893
- || this.pointCloudStreamEpochs.get(handle.id) !== this.pointCloudStreamEpoch)
894
- throw rendererDeviceLostError();
895
- return this.pointCloudRenderer;
877
+ return beginPointCloudStreamImpl(this, meta);
896
878
  }
897
879
  appendPointCloudChunk(handle, chunk) {
898
- this.currentPointCloudStreamRenderer(handle).appendChunk(handle, chunk);
899
- this.modelBoundsTracker.expandForPointClouds();
900
- this.camera.setSceneBounds(this.modelBounds);
901
- this.requestRender();
880
+ appendPointCloudChunkImpl(this, handle, chunk);
902
881
  }
903
882
  endPointCloudStream(handle) {
904
- this.currentPointCloudStreamRenderer(handle).endAsset(handle);
905
- this.requestRender();
883
+ endPointCloudStreamImpl(this, handle);
906
884
  }
907
885
  removePointCloudAsset(handle) {
908
- if (!this.pointCloudStreamEpochs.delete(handle.id))
909
- return;
910
- this.pointCloudRenderer?.removeAsset(handle);
911
- // Bounds may have shrunk — recompute from scratch so fit-to-view
912
- // and section-plane sliders see fresh extents.
913
- this.refreshPlacementBounds();
886
+ removePointCloudAssetImpl(this, handle);
914
887
  }
915
888
  /**
916
889
  * Reassign a streamed point-cloud's expressId after upload. Use
@@ -958,6 +931,13 @@ export class Renderer {
958
931
  this.clearCaches();
959
932
  this.refreshPlacementBounds();
960
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
+ }
961
941
  refreshPlacementBounds() {
962
942
  this.modelBoundsTracker.recompute();
963
943
  this.camera.setSceneBounds(this.modelBounds);
@@ -1157,65 +1137,7 @@ export class Renderer {
1157
1137
  * collector drops them.
1158
1138
  */
1159
1139
  shadowOccluderBatches(options, device, hasVisibilityFiltering) {
1160
- const all = this.scene.getBatchedMeshes();
1161
- if (!hasVisibilityFiltering) {
1162
- for (const batch of all)
1163
- this.noteShadowOccluderResidency(batch);
1164
- return all;
1165
- }
1166
- const pipeline = this.pipeline;
1167
- const out = [];
1168
- for (const batch of all) {
1169
- const vis = classifyBatchVisibility(batch.expressIds, options.hiddenIds, options.isolatedIds);
1170
- if (vis.kind === 'none')
1171
- continue; // fully hidden → does not cast
1172
- if (vis.kind === 'all') {
1173
- this.noteShadowOccluderResidency(batch);
1174
- out.push(batch); // fully visible → its own buffers
1175
- continue;
1176
- }
1177
- // Partially hidden. Transparent parents don't cast (collector's alpha
1178
- // filter) — skip rather than build a wasted, divergent-key clone.
1179
- if (batch.color[3] < DEFAULT_MIN_CAST_ALPHA)
1180
- continue;
1181
- // The partial sub-batch is built from the PARENT's CPU meshData, so a
1182
- // cold parent yields nothing until it is restored. Queue that restore
1183
- // here too: the colour pass only queues it for parents inside its own
1184
- // frustum, but an up-sun occluder behind the camera still has to cast.
1185
- this.noteShadowOccluderResidency(batch);
1186
- // Opaque partial: reuse the colour pass's cached sub-batch. The key is
1187
- // visibility-content-independent; `_partialBatchEpoch` invalidates it on
1188
- // any hide/isolate or override change, so the clone is always current.
1189
- // Without a pipeline the renderer isn't drawing, so skip (the shadow
1190
- // pass won't run either); casting the whole parent would be wrong.
1191
- if (!pipeline)
1192
- continue;
1193
- const sub = this.scene.getOrCreatePartialBatch(`${batch.colorKey}:${batch.id}`, batch.colorKey, vis.visibleIds, device, pipeline, this._partialBatchEpoch);
1194
- // A cold parent yields an empty partial (its residency restore is queued
1195
- // above); skip this frame — the collector drops zero-index draws anyway,
1196
- // and the subset casts once resident.
1197
- if (sub && sub.indexCount > 0)
1198
- out.push(sub);
1199
- }
1200
- return out;
1201
- }
1202
- /**
1203
- * Keep a shadow occluder batch resident so it does not thin out silently on
1204
- * large models under the GPU residency budget (#2670 review). The depth pass
1205
- * reads these batches' buffers, but that read did not count as usage, so an
1206
- * up-sun occluder outside the colour frustum aged into an eviction candidate.
1207
- * Transparent batches never cast (the collector's alpha filter), so their
1208
- * residency is irrelevant here.
1209
- */
1210
- noteShadowOccluderResidency(batch) {
1211
- if (batch.color[3] < DEFAULT_MIN_CAST_ALPHA)
1212
- return;
1213
- if (batch.gpuResident === false) {
1214
- this.scene.requestBatchResidency(batch);
1215
- }
1216
- else {
1217
- this.scene.recordBatchDrawn(batch);
1218
- }
1140
+ return shadowOccluderBatches(this.scene, options, device, hasVisibilityFiltering, this.pipeline, this._partialBatchEpoch);
1219
1141
  }
1220
1142
  /** Guarded entry point (#4885) for the unguarded body below. */
1221
1143
  createMeshFromData(meshData) {
@@ -1234,18 +1156,17 @@ export class Renderer {
1234
1156
  const interleaved = new Float32Array(interleavedRaw);
1235
1157
  const interleavedU32 = new Uint32Array(interleavedRaw);
1236
1158
  // Build this individual mesh (selection highlight + GPU object-id picker)
1237
- // in ABSOLUTE world space so it renders with an identity model matrix.
1159
+ // in the same small local frame as its source batch.
1238
1160
  // CRITICAL: replicate the BATCH's exact two-step f32 path so the highlight
1239
1161
  // is bit-coincident with its source surface (no z-fight, no depth bias):
1240
1162
  // batch stores s = f32(local + (origin - sharedOrigin)) [merge]
1241
- // batch shader world = f32(f32(sharedOrigin) + s) [draw]
1242
- // We compute the same `world` here. When there's no shared origin yet
1243
- // (legacy / pre-batch), fall back to a plain f64 fold (local + origin).
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.
1244
1166
  const o = meshData.origin;
1245
1167
  const so = this.scene.getSharedFrameOrigin(meshData.modelIndex, meshData);
1246
1168
  const ox = o ? o[0] : 0, oy = o ? o[1] : 0, oz = o ? o[2] : 0;
1247
1169
  const fr = Math.fround;
1248
- const sox = so ? fr(so[0]) : null, soy = so ? fr(so[1]) : 0, soz = so ? fr(so[2]) : 0;
1249
1170
  const dx = so ? (ox - so[0]) : ox, dy = so ? (oy - so[1]) : oy, dz = so ? (oz - so[2]) : oz;
1250
1171
  // Quantized batches (issue #1682 phase 6) render lattice-snapped
1251
1172
  // positions: the shader's quantMin + q*step is exactly the lattice
@@ -1262,9 +1183,9 @@ export class Renderer {
1262
1183
  for (let i = 0; i < vertexCount; i++) {
1263
1184
  const base = i * 7;
1264
1185
  const posBase = i * 3;
1265
- interleaved[base] = so ? fr(sox + snap(fr(p[posBase] + dx))) : snap(p[posBase] + dx);
1266
- interleaved[base + 1] = so ? fr(soy + snap(fr(p[posBase + 1] + dy))) : snap(p[posBase + 1] + dy);
1267
- interleaved[base + 2] = so ? fr(soz + snap(fr(p[posBase + 2] + dz))) : snap(p[posBase + 2] + dz);
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]);
1268
1189
  const hasNormals = meshData.normals.length > 0;
1269
1190
  interleaved[base + 3] = hasNormals ? meshData.normals[posBase] : 0;
1270
1191
  interleaved[base + 4] = hasNormals ? meshData.normals[posBase + 1] : 0;
@@ -1294,7 +1215,9 @@ export class Renderer {
1294
1215
  usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
1295
1216
  });
1296
1217
  device.queue.writeBuffer(indexBuffer, 0, meshData.indices);
1297
- // Add to scene with identity transform (positions already in world space).
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.
1298
1221
  // Flagged `hydrated` so it can be freed when its entity leaves the
1299
1222
  // selection — these duplicate geometry already drawn by a batch and would
1300
1223
  // otherwise accumulate + double-draw (see Scene.disposeHydratedMeshesExcept).
@@ -1309,7 +1232,14 @@ export class Renderer {
1309
1232
  vertexBuffer,
1310
1233
  indexBuffer,
1311
1234
  indexCount: meshData.indices.length,
1312
- transform: MathUtils.identity(),
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],
1313
1243
  color: meshData.color,
1314
1244
  hydrated: true,
1315
1245
  });
@@ -1469,6 +1399,7 @@ export class Renderer {
1469
1399
  // emit a stream of validation errors. Stay quiet until re-init.
1470
1400
  if (this.deviceLost) {
1471
1401
  this._renderSkipCount++;
1402
+ cancelRendererColorFrame(this);
1472
1403
  return;
1473
1404
  }
1474
1405
  try {
@@ -1480,6 +1411,7 @@ export class Renderer {
1480
1411
  // as "did not fail" — see `frameContainedThrow`.
1481
1412
  if (!this.frameContainedThrow)
1482
1413
  this.consecutiveDegradedFrames = 0;
1414
+ retryRendererColorFrame(this, () => this.requestRender());
1483
1415
  }
1484
1416
  catch (error) {
1485
1417
  // Safari (26.5) reports device loss SYNCHRONOUSLY: a call against a
@@ -1495,6 +1427,7 @@ export class Renderer {
1495
1427
  // host down with us".
1496
1428
  this._renderSkipCount++;
1497
1429
  this.containFrameThrow(error, 'frame');
1430
+ cancelRendererColorFrame(this);
1498
1431
  }
1499
1432
  }
1500
1433
  /**
@@ -1506,23 +1439,16 @@ export class Renderer {
1506
1439
  this._renderSkipCount++;
1507
1440
  return;
1508
1441
  }
1509
- // Validate canvas dimensions
1510
- // Align width to 64 pixels for WebGPU texture row alignment (256 bytes / 4 bytes per pixel)
1511
- // and clamp both axes to the GPU's max 2D texture dimension. Some hosts (e.g. tall iframes
1512
- // on high-DPR displays) can produce canvas dimensions that exceed 8192 and would otherwise
1513
- // make every depth/colour texture allocation a validation error.
1514
- const rect = this.canvas.getBoundingClientRect();
1515
- const maxDim = this.device.getMaxTextureDimension();
1516
- const rawWidth = Math.max(1, Math.floor(rect.width));
1517
- const widthAligned = Math.max(64, Math.floor(rawWidth / 64) * 64);
1518
- const width = Math.min(widthAligned, Math.floor(maxDim / 64) * 64);
1519
- const rawHeight = Math.max(1, Math.floor(rect.height));
1520
- const height = Math.min(rawHeight, maxDim);
1521
- // Skip rendering if canvas is too small
1522
- 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) {
1523
1447
  this._renderSkipCount++;
1524
1448
  return;
1525
1449
  }
1450
+ const { width, height } = measured;
1451
+ this.pixelRatio = measured.pixelRatio;
1526
1452
  // Update canvas pixel dimensions if needed
1527
1453
  const dimensionsChanged = this.canvas.width !== width || this.canvas.height !== height;
1528
1454
  if (dimensionsChanged) {
@@ -1546,6 +1472,13 @@ export class Renderer {
1546
1472
  }
1547
1473
  const device = this.device.getDevice();
1548
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);
1549
1482
  // Frame stats (issue #1682): geometry draw calls + per-frame cull
1550
1483
  // outcomes, snapshotted into _lastFrameStats before queue.submit.
1551
1484
  let frameDrawCalls = 0;
@@ -1585,11 +1518,8 @@ export class Renderer {
1585
1518
  const edgeIntensity = Math.min(3.0, Math.max(0.0, visualEnhancement.edgeContrast.intensity));
1586
1519
  const edgeEnabledU32 = edgeEnabled ? 1 : 0;
1587
1520
  const edgeIntensityMilliU32 = Math.round(edgeIntensity * 1000);
1588
- const contactEnabled = effectsLive && visualEnhancement.enabled && visualEnhancement.contactShading.quality !== 'off';
1589
- const separationEnabled = effectsLive && visualEnhancement.enabled
1590
- && visualEnhancement.separationLines.enabled
1591
- && visualEnhancement.separationLines.quality !== 'off';
1592
- const needsObjectIdPass = contactEnabled || separationEnabled;
1521
+ // Only the separation lines read the object-id attachment after the pass.
1522
+ const needsObjectIdPass = livePostEffects(visualEnhancement, effectsLive).separationLines;
1593
1523
  // Check if visibility filtering is active
1594
1524
  const hasHiddenFilter = options.hiddenIds && options.hiddenIds.size > 0;
1595
1525
  const hasIsolatedFilter = options.isolatedIds !== null && options.isolatedIds !== undefined;
@@ -1697,8 +1627,7 @@ export class Renderer {
1697
1627
  // Frustum culling (if enabled and spatial index available)
1698
1628
  if (options.enableFrustumCulling && options.spatialIndex) {
1699
1629
  try {
1700
- const frustum = FrustumUtils.fromViewProjMatrix(viewProj);
1701
- const visibleIds = new Set(options.spatialIndex.queryFrustum(frustum));
1630
+ const visibleIds = new Set(options.spatialIndex.queryFrustum(sourceCullFrustum));
1702
1631
  meshes = meshes.filter(mesh => visibleIds.has(mesh.expressId));
1703
1632
  }
1704
1633
  catch (error) {
@@ -1739,12 +1668,15 @@ export class Renderer {
1739
1668
  }
1740
1669
  return; // Skip this frame, context will be reconfigured next frame
1741
1670
  }
1671
+ let colorCapture = null;
1672
+ let colorReadback = null;
1742
1673
  try {
1743
1674
  const clearColor = options.clearColor
1744
1675
  ? (Array.isArray(options.clearColor)
1745
1676
  ? { r: options.clearColor[0], g: options.clearColor[1], b: options.clearColor[2], a: options.clearColor[3] }
1746
1677
  : options.clearColor)
1747
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());
1748
1680
  const textureView = currentTexture.createView();
1749
1681
  const objectIdView = this.pipeline.getObjectIdTextureView();
1750
1682
  // Separate meshes into opaque and transparent
@@ -1818,6 +1750,8 @@ export class Renderer {
1818
1750
  for (const mesh of allMeshes) {
1819
1751
  if (mesh.uniformBuffer) {
1820
1752
  meshBuf.set(viewProj, 0);
1753
+ relativeToEyeFrame.packUniforms(meshBuf, MESH_UNIFORM_OFFSET.rteViewProj);
1754
+ packRteCameraOrigin(relativeToEyeFrame, meshBuf);
1821
1755
  meshBuf.set(mesh.transform.m, 16);
1822
1756
  // Check if mesh is selected (single or multi-selection)
1823
1757
  // For multi-model support: also check modelIndex if provided
@@ -1858,6 +1792,13 @@ export class Renderer {
1858
1792
  clipBit;
1859
1793
  meshFlags[2] = edgeEnabledU32;
1860
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;
1861
1802
  device.queue.writeBuffer(mesh.uniformBuffer, 0, meshBuf);
1862
1803
  }
1863
1804
  }
@@ -1887,8 +1828,16 @@ export class Renderer {
1887
1828
  else {
1888
1829
  this.shadowPass.setResolution(resolution);
1889
1830
  }
1890
- const boundsMin = [bounds.min.x, bounds.min.y, bounds.min.z];
1891
- const boundsMax = [bounds.max.x, bounds.max.y, bounds.max.z];
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
+ ];
1892
1841
  // Lateral shadow fit. AT REST: fit to the camera frustum
1893
1842
  // clipped to the model (maintainer #1) so a small building on
1894
1843
  // a large site keeps sharp shadows instead of spending the
@@ -1911,9 +1860,13 @@ export class Renderer {
1911
1860
  aspect: this.canvas.height > 0 ? this.canvas.width / this.canvas.height : 1,
1912
1861
  ortho: this.camera.getProjectionMode() === 'orthographic',
1913
1862
  orthoHalfHeight: this.camera.getOrthoSize(),
1914
- boundsMin,
1915
- boundsMax,
1916
- }) ?? undefined;
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
+ }));
1917
1870
  }
1918
1871
  const sun = resolveEnvironment(options.environment).sunDirection;
1919
1872
  const fit = fitSunLightMatrix({ sunDirection: sun, boundsMin, boundsMax, focusCorners });
@@ -1948,7 +1901,7 @@ export class Renderer {
1948
1901
  box: options.clipBox?.enabled
1949
1902
  ? { min: options.clipBox.min, max: options.clipBox.max }
1950
1903
  : null,
1951
- });
1904
+ }, { cameraWorld: shadowEye });
1952
1905
  // Shadow uniform: light matrix + sampling params. The kernel
1953
1906
  // width follows the sun's angular size (physical, ~0.53°
1954
1907
  // like Blender's Sun lamp). Bias scales with the kernel: a
@@ -1958,7 +1911,7 @@ export class Renderer {
1958
1911
  const texelWorld = (2 * fit.orthoHalfWidth) / resolution;
1959
1912
  const sunAngleDeg = shadowOpts.sunAngleDeg ?? 0.53;
1960
1913
  const pcfRadius = Math.min(Math.max(sunAngleDeg * 3.0, 0.75), 8.0);
1961
- const normalBias = texelWorld * (2.0 + pcfRadius);
1914
+ const normalBias = resolveShadowNormalBiasMetres(texelWorld, pcfRadius);
1962
1915
  const s = this.shadowScratch;
1963
1916
  s.set(fit.lightViewProj.m, 0);
1964
1917
  s[16] = 1 / resolution; // texelSize
@@ -2094,7 +2047,7 @@ export class Renderer {
2094
2047
  if (allBatchedMeshes.length > 0 || this.scene.getTexturedMeshes().length > 0 || this.scene.getInstancedTemplates().length > 0) {
2095
2048
  // Frustum culling for batched meshes - skip entire batches outside the camera view
2096
2049
  // This is the primary performance optimization for large models (200K+ meshes)
2097
- const frustum = FrustumUtils.fromViewProjMatrix(viewProj);
2050
+ const frustum = rteCullFrustum;
2098
2051
  // Contribution culling (issue #1682): skip batches whose world
2099
2052
  // AABB projects below a pixel threshold. Disabled unless the
2100
2053
  // caller opts in via options.contributionCull; the threshold is
@@ -2121,7 +2074,7 @@ export class Renderer {
2121
2074
  mode: this.camera.getProjectionMode(),
2122
2075
  fovYRadians: this.camera.getFOV(),
2123
2076
  orthoHalfHeight: this.camera.getOrthoSize(),
2124
- viewportHeightPx: this.canvas.height,
2077
+ viewportHeightPx: this.canvas.height / this.pixelRatio, // CSS px, like the thresholds
2125
2078
  };
2126
2079
  }
2127
2080
  // Per-batch visibility (only meaningful while filtering is active).
@@ -2176,7 +2129,7 @@ export class Renderer {
2176
2129
  // Frustum culling: skip batches entirely outside the camera view
2177
2130
  if (batch.bounds) {
2178
2131
  const batchAABB = { min: batch.bounds.min, max: batch.bounds.max };
2179
- if (!FrustumUtils.isAABBVisible(frustum, batchAABB)) {
2132
+ if (!isRteAabbVisible(frustum, batchAABB, rteCullCamera)) {
2180
2133
  frameBatchesFrustumCulled++;
2181
2134
  continue; // Entire batch is off-screen
2182
2135
  }
@@ -2272,6 +2225,8 @@ export class Renderer {
2272
2225
  const tpl = this.uniformScratch;
2273
2226
  const tplFlags = this.uniformScratchU32;
2274
2227
  tpl.set(viewProj, 0);
2228
+ relativeToEyeFrame.packUniforms(tpl, MESH_UNIFORM_OFFSET.rteViewProj);
2229
+ packRteCameraOrigin(relativeToEyeFrame, tpl);
2275
2230
  // Identity model matrix (positions already in world space)
2276
2231
  tpl[16] = 1;
2277
2232
  tpl[17] = 0;
@@ -2322,6 +2277,11 @@ export class Renderer {
2322
2277
  tplClipBit;
2323
2278
  tplFlags[2] = edgeEnabledU32;
2324
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);
2325
2285
  // Helper function to render a batch — patches color into the shared template
2326
2286
  const renderBatch = (batch) => {
2327
2287
  if (!batch.bindGroup || !batch.uniformBuffer)
@@ -2341,6 +2301,11 @@ export class Renderer {
2341
2301
  tpl[28] = o ? o[0] : 0;
2342
2302
  tpl[29] = o ? o[1] : 0;
2343
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;
2344
2309
  // Quantized dequantization params (issue #1682 phase 6);
2345
2310
  // zeroed for f32 batches (their pipelines ignore them).
2346
2311
  const qz = batch.quantized;
@@ -2420,7 +2385,7 @@ export class Renderer {
2420
2385
  const kept = [];
2421
2386
  for (const it of instancedTemplates) {
2422
2387
  if (it.bounds) {
2423
- if (!FrustumUtils.isAABBVisible(frustum, it.bounds)) {
2388
+ if (!isRteAabbVisible(frustum, it.bounds, rteCullCamera)) {
2424
2389
  frameInstancedFrustumCulled++;
2425
2390
  continue;
2426
2391
  }
@@ -2438,11 +2403,12 @@ export class Renderer {
2438
2403
  visibleInstanced = kept;
2439
2404
  }
2440
2405
  if (visibleInstanced.length > 0) {
2406
+ uploadInstancedRteDeltas(device, visibleInstanced, relativeToEyeFrame.getCameraWorld());
2441
2407
  // Opaque instanced pass. flags.x bit 2 marks "instanced pass" so the
2442
2408
  // shader routes per-instance opacity: opaque (or selected) occurrences
2443
2409
  // draw here; translucent ones (lens/x-ray/compare overrides) are
2444
2410
  // discarded and drawn in the transparent sub-pass below.
2445
- this.pipeline.writeRawUniforms(tpl, 0x4);
2411
+ this.pipeline.writeRawUniforms(tpl, MESH_FLAG_RTE_DRAWABLE | 0x4);
2446
2412
  pass.setPipeline(this.pipeline.getInstancedPipeline());
2447
2413
  pass.setBindGroup(0, this.pipeline.getBindGroup());
2448
2414
  pass.setBindGroup(1, this.pipeline.getEnvironmentBindGroup());
@@ -2502,6 +2468,8 @@ export class Renderer {
2502
2468
  tpl[28] = tm.origin[0];
2503
2469
  tpl[29] = tm.origin[1];
2504
2470
  tpl[30] = tm.origin[2];
2471
+ relativeToEyeFrame.packDrawableOrigin(tm.origin, tpl, MESH_UNIFORM_OFFSET.drawableDelta);
2472
+ tplFlags[0] |= MESH_FLAG_RTE_DRAWABLE;
2505
2473
  tpl[32] = txOverride ? txOverride[0] : tm.color[0];
2506
2474
  tpl[33] = txOverride ? txOverride[1] : tm.color[1];
2507
2475
  tpl[34] = txOverride ? txOverride[2] : tm.color[2];
@@ -2648,7 +2616,7 @@ export class Renderer {
2648
2616
  if (visibleInstanced.length > 0 &&
2649
2617
  this.scene.hasTransparentInstances() &&
2650
2618
  instancedTransparentPipeline !== null) {
2651
- this.pipeline.writeRawUniforms(tpl, 0x4 | 0x8);
2619
+ this.pipeline.writeRawUniforms(tpl, MESH_FLAG_RTE_DRAWABLE | 0x4 | 0x8);
2652
2620
  pass.setPipeline(instancedTransparentPipeline);
2653
2621
  pass.setBindGroup(0, this.pipeline.getBindGroup());
2654
2622
  pass.setBindGroup(1, this.pipeline.getEnvironmentBindGroup());
@@ -2705,6 +2673,10 @@ export class Renderer {
2705
2673
  tplClipBit;
2706
2674
  tplFlags[2] = edgeEnabledU32;
2707
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;
2708
2680
  device.queue.writeBuffer(mesh.uniformBuffer, 0, tpl);
2709
2681
  pass.setBindGroup(0, mesh.bindGroup);
2710
2682
  pass.setVertexBuffer(0, mesh.vertexBuffer);
@@ -2765,6 +2737,10 @@ export class Renderer {
2765
2737
  tplClipBit;
2766
2738
  tplFlags[2] = edgeEnabledU32;
2767
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;
2768
2744
  device.queue.writeBuffer(mesh.uniformBuffer, 0, tpl);
2769
2745
  pass.setPipeline(this.pipeline.getSelectionPipeline());
2770
2746
  pass.setBindGroup(0, mesh.bindGroup);
@@ -2812,65 +2788,55 @@ export class Renderer {
2812
2788
  // viewport size to convert pixel sizes into clip-space offsets.
2813
2789
  if (this.pointCloudRenderer && this.pointCloudRenderer.hasAssets()) {
2814
2790
  this.pointCloudRenderer.draw(pass, {
2815
- viewProj,
2791
+ viewProj: relativeToEyeFrame.getViewProjection().m,
2792
+ relativeToEyeFrame,
2816
2793
  sectionPlane: sectionPlaneData
2817
2794
  ? { ...sectionPlaneData, flipped: options.sectionPlane?.flipped === true }
2818
2795
  : null,
2819
- viewport: { width: this.canvas.width, height: this.canvas.height },
2796
+ clipBox: options.clipBox,
2797
+ viewport: { width: this.canvas.width / this.pixelRatio, height: this.canvas.height / this.pixelRatio },
2820
2798
  });
2821
2799
  }
2822
2800
  // Section-plane gizmo, 2D section cap and every standalone 3D
2823
2801
  // overlay (annotation / alignment / grid / DXF / clash / symbolic
2824
2802
  // text). One draw call into the pass — see RendererOverlays.draw().
2825
- this.referenceImages.draw(pass, viewProj);
2803
+ this.referenceImages.draw(pass, viewProj, relativeToEyeFrame.getViewProjection().m, relativeToEyeFrame.getCameraWorld());
2826
2804
  this.overlays.draw(pass, {
2827
2805
  options,
2828
2806
  viewProj,
2829
2807
  modelBounds: this.getModelBounds(),
2830
2808
  camera: this.camera,
2831
- canvasWidth: this.canvas.width,
2832
- 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(),
2833
2814
  });
2834
2815
  pass.end();
2835
- const canRunPostPass = (contactEnabled || separationEnabled)
2836
- && this.postProcessor !== null;
2837
- if (canRunPostPass && this.postProcessor) {
2838
- this.postProcessor.updateOptions({
2839
- enableContactShading: contactEnabled,
2840
- contactRadius: visualEnhancement.contactShading.radius,
2841
- contactIntensity: visualEnhancement.contactShading.intensity,
2842
- });
2843
- this.postProcessor.apply(encoder, {
2844
- targetView: textureView,
2845
- // Depth-only view required because depth24plus-stencil8
2846
- // cannot be sampled as texture_depth_* with aspect 'all'.
2847
- depthView: this.pipeline.getDepthOnlyTextureView(),
2848
- objectIdView: this.pipeline.getObjectIdTextureView(),
2849
- contactQuality: contactEnabled && visualEnhancement.contactShading.quality === 'high' ? 'high' : 'low',
2850
- radius: Math.min(3.0, Math.max(1.0, visualEnhancement.contactShading.radius)),
2851
- intensity: contactEnabled ? Math.min(1.0, Math.max(0.0, visualEnhancement.contactShading.intensity)) : 0.0,
2852
- separationQuality: visualEnhancement.separationLines.quality === 'high' ? 'high' : 'low',
2853
- separationRadius: Math.min(2.0, Math.max(1.0, visualEnhancement.separationLines.radius)),
2854
- separationIntensity: separationEnabled ? Math.min(1.0, Math.max(0.0, visualEnhancement.separationLines.intensity)) : 0.0,
2855
- enableSeparationLines: separationEnabled,
2856
- });
2857
- }
2858
- // Eye-Dome Lighting — runs AFTER contact/separation so it darkens
2859
- // every layer uniformly. Cheap (~9 depth taps), only active when
2860
- // there are point clouds in the scene and the user has enabled it.
2861
- if (this.edlPass
2862
- && this.edlOptions.enabled
2863
- && this.pointCloudRenderer?.hasAssets()) {
2864
- this.edlPass.apply(encoder, {
2865
- targetView: textureView,
2866
- depthView: this.pipeline.getDepthOnlyTextureView(),
2867
- }, {
2868
- strength: this.edlOptions.strength,
2869
- radiusPx: this.edlOptions.radiusPx,
2870
- highQuality: this.edlOptions.highQuality,
2871
- });
2872
- }
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);
2873
2834
  device.queue.submit([encoder.finish()]);
2835
+ if (colorReadback && colorCapture) {
2836
+ settleRendererColorFrameCapture(this, colorCapture, colorReadback);
2837
+ colorCapture = null;
2838
+ colorReadback = null;
2839
+ }
2874
2840
  this._lastFrameStats = {
2875
2841
  drawCalls: frameDrawCalls,
2876
2842
  batchesDrawn: frameBatchesDrawn,
@@ -2894,6 +2860,7 @@ export class Renderer {
2894
2860
  }
2895
2861
  }
2896
2862
  catch (error) {
2863
+ discardRendererColorFrameReadback(colorReadback);
2897
2864
  // Balance the validation scope if we threw before popping it above —
2898
2865
  // an unpopped scope would capture every later frame's errors silently.
2899
2866
  // drainErrorScope logs a pop rejection (device loss) rather than
@@ -2913,7 +2880,9 @@ export class Renderer {
2913
2880
  // typed-array views — plus one 5-argument call in
2914
2881
  // `point-cloud-uniforms.ts` whose offset and size are compile-time
2915
2882
  // constants matching its scratch array — so the spec's
2916
- // `OperationError` preconditions are unreachable; the one
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
2917
2886
  // `copyExternalImageToTexture` copies the glyph atlas's own
2918
2887
  // never-externally-drawn canvas at its full fixed size, so neither
2919
2888
  // `SecurityError` nor a zero-size `OperationError` can arise; and
@@ -2922,10 +2891,11 @@ export class Renderer {
2922
2891
  // draws, `finish`, `submit`, `createBindGroup`) reports failure as
2923
2892
  // an asynchronous `GPUValidationError` through the error scope, not
2924
2893
  // as a throw. The region's real healthy-device failure is
2925
- // `getOrCreatePartialBatch`'s `createBuffer({ mappedAtCreation:
2926
- // true })`, and that throws a `RangeError` — which is exactly why
2894
+ // `getOrCreatePartialBatch`'s CPU-side geometry merge running out of
2895
+ // host memory, and that throws a `RangeError` — which is exactly why
2927
2896
  // the discriminator keys on the TYPE and not on "a frame threw".
2928
2897
  this.containFrameThrow(error, 'encode');
2898
+ cancelRendererColorFrame(this);
2929
2899
  }
2930
2900
  }
2931
2901
  /**
@@ -3252,30 +3222,9 @@ export class Renderer {
3252
3222
  }
3253
3223
  return this.device.getDevice();
3254
3224
  }
3255
- /**
3256
- * Capture a screenshot of the current view
3257
- * Waits for GPU work to complete and captures exactly what's displayed
3258
- * @returns PNG data URL or null if capture failed
3259
- */
3260
- async captureScreenshot() {
3261
- if (!this.device.isInitialized()) {
3262
- console.warn('[Renderer] Cannot capture screenshot: not initialized');
3263
- return null;
3264
- }
3265
- try {
3266
- // Wait for any pending GPU work to complete before capturing
3267
- // This ensures we capture the fully rendered frame
3268
- const device = this.device.getDevice();
3269
- await device.queue.onSubmittedWorkDone();
3270
- // Capture exactly what's displayed on the canvas
3271
- const dataUrl = this.canvas.toDataURL('image/png');
3272
- return dataUrl;
3273
- }
3274
- catch (error) {
3275
- console.error('[Renderer] Screenshot capture failed:', error);
3276
- return null;
3277
- }
3278
- }
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()); }
3279
3228
  /**
3280
3229
  * Destroy the renderer and release all GPU resources.
3281
3230
  *
@@ -3310,6 +3259,7 @@ export class Renderer {
3310
3259
  destroy() {
3311
3260
  this.initGeneration++;
3312
3261
  this.destroyed = true;
3262
+ cancelRendererColorFrame(this);
3313
3263
  this.teardown();
3314
3264
  this.rejectReadyWaiters(rendererDestroyedError());
3315
3265
  }
@@ -3323,6 +3273,7 @@ export class Renderer {
3323
3273
  // go back to waiting. Set first: every release below is synchronous, but
3324
3274
  // the flag is what a caller holding a live reference actually reads.
3325
3275
  this.ready = false;
3276
+ cancelRendererColorFrame(this);
3326
3277
  // Scene mesh GPU buffers
3327
3278
  if (clearScene) {
3328
3279
  this.scene.clear();
@@ -3339,11 +3290,8 @@ export class Renderer {
3339
3290
  this.picker?.destroy();
3340
3291
  this.picker = null;
3341
3292
  this.pickingManager.setPicker(null);
3342
- // Post-processor uniform buffer
3343
- this.postProcessor?.destroy();
3344
- this.postProcessor = null;
3345
- this.edlPass?.destroy();
3346
- this.edlPass = null;
3293
+ this.postPasses?.destroy();
3294
+ this.postPasses = null;
3347
3295
  this.skyPass?.destroy();
3348
3296
  this.skyPass = null;
3349
3297
  this.shadowPass?.destroy();
@@ -3353,6 +3301,7 @@ export class Renderer {
3353
3301
  this.overlays.destroy();
3354
3302
  this.referenceImages.destroy();
3355
3303
  // Point cloud GPU resources
3304
+ this.pointCloudNextHandleId = this.pointCloudRenderer?.handleIds?.current() ?? this.pointCloudNextHandleId;
3356
3305
  this.pointCloudStreamEpoch++;
3357
3306
  this.pointCloudStreamEpochs.clear();
3358
3307
  this.pointCloudRenderer?.clear();