Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RD-298: Add WebGL context loss warning message #120

Merged
merged 5 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

## NEXT
### New Features

- Shows a warning message in the map container if WebGL context is lost
- The event `"webglContextLost"` is now exposed
- The `Map` class instances now have a `.setTerrainAnimationDuration(d: number)` method
- The `Map` class instances now have events related to terrain animation `"terrainAnimationStart"` and `"terrainAnimationStop"`
- expose the function `getWebGLSupportError()` to detect WebGL compatibility


## 2.3.0
### Bug Fixes
- Updating from MapLibre v4.4.1 to v4.7.0. See Maplibre changelogs for [v4.5.0](https://github.com/maplibre/maplibre-gl-js/blob/main/CHANGELOG.md#450), [v4.5.1](https://github.com/maplibre/maplibre-gl-js/blob/main/CHANGELOG.md#451), [v4.5.2](https://github.com/maplibre/maplibre-gl-js/blob/main/CHANGELOG.md#452), and [v4.6.0](https://github.com/maplibre/maplibre-gl-js/blob/main/CHANGELOG.md#460)
Expand Down
21 changes: 21 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,27 @@ We believe that the *promise* approach is better because it does not nest scopes

> 📣 *__Note:__* Generally speaking, *promises* are not a go to replacement for all event+callback and are suitable only for events that are called only once in the lifecycle of a Map instance. This is the reason why we have decided to provide a *promise* equivalent only for the `load`, `ready` and `loadWithTerrain` events but not for events that may be called multiple time such as interaction events.

### The `webglContextLost` event
The maps is rendered with WebGL, that leverages the GPU to provide high-performance graphics. In some cases, the host machine, operating system or the graphics driver, can decide that continuing to run such high performance graphics is unsustainable, and will abort the process. This is called a "WebGL context loss". Such situation happens when the ressources are running low or when multiple browser tabs are competing to access graphics memory.

The best course of action in such situation varies from an app to another. Sometimes a page refresh is the best thing to do, in other cases, instantiating a new Map dynmicaly at application level is more appropriate because it hides a technical failure to the end user. The event `webglContextLost` is exposed so that the most appropriate scenario can be implemented at application level.

Here is how to respond to a WebGL context loss with a simple page refresh:
```ts

// Init the map
const map = new maptilersdk.Map({
container: "map-container",
hash: true,
})

// Refresh the page if context is lost.
// Since `hash` is true, the location will be the same as before
map.on("webglContextLost", (e) => {
location.reload();
})
```

# Color Ramps
A color ramp is a color gradient defined in a specific interval, for instance in [0, 1], and for any value within this interval will retrieve a color. They are defined by at least a color at each bound and usually additional colors within the range.

Expand Down
11 changes: 10 additions & 1 deletion src/Map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import type { ReferenceMapStyle, MapStyleVariant } from "@maptiler/client";
import { config, MAPTILER_SESSION_ID, type SdkConfig } from "./config";
import { defaults } from "./defaults";
import { MaptilerLogoControl } from "./MaptilerLogoControl";
import { combineTransformRequest, displayNoWebGlWarning } from "./tools";
import { combineTransformRequest, displayNoWebGlWarning, displayWebGLContextLostWarning } from "./tools";
import { getBrowserLanguage, Language, type LanguageInfo } from "./language";
import { styleToStyle } from "./mapstyle";
import { MaptilerTerrainControl } from "./MaptilerTerrainControl";
Expand Down Expand Up @@ -556,6 +556,15 @@ export class Map extends maplibregl.Map {
if (options.terrain) {
this.enableTerrain(options.terrainExaggeration ?? this.terrainExaggeration);
}

// Display a message if WebGL context is lost
this.once("load", () => {
this.getCanvas().addEventListener("webglcontextlost", (e) => {
console.warn(e);
displayWebGLContextLostWarning(options.container);
this.fire("webglContextLost", { error: e });
});
});
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/style/style_template.css
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@
line-height: 14px;
}

.no-webgl-support-div {
.webgl-warning-div {
position: absolute;
top: 0;
left: 0;
Expand Down
27 changes: 26 additions & 1 deletion src/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,32 @@ export function displayNoWebGlWarning(container: HTMLElement | string) {

const errorMessageDiv = document.createElement("div");
errorMessageDiv.innerHTML = webglError;
errorMessageDiv.classList.add("no-webgl-support-div");
errorMessageDiv.classList.add("webgl-warning-div");
actualContainer.appendChild(errorMessageDiv);
throw new Error(webglError);
}

/**
* Display an error message in the Map div if WebGL2 is not supported
*/
export function displayWebGLContextLostWarning(container: HTMLElement | string) {
const webglError = "The WebGL context was lost.";

let actualContainer: HTMLElement | null = null;

if (typeof container === "string") {
actualContainer = document.getElementById(container);
} else if (container instanceof HTMLElement) {
actualContainer = container;
}

if (!actualContainer) {
throw new Error("The Map container must be provided.");
}

const errorMessageDiv = document.createElement("div");
errorMessageDiv.innerHTML = webglError;
errorMessageDiv.classList.add("webgl-warning-div");
actualContainer.appendChild(errorMessageDiv);
// throw new Error(webglError);
}
Loading