Skip to content

perf: faster chunk visibility testing#665

Merged
aganders3 merged 4 commits into
mainfrom
aganders3/bound-chunk-visibility
May 15, 2026
Merged

perf: faster chunk visibility testing#665
aganders3 merged 4 commits into
mainfrom
aganders3/bound-chunk-visibility

Conversation

@aganders3

Copy link
Copy Markdown
Collaborator

Summary

This restructures the chunks array to Chunk[lod][t][c][z][y][x] and uses direct indexing to reduce the total number of intersection tests used to determine chunk visibility and priority.

On main, updateChunksForImage iterates every chunk at the current time index testing each against the view bounds, channel filter, and LOD predicate. This is O(chunks_per_timepoint) per-frame work. With the new layout, chunkIndexRange(bounds, lod) converts the world-space view bounds directly to a chunk-index range, and iterateChunksInBox walks only the chunks the view actually overlaps. Per-frame work now scales with the visible region instead of the dataset.

The fallback LOD runs through the same bounded path against the prefetch AABB, so in-view fallback chunks act as a backdrop while current-LOD chunks load, and the prefetch padding stays warm for short pans. Datasets whose coarsest LOD is still pretty large (e.g. OPS) no longer pay an O(all_fallback_lod_chunks) cost per frame.

With this refactor, getChunksToRender and allVisibleFallbackLODLoaded iterate chunkViewStates_ (the per-view output map, already reflecting the latest update) instead of re-scanning all chunks on every render call. They also no longer take sliceCoords, they just operate on the state established by the prior updateChunksFor{Image,Volume} call.

The volume path (updateChunksForVolume) is intentionally left unchanged, just re-expressed through the new iterators. Extending bound-visibility to the volume layer is a natural follow-up.

Related Issue

Closes #663

Tests & Checks

I instrumented the code with performance.measure and saw the per-frame visibility checking cost drop from 300ms+ to <1ms. I did not include these measurement spans in this PR because they add noise, but I think it would be helpful to instrument the code thoroughly in a follow-up to allow us to track regressions, etc. Possibly related to #602 #166 and other perf-related issues.

The difference is easily felt with the exaSPIM data in the main image_2d example. You can also see the FPS in the stats.js overlay go from 3-5fps to 100+fps. Here are before/after comparisons of zooming and panning.

on main

Screen.Recording.2026-05-13.at.9.35.17.AM.mov

with this PR

Screen.Recording.2026-05-13.at.9.34.41.AM.mov

@aganders3 aganders3 requested a review from shlomnissan May 13, 2026 14:59

@shlomnissan shlomnissan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@aganders3 aganders3 merged commit 6fc60b5 into main May 15, 2026
10 checks passed
@aganders3 aganders3 deleted the aganders3/bound-chunk-visibility branch May 15, 2026 18:09
@czi-github-helper

Copy link
Copy Markdown

🎉 This PR is included in version 0.22.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

aganders3 added a commit that referenced this pull request May 21, 2026
…dering case) (#667)

### Summary

When working with remote rendering, I noticed a bug I introduced in #665
that results in the volume layer stalling until the entire prefetch
queue drains.

That PR rewrote `updateChunksForVolume` and replaced the per-chunk
`isCurrentLOD = chunk.lod === currentLOD_` derivation with a
`markVolumeChunkVisible(chunk, isFallbackLOD)` helper that computes
`isCurrentLOD = !isFallbackLOD`. When `currentLOD === fallbackLOD`, only
the current-LOD pass runs, so every chunk is marked with
`isFallbackLOD=false, isCurrentLOD=true`. `computePriority` then returns
`visibleCurrent` (priority 2 under `createPlaybackPolicy`) instead of
`fallbackVisible` (priority 0) — and since `prefetchTime` is priority 1
in the same policy, the current frame's chunks land in the queue
*behind* the entire prefetch backlog.

This restores previous behavior by getting both `isFallbackLOD` and
`isCurrentLOD` from the chunk's own `chunk.lod` inside
`markVolumeChunkVisible`, so for single LOD (we configure this typically
by setting min LOD === max LOD in the policy) both flags are true and
`computePriority` returns `fallbackVisible` as it did before #665.

### Tests & Checks
I confirmed this fix restores the expected (previous) behavior in a reef
idetik-remote session.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Poor pan/zoom performance in ImageLayer for large images

2 participants