Implement key strategies for improving EXT_mesh_primitive_edge_visibility tile load time
@markschlosseratbentley is already working on this.
Since Aug 4, 2026.
- #13789 by @markschlosseratbentley — merged
- Dominant language
- JavaScript
- Stars
- 15.8k
- Forks
- 3.9k
- Avg merge
- 4d 6h
- Merged PRs (30d)
- 34
Description
### What happened?
## Problem
`EdgeVisibilityPipelineStage` runs whenever `primitive.edgeVisibility` is defined; `edgeDisplayMode` only gates the final draw-command push. So in the default `SURFACES_ONLY` mode (edges never rendered), every tile still pays for edge extraction/dedup, quad-expanded edge geometry build + GPU upload, and the scene edge MRT framebuffer.
Measured (CesiumJS main, Chromium/ANGLE Metal, vsync off, localhost, default options): Dataset A loads in 1082 ms without the extension vs 2802 ms with it (**+159%**), plus ~15% lower uncapped FPS. Across an 18-dataset AEC suite the load penalty is +7% to +159% (median ~+35%); gzipped payload delta is only ~2–10%, so the cost is processing, not transfer.
## Possible fixes
### Fix 1: build edge geometry lazily
Skip `EdgeVisibilityPipelineStage`/`EdgeDetectionPipelineStage` in `ModelRuntimePrimitive.configurePipeline` when `edgeDisplayMode === SURFACES_ONLY`, and call `resetDrawCommands()` from the `edgeDisplayMode` setter so edges build on demand when first enabled.
Prototype (3 runs, run 1 dropped): load 2802 → ~1420 ms (vs no-ext ~1169 ms; residual mostly noise); FPS and JS heap at parity with no-ext; sanity anchors identical. Enabling edge display later costs a one-time ~1.2 s rebuild for this dataset.
### Fix 2: cheaper edge dedup when edges are built
With edges displayed, `extractVisibleEdges` dominates the load profile, mostly from allocating a `"${small},${big}"` string per edge for the dedup `Set`. Numeric keys (`small * 2**32 + big`, exact for ≤2^21 vertices, string fallback beyond) cut its self time 800 → 300 ms and displayed-mode load 3.19 → 2.69 s. Further headroom exists (per-edge objects → typed arrays, buffer consolidation), but with fix 1 this path is opt-in only.
### Reproduction steps
1. Produce two variants of the same tileset: `no-ext` (no AEC extensions) and `edges` (`EXT_mesh_primitive_edge_visibility` only). Serve both over localhost to remove network variance.
2. Drive Chromium from Playwright with vsync uncapped and real GPU rendering:
```
--enable-gpu --disable-frame-rate-limit --disable-gpu-vsync
```
3. In the page, create a `Viewer` with globe/sky/atmosphere/sun disabled and `requestRenderMode: false`, then load the tileset with **default options** (i.e. `edgeDisplayMode` left at `SURFACES_ONLY` (edges hidden)).
4. Time from `Cesium3DTileset.fromUrl` to "all tiles loaded". Polling `tileset.tilesLoaded` until it is stable for ~10 consecutive frames is more reliable than the `allTilesLoaded` event, which doesn't always re-fire.
5. Fly to a fixed, deterministic camera view derived from the tileset bounding sphere (e.g. center, heading 45°, pitch -15°, range `max(radius * 0.08, 1)`) so both variants see identical content.
6. Sample per-frame deltas over a fixed window for FPS/percentiles, and read `tileset.statistics` for `geometryByteLength` and `trianglesSelected`.
7. Use a fresh browser per run and discard the first run (cold OS file cache can double load time). 3+ runs per variant.
8. Sanity check: `trianglesSelected`, `commands`, and selected-tile counts should match between the two variants; that confirms an apples-to-apples comparison.
Expected: the `edges` variant loads in roughly the same time as `no-ext`, since edges are not being displayed.
Actual: the `edges` variant takes substantially longer to load (up to ~2.6x here) and renders ~10–15% slower, despite no edges being drawn.
### Sandcastle example
_No response_
### Environment
Browser: Chromium via Playwright, ANGLE/Metal backend, macOS (Apple silicon), vsync disabled
CesiumJS Version: CesiumJS `main`
Operating System: macOS
### AI acknowledgment
- [x] I used AI to generate this issue report.
- [x] (If the above is checked) I have reviewed the AI-generated content before submitting.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.