perf(raster-tileset): Reduce re-renders of RasterLayer for same per-tile transforms#543
Merged
Merged
Conversation
…ileMetadata `AffineTilesetLevel.tileTransform` and `TileMatrixAdaptor.tileTransform` each constructed two new arrow functions per call. `RasterTileLayer. _renderSubLayers` invoked them per tile on every render, so the returned references churned constantly. That instability tripped `reprojectionFnsChanged` in `RasterLayer.updateState`, which re-ran `_generateMesh`, which produced a fresh `state.mesh` wrapper, which tripped `props.mesh !== oldProps.mesh` in `SimpleMeshLayer.updateState`, which destroyed and rebuilt the GPU `Model` — incurring full shader assembly per tile per render. Move transform computation upstream into `RasterTileset2D.getTileMetadata`, which deck.gl calls once per tile at construction time. The resulting forward/inverse functions live on the tile's metadata, so consumers receive the same references across all renders for the tile's lifetime. When the tile is evicted from `TileLayer`'s cache, the transforms go with it — lifetime is automatically correct, and the cache size tracks `TileLayer.maxCacheSize` rather than growing unbounded. `_renderSubLayers` now reads `tile.forwardTransform` / `tile.inverseTransform` instead of recomputing per render. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
23a79e9 to
9049c0f
Compare
7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
I validated by hand with some console.log statements that this avoids the recreation of the MeshLayer model for the raster layer for each tile, at least in the cog-basic example when toggling the debug layer.
I think the core issue was that with new arrow functions, every change to the tile would cause a new re-render of the RasterLayer.
Summary
Compute each tile's
forwardTransform/inverseTransformonce at tile construction (insideRasterTileset2D.getTileMetadata) and attach them to the tile'sTileMetadata.RasterTileLayer._renderSubLayersreads them off the tile instead of recomputing on every render.Why
Before this change, every call to
level.tileTransform(col, row)constructed two fresh arrow functions:RasterTileLayer._renderSubLayerscalls this on every render for every visible tile, so the references churned constantly. That instability trippedreprojectionFnsChangedinRasterLayer.updateState, which re-ran_generateMesh, which produced a freshstate.meshwrapper, which trippedprops.mesh !== oldProps.meshinSimpleMeshLayer.updateState, which destroyed and rebuilt the GPUModel— incurring full shader assembly (assembleGLSLShaderPair,extractShaderUniformBlockFieldNames, etc.) per tile per render.In a usgs-topo mosaic with ~411 active tile sublayers, this dominated frame time during any layer prop change (e.g. toggling a debug overlay).
Why tile-attached vs. level-cached
Earlier draft of this PR cached
tileTransform(col, row)on the level itself. That worked, but the cache had no eviction — entries accumulated for every(col, row)ever queried across the level's lifetime.Attaching to
TileMetadatainstead gives correct lifetime semantics for free:TileLayer→getTileMetadataruns once → transforms computed onceTileLayer's cache, every render reads the same references off the tileTileLayer's cache, the transforms go with itTileLayer.maxCacheSize— no separate growth concernTransforms are also co-located with the
(x, y, z)identity that defines them, which is conceptually cleaner.Verified
In
cog-basicwith diagnostic logs in place: toggling the debug overlay used to fireRasterLayer._generateMeshandMeshTextureLayer.getShadersonce per tile per toggle. With this change, neither fires — onlyRasterLayer.renderLayersruns, and it's cheap because it just reads stable references off the tile.Test plan
RasterTileset2D.getTileMetadataattaches functioningforwardTransform/inverseTransformto the metadatausgs-topo-cutlineexampleSupersedes
Replaces #540 (mesh-shape refactor in
RasterLayer), which was treating a symptom of this same instability. #540 should be closed in favor of this PR. #541 (shader-assembler memoization) becomes optional defensive caching with this fix in place.🤖 Generated with Claude Code