Skip to content

Commit 4a82ff7

Browse files
committed
[GLJS-1120] Fix a blank map issue after WebGL context loss (internal-2150)
1 parent e562bd8 commit 4a82ff7

File tree

8 files changed

+222
-9
lines changed

8 files changed

+222
-9
lines changed

3d-style/render/model_manager.ts

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ class ModelManager extends Evented {
2121
[id: string]: ReferencedModel;
2222
};
2323
};
24+
modelUris: {
25+
[scope: string]: {
26+
[id: string]: string;
27+
}
28+
};
2429
numModelsLoading: {
2530
[scope: string]: number;
2631
};
@@ -30,6 +35,7 @@ class ModelManager extends Evented {
3035
super();
3136
this.requestManager = requestManager;
3237
this.models = {'': {}};
38+
this.modelUris = {'': {}};
3339
this.numModelsLoading = {};
3440
}
3541

@@ -53,7 +59,11 @@ class ModelManager extends Evented {
5359

5460
load(modelUris: {
5561
[key: string]: string;
56-
}, scope: string) {
62+
}, scope: string, options: {
63+
keepNumReferences?: boolean
64+
} = {
65+
keepNumReferences: false
66+
}) {
5767
if (!this.models[scope]) this.models[scope] = {};
5868

5969
const modelIds = Object.keys(modelUris);
@@ -70,7 +80,9 @@ class ModelManager extends Evented {
7080
// @ts-expect-error - TS2339 - Property 'value' does not exist on type 'PromiseSettledResult<any>'.
7181
const {status, value} = results[i];
7282
if (status === 'fulfilled' && value) {
73-
this.models[scope][modelIds[i]] = {model: value, numReferences : 1};
83+
const previousModel = this.models[scope][modelIds[i]];
84+
const numReferences = options.keepNumReferences && previousModel ? previousModel.numReferences : 1;
85+
this.models[scope][modelIds[i]] = {model: value, numReferences};
7486
}
7587
}
7688
this.numModelsLoading[scope] -= modelIds.length;
@@ -99,35 +111,47 @@ class ModelManager extends Evented {
99111

100112
addModel(id: string, url: string, scope: string) {
101113
if (!this.models[scope]) this.models[scope] = {};
114+
if (!this.modelUris[scope]) this.modelUris[scope] = {};
115+
102116
// update num references if the model exists
103117
if (this.hasModel(id, scope)) {
104118
this.models[scope][id].numReferences++;
105119
}
106-
this.load({[id]: this.requestManager.normalizeModelURL(url)}, scope);
120+
121+
this.modelUris[scope][id] = this.requestManager.normalizeModelURL(url);
122+
123+
this.load({[id]: this.modelUris[scope][id]}, scope);
107124
}
108125

109126
addModels(models: ModelsSpecification, scope: string) {
110127
if (!this.models[scope]) this.models[scope] = {};
128+
if (!this.modelUris[scope]) this.modelUris[scope] = {};
111129

112-
const modelUris: Record<string, any> = {};
130+
const modelUris: Record<string, any> = this.modelUris[scope];
113131
for (const modelId in models) {
114132
// Add a void object so we mark this model as requested
115133
// @ts-expect-error - TS2739 - Type '{}' is missing the following properties from type 'ReferencedModel': model, numReferences
116134
this.models[scope][modelId] = {};
117135
modelUris[modelId] = this.requestManager.normalizeModelURL(models[modelId]);
118136
}
119-
this.load(modelUris, scope);
137+
this.load(modelUris, scope, {keepNumReferences: true});
138+
}
139+
140+
reloadModels(scope: string) {
141+
this.load(this.modelUris[scope], scope);
120142
}
121143

122144
addModelsFromBucket(modelUris: Array<string>, scope: string) {
123145
if (!this.models[scope]) this.models[scope] = {};
146+
if (!this.modelUris[scope]) this.modelUris[scope] = {};
124147

125148
const modelsRequests: Record<string, any> = {};
126149
for (const modelUri of modelUris) {
127150
if (this.hasModel(modelUri, scope)) {
128151
this.models[scope][modelUri].numReferences++;
129152
} else {
130-
modelsRequests[modelUri] = this.requestManager.normalizeModelURL(modelUri);
153+
this.modelUris[scope][modelUri] = this.requestManager.normalizeModelURL(modelUri);
154+
modelsRequests[modelUri] = this.modelUris[scope][modelUri];
131155
}
132156
}
133157
this.load(modelsRequests, scope);
@@ -139,6 +163,7 @@ class ModelManager extends Evented {
139163
if (this.models[scope][id].numReferences === 0) {
140164
const model = this.models[scope][id].model;
141165
delete this.models[scope][id];
166+
delete this.modelUris[scope][id];
142167
model.destroy();
143168
}
144169
}

3d-style/source/tiled_3d_model_source.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import loadTileJSON from '../../src/source/load_tilejson';
44
import TileBounds from '../../src/source/tile_bounds';
55
import {extend} from '../../src/util/util';
66
import {postTurnstileEvent} from '../../src/util/mapbox';
7+
import {makeFQID} from '../../src/util/fqid';
78

89
// Import Tiled3dModelBucket as a module with side effects to ensure
910
// it's registered as a serializable class on the main thread
@@ -49,7 +50,6 @@ class Tiled3DModelSource extends Evented<SourceEvents> implements ISource {
4950
map: Map;
5051

5152
onRemove: undefined;
52-
reload: undefined;
5353
abortTile: undefined;
5454
unloadTile: undefined;
5555
prepare: undefined;
@@ -82,6 +82,18 @@ class Tiled3DModelSource extends Evented<SourceEvents> implements ISource {
8282
this.load();
8383
}
8484

85+
reload() {
86+
this.cancelTileJSONRequest();
87+
const fqid = makeFQID(this.id, this.scope);
88+
this.load(() => this.map.style.clearSource(fqid));
89+
}
90+
91+
cancelTileJSONRequest() {
92+
if (!this._tileJSONRequest) return;
93+
this._tileJSONRequest.cancel();
94+
this._tileJSONRequest = null;
95+
}
96+
8597
load(callback?: Callback<undefined>) {
8698
this._loaded = false;
8799
this.fire(new Event('dataloading', {dataType: 'source'}));

src/source/geojson_source.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {extend} from '../util/util';
33
import EXTENT from '../style-spec/data/extent';
44
import {ResourceType} from '../util/ajax';
55
import browser from '../util/browser';
6+
import {makeFQID} from '../util/fqid';
67

78
import type {ISource, SourceEvents} from './source';
89
import type {Map as MapboxMap} from '../ui/map';
@@ -94,7 +95,6 @@ class GeoJSONSource extends Evented<SourceEvents> implements ISource {
9495
_pendingLoad: Cancelable | null | undefined;
9596
_partialReload: boolean;
9697

97-
reload: undefined;
9898
hasTile: undefined;
9999
prepare: undefined;
100100
afterUpdate: undefined;
@@ -429,6 +429,12 @@ class GeoJSONSource extends Evented<SourceEvents> implements ISource {
429429
return this._loaded;
430430
}
431431

432+
reload() {
433+
const fqid = makeFQID(this.id, this.scope);
434+
this.map.style.clearSource(fqid);
435+
this._updateWorkerData();
436+
}
437+
432438
loadTile(tile: Tile, callback: Callback<undefined>) {
433439
const message = !tile.actor ? 'loadTile' : 'reloadTile';
434440
tile.actor = this.actor;

src/style/style.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3727,6 +3727,13 @@ class Style extends Evented<MapEvents> {
37273727
}
37283728
}
37293729

3730+
reloadModels() {
3731+
this.modelManager.reloadModels('');
3732+
this.forEachFragmentStyle((style) => {
3733+
style.modelManager.reloadModels(style.scope);
3734+
});
3735+
}
3736+
37303737
updateSources(transform: Transform) {
37313738
let lightDirection: vec3 | null | undefined;
37323739
if (this.directionalLight) {

src/ui/map.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4163,7 +4163,10 @@ export class Map extends Camera {
41634163

41644164
_contextRestored(event: any) {
41654165
this._setupPainter();
4166-
this.resize();
4166+
this.painter.resize(Math.ceil(this._containerWidth), Math.ceil(this._containerHeight));
4167+
this._updateTerrain();
4168+
this.style.reloadModels();
4169+
this.style.clearSources();
41674170
this._update();
41684171
this.fire(new Event('webglcontextrestored', {originalEvent: event}));
41694172
}

test/integration/lib/operation-handlers.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@ export const operationHandlers = {
2727

2828
waitForRender(map, () => map.loaded(), doneCb);
2929
},
30+
forceContextRestart(map, params, doneCb) {
31+
const canvas = map.getCanvas();
32+
const ext = map.painter.context.gl.getExtension('WEBGL_lose_context');
33+
canvas.addEventListener('webglcontextlost', (e) => {
34+
e.preventDefault();
35+
setTimeout(() => {
36+
ext.restoreContext();
37+
doneCb();
38+
});
39+
});
40+
ext.loseContext();
41+
},
3042
waitFrameReady(map, params, doneCb) {
3143
let timeIterationInterval = 0;
3244
if (params.length) {
Loading
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
{
2+
"version": 8,
3+
"metadata": {
4+
"test": {
5+
"width": 128,
6+
"height": 128,
7+
"allowed": 0.003,
8+
"operations": [
9+
["wait"],
10+
["forceContextRestart"],
11+
["wait"]
12+
]
13+
}
14+
},
15+
"sprite": "local://sprites/sprite",
16+
"glyphs": "local://glyphs/{fontstack}/{range}.pbf",
17+
"models": {
18+
"maple1": "local://models/maple1-lod2.glb",
19+
"maple2": "local://models/maple2-lod2.glb",
20+
"oak1": "local://models/oak1-lod2.glb",
21+
"oak2": "local://models/oak2-lod2.glb"
22+
},
23+
"sources": {
24+
"mapbox": {
25+
"type": "vector",
26+
"maxzoom": 15,
27+
"tiles": [
28+
"local://tiles/{z}-{x}-{y}.vector.pbf"
29+
]
30+
},
31+
"trees": {
32+
"type": "vector",
33+
"maxzoom": 15,
34+
"tiles": [
35+
"local://tiles/trees/{z}-{x}-{y}.pbf"
36+
]
37+
},
38+
"symbols": {
39+
"type": "geojson",
40+
"data": {
41+
"type": "FeatureCollection",
42+
"features": [
43+
{
44+
"type": "Feature",
45+
"properties": {},
46+
"geometry": {
47+
"coordinates": [
48+
-122.40258704262851,
49+
37.784333172276225
50+
],
51+
"type": "Point"
52+
}
53+
}
54+
]
55+
}
56+
}
57+
},
58+
"layers": [
59+
{
60+
"id": "background",
61+
"type": "background",
62+
"paint": {
63+
"background-color": "lightgray"
64+
}
65+
},
66+
{
67+
"id": "land",
68+
"type": "fill",
69+
"source": "mapbox",
70+
"source-layer": "water",
71+
"paint": {
72+
"fill-color": "lightblue"
73+
}
74+
},
75+
{
76+
"id": "road",
77+
"type": "line",
78+
"source": "mapbox",
79+
"source-layer": "road",
80+
"paint": {
81+
"line-color": "lightyellow",
82+
"line-width": 10,
83+
"line-emissive-strength": 1
84+
}
85+
},
86+
{
87+
"id": "tree-layer",
88+
"type": "model",
89+
"source": "trees",
90+
"source-layer": "trees",
91+
"layout" : {
92+
"model-id":
93+
["match", ["%", ["id"], 4],
94+
0, "maple1",
95+
1, "maple2",
96+
3, "oak1",
97+
"oak2"]
98+
},
99+
"paint": {
100+
"model-rotation": ["match", ["%", ["id"], 4],
101+
0, ["literal", [0.0, 0.0, 0.0]],
102+
1, ["literal", [0.0, 0.0, 50.0]],
103+
2, ["literal", [10.0, 0.0, 120.0]],
104+
["literal", [0.0, -3.0, -60]]],
105+
"model-scale": ["match", ["%", ["id"], 3],
106+
0, ["literal", [3.0, 3.25, 3.0]],
107+
1, ["literal", [1.8, 1.9, 1.8]],
108+
["literal", [5.2, 5.1, 5.1]]],
109+
"model-color": [
110+
"case",
111+
["boolean", ["feature-state", "hover"], false],
112+
"blue",
113+
["match", ["%", ["id"], 6],
114+
0, "orange",
115+
1, "gray",
116+
2, "white",
117+
3, "pink",
118+
4, "yellow",
119+
"green"]
120+
],
121+
"model-color-mix-intensity": 0.2,
122+
"model-cutoff-fade-range": 0.2
123+
}
124+
},
125+
{
126+
"id": "geometry",
127+
"type": "symbol",
128+
"source": "symbols",
129+
"layout": {
130+
"icon-image": "rocket-12",
131+
"text-field": "Mapbox",
132+
"text-font": [
133+
"Open Sans Semibold",
134+
"Arial Unicode MS Bold"
135+
],
136+
"text-allow-overlap": true,
137+
"text-ignore-placement": true,
138+
"text-offset": [0, 1]
139+
}
140+
}
141+
],
142+
"zoom": 15.1,
143+
"bearing": 264,
144+
"center": [
145+
-122.4027,
146+
37.7845
147+
]
148+
}

0 commit comments

Comments
 (0)