CesiumGS / CesiumGS/cesium

Implement key strategies for improving EXT_mesh_primitive_edge_visibility tile load time

Open
#13,671 2 comments 0 reactions 1 assignee View on GitHub

@markschlosseratbentley is already working on this.

Since Aug 4, 2026.

  • #13789 by @markschlosseratbentley — merged
category - 3d tiles type - bug
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.