Skip to content

Commit 9480a26

Browse files
kylebarronclaude
andauthored
perf(raster-tileset): Reduce re-renders of RasterLayer for same per-tile transforms (#543)
* perf(raster-tileset): attach per-tile forward/inverse transforms to TileMetadata `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> * More concise --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3e05a59 commit 9480a26

3 files changed

Lines changed: 80 additions & 6 deletions

File tree

packages/deck.gl-raster/src/raster-tile-layer/raster-tile-layer.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -380,12 +380,9 @@ export class RasterTileLayer<
380380
return debugLayers;
381381
}
382382

383-
const { x, y, z } = tile.index;
384-
const level = descriptor.levels[z];
385-
if (!level) {
386-
return debugLayers;
387-
}
388-
const { forwardTransform, inverseTransform } = level.tileTransform(x, y);
383+
// Access forwardTransform/inverseTransform from tile metadata so that
384+
// reference equality holds across renders.
385+
const { forwardTransform, inverseTransform } = tile;
389386
const tileResult = renderTile(props.data);
390387
if (!tileResult) {
391388
return debugLayers;

packages/deck.gl-raster/src/raster-tileset/raster-tileset-2d.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import type {
2121
Bounds,
2222
Corners,
2323
ProjectedBoundingBox,
24+
ProjectionFunction,
2425
TileIndex,
2526
ZRange,
2627
} from "./types.js";
@@ -55,6 +56,24 @@ export type TileMetadata = {
5556
* Tile height in pixels.
5657
*/
5758
tileHeight: number;
59+
60+
/**
61+
* Forward (tile-local pixel → CRS) transform for this tile.
62+
*
63+
* Stable across the tile's lifetime; computed once at tile creation. Stored
64+
* on the tile so downstream layers (e.g. `RasterTileLayer._renderSubLayers`)
65+
* receive a reference-stable function across renders, which is what
66+
* `RasterLayer`'s `reprojectionFnsChanged` check needs to avoid spurious mesh
67+
* regeneration.
68+
*/
69+
forwardTransform: ProjectionFunction;
70+
71+
/**
72+
* Inverse (CRS → tile-local pixel) transform.
73+
*
74+
* Same stability guarantees as {@link TileMetadata.forwardTransform}.
75+
*/
76+
inverseTransform: ProjectionFunction;
5877
};
5978

6079
/**
@@ -253,6 +272,9 @@ export class RasterTileset2D extends Tileset2D {
253272
...projectedBounds,
254273
);
255274

275+
const { forwardTransform, inverseTransform } =
276+
levelDescriptor.tileTransform(x, y);
277+
256278
return {
257279
bbox: {
258280
west,
@@ -269,6 +291,8 @@ export class RasterTileset2D extends Tileset2D {
269291
projectedCorners,
270292
tileWidth,
271293
tileHeight,
294+
forwardTransform,
295+
inverseTransform,
272296
};
273297
}
274298
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import type { _Tileset2DProps as Tileset2DProps } from "@deck.gl/geo-layers";
2+
import { compose, scale, translation } from "@developmentseed/affine";
3+
import { describe, expect, it } from "vitest";
4+
import { AffineTileset } from "../../src/raster-tileset/affine-tileset.js";
5+
import { AffineTilesetLevel } from "../../src/raster-tileset/affine-tileset-level.js";
6+
import { RasterTileset2D } from "../../src/raster-tileset/raster-tileset-2d.js";
7+
8+
const identity = (x: number, y: number): [number, number] => [x, y];
9+
10+
const PROJECTIONS = {
11+
projectTo3857: identity,
12+
projectFrom3857: identity,
13+
projectTo4326: identity,
14+
projectFrom4326: identity,
15+
};
16+
17+
function tilesetProps(): Tileset2DProps {
18+
return { getTileData: () => new Promise(() => {}) } as Tileset2DProps;
19+
}
20+
21+
describe("RasterTileset2D.getTileMetadata", () => {
22+
it("attaches per-tile forwardTransform/inverseTransform to TileMetadata", () => {
23+
const level = new AffineTilesetLevel({
24+
affine: compose(translation(100, 200), scale(10, -10)),
25+
arrayWidth: 8,
26+
arrayHeight: 8,
27+
tileWidth: 4,
28+
tileHeight: 4,
29+
mpu: 1,
30+
});
31+
const descriptor = new AffineTileset({
32+
levels: [level],
33+
...PROJECTIONS,
34+
});
35+
const tileset = new RasterTileset2D(tilesetProps(), descriptor);
36+
37+
const metadata = tileset.getTileMetadata({ x: 1, y: 1, z: 0 });
38+
39+
expect(typeof metadata.forwardTransform).toBe("function");
40+
expect(typeof metadata.inverseTransform).toBe("function");
41+
42+
// Tile (1,1) at pixel (0,0) should map to the CRS origin of that tile.
43+
// Tile is 4x4 pixels at 10 CRS units/pixel from origin (100, 200), Y flipped.
44+
const [x, y] = metadata.forwardTransform(0, 0);
45+
expect(x).toBeCloseTo(140, 10);
46+
expect(y).toBeCloseTo(160, 10);
47+
48+
// Round-trip via inverseTransform.
49+
const [px, py] = metadata.inverseTransform(x, y);
50+
expect(px).toBeCloseTo(0, 10);
51+
expect(py).toBeCloseTo(0, 10);
52+
});
53+
});

0 commit comments

Comments
 (0)