Skip to content

Commit fb17bbd

Browse files
chrisgervangibgreenclaude
authored
feat(examples): add useDevicePixels toggle to basemap-browser (#10332)
* chore(core) Adopt luma CanvasContext * fix * chore(core): route canvas sizing through CanvasContext * fix(google-maps): update overlay style container * fix(core): sync attached gl drawing buffer on resize * prettier * docs: add agent merge guidance * fix(react): preserve element props typing * docs: expand merge readiness guidance * fix * test(core): isolate canvas context resize devices * Revert "test(core): isolate canvas context resize devices" This reverts commit b13922f. * test(core): avoid shared canvas context mutation * test(core): avoid shared picker canvas context mutation * fix(core): only forward explicit device pixel ratio * fix(core): avoid replaying default props into canvas context * fix(core): repopulate invalid picking color cache * Address comments * feat(examples): add useDevicePixels toggle to basemap-browser Adds a checkbox to test useDevicePixels override (toggles between true and 1.5) across all renderer configurations. This helps verify that custom DPR settings are correctly forwarded in attached-gl/interleaved mode paths. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(examples): use basemap pixelRatio for interleaved mode + add CDN test pages Pass pixelRatio to MapLibre/Mapbox Map constructor when useDevicePixels is set to a custom value, since the basemap owns the canvas in interleaved mode. Also adds standalone HTML test pages for validating useDevicePixels across deck.gl 9.1, 9.2, 9.3, and local builds. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(examples): persist useDevicePixels in URL params Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(examples): handle unsupported Mapbox pixel ratio * revert(examples): remove pixel ratio test pages * fix(examples): clarify unset pixel ratio override --------- Co-authored-by: Ib Green <ib@unfolded.ai> Co-authored-by: Ib Green <ib.green.home@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 7bcbbd2 commit fb17bbd

13 files changed

Lines changed: 116 additions & 19 deletions

File tree

examples/basemap-browser/src/config/build-config.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ export function buildConfig(
2424
dimensions: Dimensions,
2525
onViewStateChange?: ViewStateChangeCallback
2626
): Config {
27-
const {basemap, framework, interleaved, globe, multiView, maskDemo, stressTest} = dimensions;
27+
const {basemap, framework, interleaved, globe, multiView, maskDemo, stressTest, useDevicePixels} =
28+
dimensions;
2829

2930
// Validate dimensions (warnings only)
3031
const validation = validateDimensions(dimensions);
@@ -58,6 +59,7 @@ export function buildConfig(
5859
multiView,
5960
maskDemo,
6061
stressTest,
62+
useDevicePixels,
6163

6264
// Computed configuration
6365
mapStyle,

examples/basemap-browser/src/config/dimensions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ export const DEFAULT_DIMENSIONS: Dimensions = {
1414
globe: false,
1515
multiView: false,
1616
maskDemo: false,
17-
stressTest: 'none'
17+
stressTest: 'none',
18+
useDevicePixels: true
1819
};
1920

2021
/**

examples/basemap-browser/src/config/validation.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ const VALIDATION_RULES: ValidationRule[] = [
5555
message: 'Google Maps cannot render layers under map labels (no slot/beforeId support)',
5656
severity: 'info'
5757
})
58+
},
59+
60+
// Mapbox does not expose a pixel ratio override for its shared canvas
61+
{
62+
check: d => d.basemap === 'mapbox' && d.interleaved && d.useDevicePixels !== true,
63+
warning: () => ({
64+
dimension: 'useDevicePixels',
65+
message: 'Pixel ratio overrides have no effect with Mapbox in interleaved mode',
66+
severity: 'warning'
67+
})
5868
}
5969
];
6070

examples/basemap-browser/src/control-panel.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,20 @@ function getDimensionsFromUrl(): Partial<Dimensions> {
6868
result.stressTest = stressTest;
6969
}
7070

71+
if (params.has('useDevicePixels')) {
72+
const udp = params.get('useDevicePixels');
73+
if (udp === 'true') {
74+
result.useDevicePixels = true;
75+
} else if (udp === 'false') {
76+
result.useDevicePixels = false;
77+
} else {
78+
const num = Number(udp);
79+
if (Number.isFinite(num) && num > 0) {
80+
result.useDevicePixels = num;
81+
}
82+
}
83+
}
84+
7185
return result;
7286
}
7387

@@ -81,6 +95,7 @@ function setUrlFromDimensions(dimensions: Dimensions) {
8195
params.set('multiView', String(dimensions.multiView));
8296
params.set('maskDemo', String(dimensions.maskDemo));
8397
params.set('stressTest', dimensions.stressTest);
98+
params.set('useDevicePixels', String(dimensions.useDevicePixels));
8499
const newUrl = `${window.location.pathname}?${params.toString()}`;
85100
window.history.replaceState({}, '', newUrl);
86101
}
@@ -106,6 +121,12 @@ export default function ControlPanel({onConfigChange}: ControlPanelProps) {
106121
() => buildConfig(dimensions, handleViewStateChange),
107122
[dimensions, handleViewStateChange]
108123
);
124+
const displayedPixelRatioOverride =
125+
dimensions.useDevicePixels === true
126+
? 'unset'
127+
: dimensions.useDevicePixels === false
128+
? 1
129+
: dimensions.useDevicePixels;
109130

110131
// Update a single dimension
111132
const updateDimension = useCallback(
@@ -262,6 +283,20 @@ export default function ControlPanel({onConfigChange}: ControlPanelProps) {
262283
</label>
263284
</div>
264285

286+
{/* Pixel Ratio Override */}
287+
<div className="section">
288+
<label>
289+
<input
290+
type="checkbox"
291+
checked={dimensions.useDevicePixels !== true}
292+
onChange={() =>
293+
updateDimension('useDevicePixels', dimensions.useDevicePixels === true ? 1.5 : true)
294+
}
295+
/>
296+
Pixel Ratio Override: {displayedPixelRatioOverride}
297+
</label>
298+
</div>
299+
265300
{/* Stress Test Selection */}
266301
<div className="section">
267302
<div className="label">Stress Test:</div>

examples/basemap-browser/src/renderers/pure-js/deck-only.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,16 @@ import type {Config} from '../../types';
77
import {getBaseMapViewState} from '../../config';
88

99
export function mount(container: HTMLElement, config: Config): () => void {
10-
const {initialViewState, layers, multiView, views, layerFilter, globe, onViewStateChange} =
11-
config;
10+
const {
11+
initialViewState,
12+
layers,
13+
multiView,
14+
views,
15+
layerFilter,
16+
globe,
17+
useDevicePixels,
18+
onViewStateChange
19+
} = config;
1220

1321
// Create a wrapper div for Deck to render into
1422
const wrapper = document.createElement('div');
@@ -24,7 +32,8 @@ export function mount(container: HTMLElement, config: Config): () => void {
2432
height: '100%',
2533
initialViewState: viewState,
2634
controller: true,
27-
layers
35+
layers,
36+
useDevicePixels
2837
};
2938

3039
// Use GlobeView for globe projection

examples/basemap-browser/src/renderers/pure-js/google-maps.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ function loadGoogleMapsAPI(apiKey: string): Promise<any> {
4747
}
4848

4949
export function mount(container: HTMLElement, config: Config): () => void {
50-
const {initialViewState, layers, interleaved, onViewStateChange} = config;
50+
const {initialViewState, layers, interleaved, useDevicePixels, onViewStateChange} = config;
5151
const viewState = getBaseMapViewState(initialViewState);
5252

5353
// eslint-disable-next-line no-process-env
@@ -84,7 +84,8 @@ export function mount(container: HTMLElement, config: Config): () => void {
8484

8585
overlay = new GoogleMapsOverlay({
8686
interleaved,
87-
layers
87+
layers,
88+
useDevicePixels
8889
});
8990

9091
overlay.setMap(map);

examples/basemap-browser/src/renderers/pure-js/mapbox.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export function mount(container: HTMLElement, config: Config): () => void {
1717
multiView,
1818
views,
1919
layerFilter,
20+
useDevicePixels,
2021
onViewStateChange
2122
} = config;
2223

@@ -38,18 +39,20 @@ export function mount(container: HTMLElement, config: Config): () => void {
3839
// For multi-view, extract the mapbox view state for the base map
3940
const mapInitialViewState = getBaseMapViewState(initialViewState);
4041

41-
const map = new mapboxgl.Map({
42+
const mapOpts: mapboxgl.MapOptions = {
4243
container,
4344
style: mapStyle,
4445
center: [mapInitialViewState.longitude, mapInitialViewState.latitude],
4546
zoom: mapInitialViewState.zoom,
4647
bearing: mapInitialViewState.bearing || 0,
4748
pitch: mapInitialViewState.pitch || 0
48-
});
49+
};
50+
const map = new mapboxgl.Map(mapOpts);
4951

5052
const overlayConfig: any = {
5153
interleaved,
52-
layers
54+
layers,
55+
useDevicePixels
5356
};
5457

5558
if (multiView && views) {

examples/basemap-browser/src/renderers/pure-js/maplibre.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,32 @@ export function mount(container: HTMLElement, config: Config): () => void {
1717
multiView,
1818
views,
1919
layerFilter,
20+
useDevicePixels,
2021
onViewStateChange
2122
} = config;
2223

2324
// For multi-view, extract the mapbox view state for the base map
2425
const mapInitialViewState = getBaseMapViewState(initialViewState);
2526

26-
const map = new maplibregl.Map({
27+
const mapOpts: maplibregl.MapOptions = {
2728
container,
2829
style: mapStyle,
2930
center: [mapInitialViewState.longitude, mapInitialViewState.latitude],
3031
zoom: mapInitialViewState.zoom,
3132
bearing: mapInitialViewState.bearing || 0,
3233
pitch: mapInitialViewState.pitch || 0
33-
});
34+
};
35+
if (typeof useDevicePixels === 'number') {
36+
mapOpts.pixelRatio = useDevicePixels;
37+
} else if (useDevicePixels === false) {
38+
mapOpts.pixelRatio = 1;
39+
}
40+
const map = new maplibregl.Map(mapOpts);
3441

3542
const overlayConfig: any = {
3643
interleaved,
37-
layers
44+
layers,
45+
useDevicePixels
3846
};
3947

4048
if (multiView && views) {

examples/basemap-browser/src/renderers/react/deck-only-component.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,16 @@ type DeckOnlyComponentProps = {
1313
};
1414

1515
export default function DeckOnlyComponent({config}: DeckOnlyComponentProps) {
16-
const {initialViewState, layers, multiView, views, layerFilter, globe, onViewStateChange} =
17-
config;
16+
const {
17+
initialViewState,
18+
layers,
19+
multiView,
20+
views,
21+
layerFilter,
22+
globe,
23+
useDevicePixels,
24+
onViewStateChange
25+
} = config;
1826

1927
const handleViewStateChange = useCallback(
2028
({viewState: vs}: {viewState: any}) => {
@@ -63,6 +71,7 @@ export default function DeckOnlyComponent({config}: DeckOnlyComponentProps) {
6371
controller={true}
6472
layers={layers}
6573
views={effectiveViews}
74+
useDevicePixels={useDevicePixels}
6675
layerFilter={multiView ? layerFilter : undefined}
6776
onViewStateChange={handleViewStateChange}
6877
/>

examples/basemap-browser/src/renderers/react/google-maps-component.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,19 @@ import {getBaseMapViewState} from '../../config';
1212
function GoogleMapsDeckOverlay({
1313
layers,
1414
interleaved,
15+
useDevicePixels,
1516
onViewStateChange
1617
}: {
1718
layers: any[];
1819
interleaved: boolean;
20+
useDevicePixels?: boolean | number;
1921
onViewStateChange?: (vs: InitialViewState) => void;
2022
}) {
2123
const map = useMap();
22-
const overlay = useMemo(() => new GoogleMapsOverlay({interleaved}), [interleaved]);
24+
const overlay = useMemo(
25+
() => new GoogleMapsOverlay({interleaved, useDevicePixels}),
26+
[interleaved, useDevicePixels]
27+
);
2328

2429
useEffect(() => {
2530
if (map) {
@@ -62,7 +67,7 @@ export default function GoogleMapsComponent({config}: GoogleMapsComponentProps)
6267
// eslint-disable-next-line no-process-env
6368
const mapId = process.env.GoogleMapsMapId || 'DEMO_MAP_ID';
6469

65-
const {initialViewState, layers, interleaved, onViewStateChange} = config;
70+
const {initialViewState, layers, interleaved, useDevicePixels, onViewStateChange} = config;
6671
const viewState = getBaseMapViewState(initialViewState);
6772

6873
return (
@@ -79,6 +84,7 @@ export default function GoogleMapsComponent({config}: GoogleMapsComponentProps)
7984
<GoogleMapsDeckOverlay
8085
layers={layers}
8186
interleaved={interleaved}
87+
useDevicePixels={useDevicePixels}
8288
onViewStateChange={onViewStateChange}
8389
/>
8490
</GoogleMap>

0 commit comments

Comments
 (0)