From 35aa95298c41d186a670bc963186cae3825d0a21 Mon Sep 17 00:00:00 2001 From: Jonathan Lurie Date: Wed, 9 Oct 2024 10:31:41 +0200 Subject: [PATCH] RD-298: Add WebGL context loss warning message (#120) * Add WebGL context loss warning message * adding GL context loss event * readme typos * readme typos --- CHANGELOG.md | 4 ++-- readme.md | 21 +++++++++++++++++++++ src/Map.ts | 11 ++++++++++- src/style/style_template.css | 2 +- src/tools.ts | 27 ++++++++++++++++++++++++++- 5 files changed, 60 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11a0c26..d8d8bd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/readme.md b/readme.md index e4e03fd..a31349d 100644 --- a/readme.md +++ b/readme.md @@ -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. diff --git a/src/Map.ts b/src/Map.ts index c735e62..8966c0a 100644 --- a/src/Map.ts +++ b/src/Map.ts @@ -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"; @@ -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 }); + }); + }); } /** diff --git a/src/style/style_template.css b/src/style/style_template.css index 7433943..600b79b 100644 --- a/src/style/style_template.css +++ b/src/style/style_template.css @@ -145,7 +145,7 @@ line-height: 14px; } -.no-webgl-support-div { +.webgl-warning-div { position: absolute; top: 0; left: 0; diff --git a/src/tools.ts b/src/tools.ts index c5a9d82..fac3ee4 100644 --- a/src/tools.ts +++ b/src/tools.ts @@ -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); +}