Skip to content

Commit

Permalink
Use WebGL2 context when available
Browse files Browse the repository at this point in the history
PR feedback

remove Webgl timing

faster webgl2 detection in Chrome

remove more unused extensions

cache WebGL version and don't rely on canvas

wip

fix heatmap?

wip

wip
  • Loading branch information
rotu committed Apr 6, 2023
1 parent edfaa54 commit 58680d9
Show file tree
Hide file tree
Showing 11 changed files with 92 additions and 166 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -18,6 +18,7 @@
- Adding a `warnonce` when terrain and hillshade source are the same ([#2298](https://github.com/maplibre/maplibre-gl-js/pull/2298))
- Remove a deprecation warning by removing an empty texture that is no longer being used in the codebase ([#2299](https://github.com/maplibre/maplibre-gl-js/pull/2299))
- Improve initial loading performance by lazy serializing layers only when needed. ([#2306](https://github.com/maplibre/maplibre-gl-js/pull/2306))
- 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
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
@@ -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
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
@@ -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
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
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
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 58680d9

Please sign in to comment.