Skip to content

Commit

Permalink
Allow choosing webgl version (#5236)
Browse files Browse the repository at this point in the history
* Allow setting specific webgl version

* Add test

* Fix typo

* Add changelog entry

* Add type

* Use afterEach

* Address feedback
  • Loading branch information
ibesora authored Dec 18, 2024
1 parent 5d5a2cd commit 25be943
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## main

### ✨ Features and improvements
- Allows setting the desired WebGL version to use ([#5236](https://github.com/maplibre/maplibre-gl-js/pull/5236)). You can now use `contextType` inside `canvasContextAttributes` to choose which WebGL version to use
- _...Add new stuff here..._

### 🐞 Bug fixes
Expand Down
22 changes: 15 additions & 7 deletions src/ui/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ import {MercatorCameraHelper} from '../geo/projection/mercator_camera_helper';

const version = packageJSON.version;

type WebGLSupportedVersions = 'webgl2' | 'webgl' | undefined
type WebGLContextAttributesWithType = WebGLContextAttributes & {contextType?: WebGLSupportedVersions};

/**
* The {@link Map} options object.
*/
Expand Down Expand Up @@ -115,9 +118,10 @@ export type MapOptions = {
/**
* Set of WebGLContextAttributes that are applied to the WebGL context of the map.
* See https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext for more details.
* @defaultValue antialias: false, powerPreference: 'high-performance', preserveDrawingBuffer: false, failIfMajorPerformanceCaveat: false, desynchronized: false
* `contextType` can be set to `webgl2` or `webgl` to force a WebGL version. Not setting it, Maplibre will do it's best to get a suitable context.
* @defaultValue antialias: false, powerPreference: 'high-performance', preserveDrawingBuffer: false, failIfMajorPerformanceCaveat: false, desynchronized: false, contextType: 'webgl2withfallback'
*/
canvasContextAttributes?: WebGLContextAttributes;
canvasContextAttributes?: WebGLContextAttributesWithType;
/**
* If `false`, the map won't attempt to re-request tiles once they expire per their HTTP `cacheControl`/`expires` headers.
* @defaultValue true
Expand Down Expand Up @@ -388,7 +392,8 @@ const defaultOptions: Readonly<Partial<MapOptions>> = {
preserveDrawingBuffer: false,
powerPreference: 'high-performance',
failIfMajorPerformanceCaveat: false,
desynchronized: false
desynchronized: false,
contextType: undefined
},

scrollZoom: true,
Expand Down Expand Up @@ -494,7 +499,7 @@ export class Map extends Camera {
_fullyLoaded: boolean;
_trackResize: boolean;
_resizeObserver: ResizeObserver;
_canvasContextAttributes: WebGLContextAttributes;
_canvasContextAttributes: WebGLContextAttributesWithType;
_refreshExpiredTiles: boolean;
_hash: Hash;
_delegatedListeners: Record<string, DelegatedListener[]>;
Expand Down Expand Up @@ -3062,9 +3067,12 @@ export class Map extends Camera {
}
}, {once: true});

const gl =
this._canvas.getContext('webgl2', attributes) as WebGL2RenderingContext ||
this._canvas.getContext('webgl', attributes) as WebGLRenderingContext;
let gl: WebGL2RenderingContext | WebGLRenderingContext | null = null;
if (this._canvasContextAttributes.contextType) {
gl = this._canvas.getContext(this._canvasContextAttributes.contextType, attributes) as WebGL2RenderingContext | WebGLRenderingContext;
} else {
gl = this._canvas.getContext('webgl2', attributes) as WebGL2RenderingContext || this._canvas.getContext('webgl', attributes) as WebGLRenderingContext;
}

if (!gl) {
const msg = 'Failed to initialize WebGL';
Expand Down
53 changes: 49 additions & 4 deletions src/ui/map_tests/map_webgl.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import {beforeEach, test, expect, vi} from 'vitest';
import {beforeEach, afterEach, test, expect, vi} from 'vitest';
import {createMap, beforeMapTest} from '../../util/test/util';

let originalGetContext: typeof HTMLCanvasElement.prototype.getContext;
beforeEach(() => {
beforeMapTest();
global.fetch = null;
originalGetContext = HTMLCanvasElement.prototype.getContext;
});

afterEach(() => {
HTMLCanvasElement.prototype.getContext = originalGetContext;
});

test('does not fire "webglcontextlost" after #remove has been called', () => new Promise<void>((done) => {
Expand Down Expand Up @@ -34,7 +40,6 @@ test('does not fire "webglcontextrestored" after #remove has been called', () =>
}));

test('WebGL error while creating map', () => {
const original = HTMLCanvasElement.prototype.getContext;
HTMLCanvasElement.prototype.getContext = function (type: string) {
if (type === 'webgl2' || type === 'webgl') {
const errorEvent = new Event('webglcontextcreationerror');
Expand All @@ -53,10 +58,50 @@ test('WebGL error while creating map', () => {

// this is from test mock
expect(errorMessageObject.statusMessage).toBe('mocked webglcontextcreationerror message');
} finally {
HTMLCanvasElement.prototype.getContext = original;
}
});

test('Check Map is being created with desired WebGL version', () => {
HTMLCanvasElement.prototype.getContext = function (type: string) {
const errorEvent = new Event('webglcontextcreationerror');
(errorEvent as any).statusMessage = `${type} is not supported`;
(this as HTMLCanvasElement).dispatchEvent(errorEvent);
return null;
};

try {
createMap({canvasContextAttributes: {contextType: 'webgl2'}});
} catch (e) {
const errorMessageObject = JSON.parse(e.message);
expect(errorMessageObject.statusMessage).toBe('webgl2 is not supported');
}

try {
createMap({canvasContextAttributes: {contextType: 'webgl'}});
} catch (e) {
const errorMessageObject = JSON.parse(e.message);
expect(errorMessageObject.statusMessage).toBe('webgl is not supported');
}

});

test('Check Map falls back to WebGL if WebGL 2 is not supported', () => {
const mockGetContext = vi.fn().mockImplementation((type: string) => {
if (type === 'webgl2') {return null;}
return originalGetContext.apply(this, [type]);
});
HTMLCanvasElement.prototype.getContext = mockGetContext

try {
createMap();
} catch(_) { // eslint-disable-line @typescript-eslint/no-unused-vars
}
expect(mockGetContext).toHaveBeenCalledTimes(2);
expect(mockGetContext.mock.calls[0][0]).toBe('webgl2');
expect(mockGetContext.mock.calls[1][0]).toBe('webgl');

});

test('Hit WebGL max drawing buffer limit', () => {
// Simulate a device with MAX_TEXTURE_SIZE=16834 and max rendering area of ~32Mpx
const container = window.document.createElement('div');
Expand Down

0 comments on commit 25be943

Please sign in to comment.