Skip to content

Commit

Permalink
Use webgl2 when available (#1891)
Browse files Browse the repository at this point in the history
  • Loading branch information
rotu committed May 12, 2023
1 parent 56a206a commit 7a1d5b5
Show file tree
Hide file tree
Showing 11 changed files with 93 additions and 165 deletions.
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');
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);
}
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();
export function isWebGL2(
gl: WebGLRenderingContext
): 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;
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) {
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);
}
}
// 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

0 comments on commit 7a1d5b5

Please sign in to comment.