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

use webgl2 when available #1891

Merged
merged 5 commits into from
May 12, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## main

### ✨ Features and improvements

- [Breaking] Improve control performance by restricting worker count to a max of 1 except safari browser. ([#2354](https://github.com/maplibre/maplibre-gl-js/pull/2354))
- Improve performance by using HTMLImageElement to download raster source images when refreshExpiredTiles tiles is false ([#2126](https://github.com/maplibre/maplibre-gl-js/pull/2126))
- [Breaking] Improve control initial loading performance by forcing fadeDuration to 0 till first idle event ([#2447](https://github.com/maplibre/maplibre-gl-js/pull/2447))
Expand All @@ -10,6 +11,7 @@
- Lazy load default style properties on demand to improve loading performance and reduce memory usage. ([#2476](https://github.com/maplibre/maplibre-gl-js/pull/2476))
- Replace playwright with puppeteer ([#2494](https://github.com/maplibre/maplibre-gl-js/pull/2494))
- Remove mocks from render tests ([#2497](https://github.com/maplibre/maplibre-gl-js/pull/2497))
- Use WebGL2 context when available ([#1891](https://github.com/maplibre/maplibre-gl-js/pull/1891)
- _...Add new stuff here..._

### 🐞 Bug fixes
Expand Down
62 changes: 33 additions & 29 deletions src/gl/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import StencilMode from './stencil_mode';
import ColorMode from './color_mode';
import CullFaceMode from './cull_face_mode';
import {deepEqual} from '../util/util';
import {ClearColor, ClearDepth, ClearStencil, ColorMask, DepthMask, StencilMask, StencilFunc, StencilOp, StencilTest, DepthRange, DepthTest, DepthFunc, Blend, BlendFunc, BlendColor, BlendEquation, CullFace, CullFaceSide, FrontFace, ProgramValue, ActiveTextureUnit, Viewport, BindFramebuffer, BindRenderbuffer, BindTexture, BindVertexBuffer, BindElementBuffer, BindVertexArrayOES, PixelStoreUnpack, PixelStoreUnpackPremultiplyAlpha, PixelStoreUnpackFlipY} from './value';
import {ClearColor, ClearDepth, ClearStencil, ColorMask, DepthMask, StencilMask, StencilFunc, StencilOp, StencilTest, DepthRange, DepthTest, DepthFunc, Blend, BlendFunc, BlendColor, BlendEquation, CullFace, CullFaceSide, FrontFace, ProgramValue, ActiveTextureUnit, Viewport, BindFramebuffer, BindRenderbuffer, BindTexture, BindVertexBuffer, BindElementBuffer, BindVertexArray, PixelStoreUnpack, PixelStoreUnpackPremultiplyAlpha, PixelStoreUnpackFlipY} from './value';

import type {TriangleIndexArray, LineIndexArray, LineStripIndexArray} from '../data/index_array_type';
import type {
StructArray,
StructArrayMember
} from '../util/struct_array';
import type {Color} from '@maplibre/maplibre-gl-style-spec';
import {isWebGL2} from './webgl2';

type ClearArgs = {
color?: Color;
Expand All @@ -24,7 +25,7 @@ type ClearArgs = {

class Context {
gl: WebGLRenderingContext;
extVertexArrayObject: any;

currentNumAttributes: number;
maxTextureSize: number;

Expand Down Expand Up @@ -55,21 +56,20 @@ class Context {
bindTexture: BindTexture;
bindVertexBuffer: BindVertexBuffer;
bindElementBuffer: BindElementBuffer;
bindVertexArrayOES: BindVertexArrayOES;
bindVertexArray: BindVertexArray;
pixelStoreUnpack: PixelStoreUnpack;
pixelStoreUnpackPremultiplyAlpha: PixelStoreUnpackPremultiplyAlpha;
pixelStoreUnpackFlipY: PixelStoreUnpackFlipY;

extTextureFilterAnisotropic: any;
extTextureFilterAnisotropicMax: any;
extTextureHalfFloat: any;
extRenderToTextureHalfFloat: any;
extTimerQuery: any;
// eslint-disable-next-line camelcase
extTextureFilterAnisotropic: EXT_texture_filter_anisotropic | null;
extTextureFilterAnisotropicMax?: GLfloat;
HALF_FLOAT?: GLenum;
RGBA16F?: GLenum;
RGB16F?: GLenum;

constructor(gl: WebGLRenderingContext) {
this.gl = gl;
this.extVertexArrayObject = this.gl.getExtension('OES_vertex_array_object');

this.clearColor = new ClearColor(this);
this.clearDepth = new ClearDepth(this);
this.clearStencil = new ClearStencil(this);
Expand Down Expand Up @@ -97,28 +97,24 @@ class Context {
this.bindTexture = new BindTexture(this);
this.bindVertexBuffer = new BindVertexBuffer(this);
this.bindElementBuffer = new BindElementBuffer(this);
this.bindVertexArrayOES = this.extVertexArrayObject && new BindVertexArrayOES(this);
this.bindVertexArray = new BindVertexArray(this);
this.pixelStoreUnpack = new PixelStoreUnpack(this);
this.pixelStoreUnpackPremultiplyAlpha = new PixelStoreUnpackPremultiplyAlpha(this);
this.pixelStoreUnpackFlipY = new PixelStoreUnpackFlipY(this);

this.extTextureFilterAnisotropic = (
gl.getExtension('EXT_texture_filter_anisotropic') ||
gl.getExtension('MOZ_EXT_texture_filter_anisotropic') ||
gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic')
);
this.extTextureFilterAnisotropic = gl.getExtension('EXT_texture_filter_anisotropic');
if (this.extTextureFilterAnisotropic) {
this.extTextureFilterAnisotropicMax = gl.getParameter(this.extTextureFilterAnisotropic.MAX_TEXTURE_MAX_ANISOTROPY_EXT);
}

this.extTextureHalfFloat = gl.getExtension('OES_texture_half_float');
if (this.extTextureHalfFloat) {
gl.getExtension('OES_texture_half_float_linear');
this.extRenderToTextureHalfFloat = gl.getExtension('EXT_color_buffer_half_float');
}

this.extTimerQuery = gl.getExtension('EXT_disjoint_timer_query');
rotu marked this conversation as resolved.
Show resolved Hide resolved
this.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
const extTextureHalfFloat = gl.getExtension('OES_texture_half_float');
this.HALF_FLOAT = isWebGL2(gl) ? gl.HALF_FLOAT : extTextureHalfFloat?.HALF_FLOAT_OES;

gl.getExtension('OES_texture_half_float_linear');
const extColorBufferHalfFloat = gl.getExtension('EXT_color_buffer_half_float');
this.RGBA16F = isWebGL2(gl) ? gl.RGBA16F : extColorBufferHalfFloat?.RGBA16F_EXT;
this.RGB16F = isWebGL2(gl) ? gl.RGB16F : extColorBufferHalfFloat?.RGB16F_EXT;
}

setDefault() {
Expand Down Expand Up @@ -179,9 +175,7 @@ class Context {
this.bindTexture.dirty = true;
this.bindVertexBuffer.dirty = true;
this.bindElementBuffer.dirty = true;
if (this.extVertexArrayObject) {
this.bindVertexArrayOES.dirty = true;
}
this.bindVertexArray.dirty = true;
this.pixelStoreUnpack.dirty = true;
this.pixelStoreUnpackPremultiplyAlpha.dirty = true;
this.pixelStoreUnpackFlipY.dirty = true;
Expand Down Expand Up @@ -292,12 +286,22 @@ class Context {
this.colorMask.set(colorMode.mask);
}

createVertexArray(): WebGLVertexArrayObject | undefined {
if (isWebGL2(this.gl))
return this.gl.createVertexArray();
return this.gl.getExtension('OES_vertex_array_object')?.createVertexArrayOES();
}

deleteVertexArray(x: WebGLVertexArrayObject | undefined) {
if (isWebGL2(this.gl))
return this.gl.deleteVertexArray(x);
return this.gl.getExtension('OES_vertex_array_object')?.deleteVertexArrayOES(x);
}

unbindVAO() {
// Unbinding the VAO prevents other things (custom layers, new buffer creation) from
// unintentionally changing the state of the last VAO used.
if (this.extVertexArrayObject) {
this.bindVertexArrayOES.set(null);
}
this.bindVertexArray.set(null);
}
}

Expand Down
8 changes: 4 additions & 4 deletions src/gl/state.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ClearColor, ClearDepth, ClearStencil, ColorMask, DepthMask, StencilMask, StencilFunc, StencilOp, StencilTest, DepthRange, DepthTest, DepthFunc, Blend, BlendFunc, BlendColor, ProgramValue, ActiveTextureUnit, Viewport, BindFramebuffer, BindRenderbuffer, BindTexture, BindVertexBuffer, BindElementBuffer, BindVertexArrayOES, PixelStoreUnpack, PixelStoreUnpackPremultiplyAlpha} from './value';
import {ClearColor, ClearDepth, ClearStencil, ColorMask, DepthMask, StencilMask, StencilFunc, StencilOp, StencilTest, DepthRange, DepthTest, DepthFunc, Blend, BlendFunc, BlendColor, ProgramValue, ActiveTextureUnit, Viewport, BindFramebuffer, BindRenderbuffer, BindTexture, BindVertexBuffer, BindElementBuffer, BindVertexArray, PixelStoreUnpack, PixelStoreUnpackPremultiplyAlpha} from './value';
import Context from './context';
import {Color} from '@maplibre/maplibre-gl-style-spec';
import {deepEqual} from '../util/util';
Expand Down Expand Up @@ -171,10 +171,10 @@ describe('BindElementBuffer', () => {
});
});

describe('BindVertexArrayOES', () => {
valueTest(BindVertexArrayOES, {
describe('BindVertexArray', () => {
valueTest(BindVertexArray, {
equality: (a, b) => a === b,
setValue: context.extVertexArrayObject
setValue: context.createVertexArray()
});
});

Expand Down
22 changes: 11 additions & 11 deletions src/gl/value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {
CullFaceModeType,
FrontFaceType,
} from './types';
import {isWebGL2} from './webgl2';

export interface IValue<T> {
current: T;
Expand Down Expand Up @@ -417,19 +418,18 @@ export class BindElementBuffer extends BaseValue<WebGLBuffer> {
}
}

export class BindVertexArrayOES extends BaseValue<any> {
vao: any;

constructor(context: Context) {
super(context);
this.vao = context.extVertexArrayObject;
}
getDefault(): any {
export class BindVertexArray extends BaseValue<WebGLVertexArrayObject> {
getDefault(): WebGLVertexArrayObject | null {
return null;
}
set(v: any) {
if (!this.vao || v === this.current && !this.dirty) return;
this.vao.bindVertexArrayOES(v);
set(v: WebGLVertexArrayObject | null) {
if (v === this.current && !this.dirty) return;
const gl = this.gl;
if (isWebGL2(gl)) {
gl.bindVertexArray(v);
} else {
gl.getExtension('OES_vertex_array_object')?.bindVertexArrayOES(v);
rotu marked this conversation as resolved.
Show resolved Hide resolved
}
this.current = v;
this.dirty = false;
}
Expand Down
12 changes: 12 additions & 0 deletions src/gl/webgl2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const cache = new WeakMap();
HarelM marked this conversation as resolved.
Show resolved Hide resolved
rotu marked this conversation as resolved.
Show resolved Hide resolved
export function isWebGL2(
gl: WebGLRenderingContext
birkskyum marked this conversation as resolved.
Show resolved Hide resolved
): gl is WebGL2RenderingContext {
if (cache.has(gl)) {
return cache.get(gl);
} else {
const value = gl.getParameter(gl.VERSION).startsWith('WebGL 2.0');
cache.set(gl, value);
return value;
}
}
6 changes: 4 additions & 2 deletions src/render/draw_heatmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,10 @@ function bindTextureToFramebuffer(context: Context, painter: Painter, texture: W
const gl = context.gl;
// Use the higher precision half-float texture where available (producing much smoother looking heatmaps);
// Otherwise, fall back to a low precision texture
const internalFormat = context.extRenderToTextureHalfFloat ? context.extTextureHalfFloat.HALF_FLOAT_OES : gl.UNSIGNED_BYTE;
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, painter.width / 4, painter.height / 4, 0, gl.RGBA, internalFormat, null);
const numType = context.HALF_FLOAT ?? gl.UNSIGNED_BYTE;
const internalFormat = context.RGBA16F ?? gl.RGBA;

gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, painter.width / 4, painter.height / 4, 0, gl.RGBA, numType, null);
fbo.colorAttachment.set(texture);
}

Expand Down
52 changes: 2 additions & 50 deletions src/render/painter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,13 @@ type PainterOptions = {
rotating: boolean;
zooming: boolean;
moving: boolean;
gpuTiming: boolean;
fadeDuration: number;
};

/**
* Initialize a new painter object.
*
* @param {Canvas} gl an experimental-webgl drawing context
* @param {Canvas} gl a webgl drawing context
* @private
*/
class Painter {
Expand Down Expand Up @@ -122,7 +121,7 @@ class Painter {
cache: {[_: string]: Program<any>};
crossTileSymbolIndex: CrossTileSymbolIndex;
symbolFadeChange: number;
gpuTimers: {[_: string]: any};
emptyTexture: Texture;
birkskyum marked this conversation as resolved.
Show resolved Hide resolved
debugOverlayTexture: Texture;
debugOverlayCanvas: HTMLCanvasElement;
// this object stores the current camera-matrix and the last render time
Expand All @@ -144,8 +143,6 @@ class Painter {
this.depthEpsilon = 1 / Math.pow(2, 16);

this.crossTileSymbolIndex = new CrossTileSymbolIndex();

this.gpuTimers = {};
}

/*
Expand Down Expand Up @@ -493,52 +490,7 @@ class Painter {
if (layer.type !== 'background' && layer.type !== 'custom' && !(coords || []).length) return;
this.id = layer.id;

this.gpuTimingStart(layer);
draw[layer.type](painter, sourceCache, layer as any, coords, this.style.placement.variableOffsets);
this.gpuTimingEnd();
}

gpuTimingStart(layer: StyleLayer) {
HarelM marked this conversation as resolved.
Show resolved Hide resolved
if (!this.options.gpuTiming) return;
const ext = this.context.extTimerQuery;
// This tries to time the draw call itself, but note that the cost for drawing a layer
// may be dominated by the cost of uploading vertices to the GPU.
// To instrument that, we'd need to pass the layerTimers object down into the bucket
// uploading logic.
let layerTimer = this.gpuTimers[layer.id];
if (!layerTimer) {
layerTimer = this.gpuTimers[layer.id] = {
calls: 0,
cpuTime: 0,
query: ext.createQueryEXT()
};
}
layerTimer.calls++;
ext.beginQueryEXT(ext.TIME_ELAPSED_EXT, layerTimer.query);
}

gpuTimingEnd() {
if (!this.options.gpuTiming) return;
const ext = this.context.extTimerQuery;
ext.endQueryEXT(ext.TIME_ELAPSED_EXT);
}

collectGpuTimers() {
const currentLayerTimers = this.gpuTimers;
this.gpuTimers = {};
return currentLayerTimers;
}

queryGpuTimers(gpuTimers: {[_: string]: any}) {
const layers = {};
for (const layerId in gpuTimers) {
const gpuTimer = gpuTimers[layerId];
const ext = this.context.extTimerQuery;
const gpuTime = ext.getQueryObjectEXT(gpuTimer.query, ext.QUERY_RESULT_EXT) / (1000 * 1000);
ext.deleteQueryEXT(gpuTimer.query);
layers[layerId] = gpuTime;
}
return layers;
}

/**
Expand Down
45 changes: 15 additions & 30 deletions src/render/vertex_array_object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ class VertexArrayObject {
this.boundDynamicVertexBuffer3 !== dynamicVertexBuffer3
);

if (!context.extVertexArrayObject || isFreshBindRequired) {
if (isFreshBindRequired) {
this.freshBind(program, layoutVertexBuffer, paintVertexBuffers, indexBuffer, vertexOffset, dynamicVertexBuffer, dynamicVertexBuffer2, dynamicVertexBuffer3);
} else {
context.bindVertexArrayOES.set(this.vao);
context.bindVertexArray.set(this.vao);

if (dynamicVertexBuffer) {
// The buffer may have been updated. Rebind to upload data.
Expand Down Expand Up @@ -90,39 +90,24 @@ class VertexArrayObject {
dynamicVertexBuffer2?: VertexBuffer | null,
dynamicVertexBuffer3?: VertexBuffer | null) {

let numPrevAttributes;
const numNextAttributes = program.numAttributes;

const context = this.context;
const gl = context.gl;

if (context.extVertexArrayObject) {
if (this.vao) this.destroy();
this.vao = context.extVertexArrayObject.createVertexArrayOES();
context.bindVertexArrayOES.set(this.vao);
numPrevAttributes = 0;

// store the arguments so that we can verify them when the vao is bound again
this.boundProgram = program;
this.boundLayoutVertexBuffer = layoutVertexBuffer;
this.boundPaintVertexBuffers = paintVertexBuffers;
this.boundIndexBuffer = indexBuffer;
this.boundVertexOffset = vertexOffset;
this.boundDynamicVertexBuffer = dynamicVertexBuffer;
this.boundDynamicVertexBuffer2 = dynamicVertexBuffer2;
this.boundDynamicVertexBuffer3 = dynamicVertexBuffer3;
if (this.vao) this.destroy();
this.vao = context.createVertexArray();
context.bindVertexArray.set(this.vao);

} else {
numPrevAttributes = context.currentNumAttributes || 0;

// Disable all attributes from the previous program that aren't used in
// the new program. Note: attribute indices are *not* program specific!
for (let i = numNextAttributes; i < numPrevAttributes; i++) {
// WebGL breaks if you disable attribute 0, so if i == 0.
// http://stackoverflow.com/questions/20305231
gl.disableVertexAttribArray(i);
}
rotu marked this conversation as resolved.
Show resolved Hide resolved
}
// store the arguments so that we can verify them when the vao is bound again
this.boundProgram = program;
this.boundLayoutVertexBuffer = layoutVertexBuffer;
this.boundPaintVertexBuffers = paintVertexBuffers;
this.boundIndexBuffer = indexBuffer;
this.boundVertexOffset = vertexOffset;
this.boundDynamicVertexBuffer = dynamicVertexBuffer;
this.boundDynamicVertexBuffer2 = dynamicVertexBuffer2;
this.boundDynamicVertexBuffer3 = dynamicVertexBuffer3;

layoutVertexBuffer.enableAttributes(gl, program);
for (const vertexBuffer of paintVertexBuffers) {
Expand Down Expand Up @@ -167,7 +152,7 @@ class VertexArrayObject {

destroy() {
if (this.vao) {
this.context.extVertexArrayObject.deleteVertexArrayOES(this.vao);
this.context.deleteVertexArray(this.vao);
this.vao = null;
}
}
Expand Down