Skip to content

Commit

Permalink
Use WebGL2 context when available
Browse files Browse the repository at this point in the history
  • Loading branch information
rotu committed Dec 11, 2022
1 parent b94bb2e commit 4ccbab6
Show file tree
Hide file tree
Showing 12 changed files with 181 additions and 78 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### ✨ Features and improvements
- *...Add new stuff here...*
- Use WebGL2 context when available ([#1891](https://github.com/maplibre/maplibre-gl-js/pull/1891))
- NavigationControlOptions is now optional when creating an instance of NavigationControl ([#1754](https://github.com/maplibre/maplibre-gl-js/issues/1754))
- Listen to webglcontextcreationerror event and give detailed debug info when it fails ([#1715](https://github.com/maplibre/maplibre-gl-js/pull/1715))
- Make sure `cooperativeGestures` overlay is always "on top" (z-index) of map features ([#1753](https://github.com/maplibre/maplibre-gl-js/pull/1753))
Expand Down
51 changes: 37 additions & 14 deletions src/gl/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ 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 '../style-spec/util/color';
import {getTimingAPI} from './timing';
import {isWebGL2} from './webgl2';

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

class Context {
gl: WebGLRenderingContext;
extVertexArrayObject: any;

currentNumAttributes: number;
maxTextureSize: number;

Expand Down Expand Up @@ -55,7 +57,7 @@ class Context {
bindTexture: BindTexture;
bindVertexBuffer: BindVertexBuffer;
bindElementBuffer: BindElementBuffer;
bindVertexArrayOES: BindVertexArrayOES;
bindVertexArray: BindVertexArray;
pixelStoreUnpack: PixelStoreUnpack;
pixelStoreUnpackPremultiplyAlpha: PixelStoreUnpackPremultiplyAlpha;
pixelStoreUnpackFlipY: PixelStoreUnpackFlipY;
Expand All @@ -64,12 +66,38 @@ class Context {
extTextureFilterAnisotropicMax: any;
extTextureHalfFloat: any;
extRenderToTextureHalfFloat: any;
extTimerQuery: any;

get timing() {
return getTimingAPI(this.gl);
}

get HALF_FLOAT(): GLenum | null {
return isWebGL2(this.gl) ? this.gl.HALF_FLOAT : this.gl.getExtension('OES_texture_half_float')?.HALF_FLOAT_OES;
}

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

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

get extColorBufferHalfFloat(): null | {
readonly RGBA16F_EXT: GLenum;
readonly RGB16F_EXT: GLenum;
readonly FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE_EXT: GLenum;
readonly UNSIGNED_NORMALIZED_EXT : GLenum;
} {
return this.gl.getExtension('EXT_color_buffer_half_float');
}

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,7 +125,7 @@ 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);
Expand All @@ -117,7 +145,6 @@ class Context {
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);
}

Expand Down Expand Up @@ -179,9 +206,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 @@ -295,9 +320,7 @@ class Context {
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 '../style-spec/util/color';
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
83 changes: 83 additions & 0 deletions src/gl/timing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {isWebGL2} from './webgl2';

interface Timing {
QUERY_COUNTER_BITS: 0x8864;
CURRENT_QUERY: 0x8865;
QUERY_RESULT: 0x8866;
QUERY_RESULT_AVAILABLE: 0x8867;
TIMESTAMP: 0x8E28;
TIME_ELAPSED: 0x88BF;
GPU_DISJOINT: 0x8FBB;

createQuery(): WebGLQuery;
deleteQuery(query: WebGLQuery): void;
isQueryEXT(query: WebGLQuery): boolean;
beginQuery(target: GLenum, query: WebGLQuery): void;
endQuery(target: GLenum): void;

getQuery(target: Timing['TIME_ELAPSED'], pname: Timing['CURRENT_QUERY']): WebGLQuery | null;
getQuery(target: Timing['TIMESTAMP'], pname: Timing['CURRENT_QUERY']): null;
getQuery(target: Timing['TIME_ELAPSED'], pname: Timing['QUERY_COUNTER_BITS']): GLint;
getQuery(target: Timing['TIMESTAMP'], pname: Timing['QUERY_COUNTER_BITS']): GLint;

getQueryParameter(query: WebGLQuery, pname: Timing['QUERY_RESULT_AVAILABLE']): GLboolean;
getQueryParameter(query: WebGLQuery, pname: Timing['QUERY_RESULT']): GLuint64;

queryCounter(query: WebGLQuery, target: Timing['TIMESTAMP']): void;
}

export type {Timing};

export function getTimingAPI(gl: WebGLRenderingContext) : null | Timing {
const result = {} as Partial<Timing>;

if (isWebGL2(gl)) {
const ext = gl.getExtension('EXT_disjoint_timer_query_webgl2');
if (!ext) return null;
for (const m of [
'createQuery',
'deleteQuery',
'isQuery',
'beginQuery',
'endQuery',
'getQuery',
'getQueryParameter',
]) {
result[m] = gl[m].bind(gl);
}
for (const p of ['GPU_DISJOINT', 'QUERY_COUNTER_BITS', 'TIMESTAMP', 'TIME_ELAPSED']) {
result[p] = ext[`${p}_EXT`];
}
for (const p of ['queryCounter']) {
result[p] = ext[`${p}EXT`].bind(ext);
}
for (const p of ['CURRENT_QUERY', 'QUERY_RESULT', 'QUERY_RESULT_AVAILABLE']) {
result[p] = gl[p];
}

return result as Timing;
}
{
const ext = gl.getExtension('EXT_disjoint_timer_query');
if (!ext) return null;
for (const m of [
'createQuery',
'deleteQuery',
'isQuery',
'beginQuery',
'endQuery',
'getQuery',
'queryCounter'
]) {
result[m] = ext[`${m}EXT`].bind(ext);
}
result.getQueryParameter = ext.getQueryObjectEXT.bind(ext);

for (const p of ['QUERY_COUNTER_BITS', 'CURRENT_QUERY', 'QUERY_RESULT', 'QUERY_RESULT_AVAILABLE',
'TIME_ELAPSED', 'TIMESTAMP', 'GPU_DISJOINT']) {
result[p] = ext[`${p}_EXT`];
}

return result as Timing;
}
}
17 changes: 8 additions & 9 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,17 @@ export class BindElementBuffer extends BaseValue<WebGLBuffer> {
}
}

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

constructor(context: Context) {
super(context);
this.vao = context.extVertexArrayObject;
}
export class BindVertexArray extends BaseValue<any> {
getDefault(): any {
return null;
}
set(v: any) {
if (!this.vao || v === this.current && !this.dirty) return;
this.vao.bindVertexArrayOES(v);
if (v === this.current && !this.dirty) return;
if (isWebGL2(this.gl)) {
this.gl.bindVertexArray(v);
} else {
this.gl.getExtension('OES_vertex_array_object')?.bindVertexArrayOES(v);
}
this.current = v;
this.dirty = false;
}
Expand Down
7 changes: 7 additions & 0 deletions src/gl/webgl2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/// <reference lib="dom" />

export function isWebGL2(
gl: WebGLRenderingContext
): gl is WebGL2RenderingContext {
return gl.getParameter(gl.VERSION).startsWith('WebGL 2.0');
}
2 changes: 1 addition & 1 deletion src/render/draw_heatmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ 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;
const internalFormat = context.HALF_FLOAT ?? gl.UNSIGNED_BYTE;
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, painter.width / 4, painter.height / 4, 0, gl.RGBA, internalFormat, null);
fbo.colorAttachment.set(texture);
}
Expand Down
22 changes: 13 additions & 9 deletions src/render/painter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ type PainterOptions = {
/**
* 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 @@ -511,7 +511,10 @@ class Painter {

gpuTimingStart(layer: StyleLayer) {
if (!this.options.gpuTiming) return;
const ext = this.context.extTimerQuery;
const timing = this.context.timing;
if (!timing)
return;

// 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
Expand All @@ -521,17 +524,17 @@ class Painter {
layerTimer = this.gpuTimers[layer.id] = {
calls: 0,
cpuTime: 0,
query: ext.createQueryEXT()
query: timing.createQuery()
};
}
layerTimer.calls++;
ext.beginQueryEXT(ext.TIME_ELAPSED_EXT, layerTimer.query);
timing.beginQuery(timing.TIME_ELAPSED, layerTimer.query);
}

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

collectGpuTimers() {
Expand All @@ -542,11 +545,12 @@ class Painter {

queryGpuTimers(gpuTimers: {[_: string]: any}) {
const layers = {};
const timing = this.context.timing;
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);

const gpuTime = timing.getQueryParameter(gpuTimer.query, timing.QUERY_RESULT) / (1000 * 1000);
timing.deleteQuery(gpuTimer.query);
layers[layerId] = gpuTime;
}
return layers;
Expand Down

0 comments on commit 4ccbab6

Please sign in to comment.