diff --git a/package-lock.json b/package-lock.json index bd16400a15c501dd7f677b77c573da1d66053668..eef02eb8afb9f6be4baa39aacd2c15fb8fc6b413 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/package.json b/package.json index 71b37e579b2bb9ea570c7b3493d43614ad1913b7..33864e84fe59b94d88a97e2ee7a7ae159ee4661e 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "@types/node-fetch": "^2.1.2", "@types/react": "^16.4.14", "@types/react-dom": "^16.0.8", + "@types/webgl2": "0.0.4", "benchmark": "^2.1.4", "cpx": "^1.5.0", "css-loader": "^1.0.0", diff --git a/src/mol-gl/webgl/buffer.ts b/src/mol-gl/webgl/buffer.ts index bde6066f1cd4aa9ca214ed43e8f926a5495f3361..8cd11ef8cb52ee4b7f5c9357a08aa366899be7e7 100644 --- a/src/mol-gl/webgl/buffer.ts +++ b/src/mol-gl/webgl/buffer.ts @@ -113,8 +113,8 @@ export function createBuffer(ctx: Context, array: ArrayType, itemSize: BufferIte const _itemCount = Math.floor(_length / itemSize) function updateData(array: ArrayType) { - gl.bindBuffer(_bufferType, _buffer) - gl.bufferData(_bufferType, array, _usageHint) + gl.bindBuffer(_bufferType, _buffer); + (gl as WebGLRenderingContext).bufferData(_bufferType, array, _usageHint) // TODO remove cast when webgl2 types are fixed } updateData(array) @@ -136,15 +136,15 @@ export function createBuffer(ctx: Context, array: ArrayType, itemSize: BufferIte updateData, updateSubData: (array: ArrayType, offset: number, count: number) => { - gl.bindBuffer(_bufferType, _buffer) - gl.bufferSubData(_bufferType, offset * _bpe, array.subarray(offset, offset + count)) + gl.bindBuffer(_bufferType, _buffer); + (gl as WebGLRenderingContext).bufferSubData(_bufferType, offset * _bpe, array.subarray(offset, offset + count)) // TODO remove cast when webgl2 types are fixed }, destroy: () => { if (destroyed) return - gl.bindBuffer(_bufferType, _buffer) + gl.bindBuffer(_bufferType, _buffer); // set size to 1 before deleting - gl.bufferData(_bufferType, 1, _usageHint) + (gl as WebGLRenderingContext).bufferData(_bufferType, 1, _usageHint) // TODO remove cast when webgl2 types are fixed gl.deleteBuffer(_buffer) destroyed = true ctx.bufferCount -= 1 @@ -164,7 +164,7 @@ export interface AttributeBuffer extends Buffer { export function createAttributeBuffer<T extends ArrayType, S extends BufferItemSize>(ctx: Context, array: ArrayType, itemSize: S, divisor: number, usageHint: UsageHint = 'dynamic'): AttributeBuffer { const { gl } = ctx - const { angleInstancedArrays } = ctx.extensions + const { instancedArrays } = ctx.extensions const buffer = createBuffer(ctx, array, itemSize, usageHint, 'attribute') const { _buffer, _bufferType, _dataType, _bpe } = buffer @@ -177,12 +177,12 @@ export function createAttributeBuffer<T extends ArrayType, S extends BufferItemS for (let i = 0; i < 4; ++i) { gl.enableVertexAttribArray(location + i) gl.vertexAttribPointer(location + i, 4, _dataType, false, 4 * 4 * _bpe, i * 4 * _bpe) - angleInstancedArrays.vertexAttribDivisorANGLE(location + i, divisor) + instancedArrays.vertexAttribDivisor(location + i, divisor) } } else { gl.enableVertexAttribArray(location) gl.vertexAttribPointer(location, itemSize, _dataType, false, 0, 0) - angleInstancedArrays.vertexAttribDivisorANGLE(location, divisor) + instancedArrays.vertexAttribDivisor(location, divisor) } } } diff --git a/src/mol-gl/webgl/compat.ts b/src/mol-gl/webgl/compat.ts new file mode 100644 index 0000000000000000000000000000000000000000..2ff0e6bb63e92f755d70098430fd59d918d512a9 --- /dev/null +++ b/src/mol-gl/webgl/compat.ts @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +export type GLRenderingContext = WebGLRenderingContext | WebGL2RenderingContext + +export function isWebGL(gl: any): gl is WebGLRenderingContext { + return typeof WebGLRenderingContext !== 'undefined' && gl instanceof WebGLRenderingContext +} + +export function isWebGL2(gl: any): gl is WebGL2RenderingContext { + return typeof WebGL2RenderingContext !== 'undefined' && gl instanceof WebGL2RenderingContext +} + +export interface COMPAT_instanced_arrays { + drawArraysInstanced(mode: number, first: number, count: number, primcount: number): void; + drawElementsInstanced(mode: number, count: number, type: number, offset: number, primcount: number): void; + vertexAttribDivisor(index: number, divisor: number): void; + readonly VERTEX_ATTRIB_ARRAY_DIVISOR: number; +} + +export function getInstancedArrays(gl: GLRenderingContext): COMPAT_instanced_arrays | null { + if (isWebGL2(gl)) { + return { + drawArraysInstanced: gl.drawArraysInstanced.bind(gl), + drawElementsInstanced: gl.drawElementsInstanced.bind(gl), + vertexAttribDivisor: gl.vertexAttribDivisor.bind(gl), + VERTEX_ATTRIB_ARRAY_DIVISOR: gl.VERTEX_ATTRIB_ARRAY_DIVISOR + } + } else { + const ext = gl.getExtension('ANGLE_instanced_arrays') + if (ext === null) return null + return { + drawArraysInstanced: ext.drawArraysInstancedANGLE.bind(ext), + drawElementsInstanced: ext.drawElementsInstancedANGLE.bind(ext), + vertexAttribDivisor: ext.vertexAttribDivisorANGLE.bind(ext), + VERTEX_ATTRIB_ARRAY_DIVISOR: ext.VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE + } + } +} + +export interface COMPAT_standard_derivatives { + readonly FRAGMENT_SHADER_DERIVATIVE_HINT: number; +} + +export function getStandardDerivatives(gl: GLRenderingContext): COMPAT_standard_derivatives | null { + if (isWebGL2(gl)) { + return { FRAGMENT_SHADER_DERIVATIVE_HINT: gl.FRAGMENT_SHADER_DERIVATIVE_HINT } + } else { + const ext = gl.getExtension('OES_standard_derivatives') + if (ext === null) { + throw new Error('Could not get "OES_standard_derivatives" extension') + } + return { FRAGMENT_SHADER_DERIVATIVE_HINT: ext.FRAGMENT_SHADER_DERIVATIVE_HINT_OES } + } +} + +export interface COMPAT_element_index_uint { +} + +export function getElementIndexUint(gl: GLRenderingContext): COMPAT_element_index_uint | null { + return isWebGL2(gl) ? {} : gl.getExtension('OES_standard_derivatives') +} + +export interface COMPAT_vertex_array_object { + readonly VERTEX_ARRAY_BINDING: number; + bindVertexArray(arrayObject: WebGLVertexArrayObject | null): void; + createVertexArray(): WebGLVertexArrayObject | null; + deleteVertexArray(arrayObject: WebGLVertexArrayObject): void; + isVertexArray(value: any): value is WebGLVertexArrayObject; +} + +export function getVertexArrayObject(gl: GLRenderingContext): COMPAT_vertex_array_object | null { + if (isWebGL2(gl)) { + return { + VERTEX_ARRAY_BINDING: gl.VERTEX_ARRAY_BINDING, + bindVertexArray: gl.bindVertexArray.bind(gl), + createVertexArray: gl.createVertexArray.bind(gl), + deleteVertexArray: gl.deleteVertexArray.bind(gl), + isVertexArray: gl.isVertexArray.bind(gl) as (value: any) => value is WebGLVertexArrayObject // TODO change when webgl2 types are fixed + } + } else { + const ext = gl.getExtension('OES_vertex_array_object') + if (ext === null) return null + return { + VERTEX_ARRAY_BINDING: ext.VERTEX_ARRAY_BINDING_OES, + bindVertexArray: ext.bindVertexArrayOES.bind(ext), + createVertexArray: ext.createVertexArrayOES.bind(ext), + deleteVertexArray: ext.deleteVertexArrayOES.bind(ext), + isVertexArray: ext.isVertexArrayOES.bind(ext) + } + } +} \ No newline at end of file diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts index 69b99ac5ac13092a40f87bf3ce82dc638fe5a023..0ba44529a345714fe0bae2c83c907cd0f9173e1c 100644 --- a/src/mol-gl/webgl/context.ts +++ b/src/mol-gl/webgl/context.ts @@ -6,12 +6,24 @@ import { createProgramCache, ProgramCache } from './program' import { createShaderCache, ShaderCache } from './shader' +import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, getVertexArrayObject, isWebGL2, COMPAT_element_index_uint, getElementIndexUint } from './compat'; + +export function getGLContext(canvas: HTMLCanvasElement, contextAttributes?: WebGLContextAttributes): GLRenderingContext | null { + function getContext(contextId: 'webgl' | 'experimental-webgl' | 'webgl2') { + try { + return canvas.getContext(contextId, contextAttributes) as GLRenderingContext | null + } catch (e) { + return null + } + } + return getContext('webgl2') || getContext('webgl') || getContext('experimental-webgl') +} function getPixelRatio() { return (typeof window !== 'undefined') ? window.devicePixelRatio : 1 } -function unbindResources (gl: WebGLRenderingContext) { +function unbindResources (gl: GLRenderingContext) { // bind null to all texture units const maxTextureImageUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) for (let i = 0; i < maxTextureImageUnits; ++i) { @@ -35,7 +47,7 @@ function unbindResources (gl: WebGLRenderingContext) { unbindFramebuffer(gl) } -function unbindFramebuffer(gl: WebGLRenderingContext) { +function unbindFramebuffer(gl: GLRenderingContext) { gl.bindFramebuffer(gl.FRAMEBUFFER, null) } @@ -55,20 +67,21 @@ export function createImageData(buffer: Uint8Array, width: number, height: numbe } type Extensions = { - angleInstancedArrays: ANGLE_instanced_arrays - standardDerivatives: OES_standard_derivatives - oesElementIndexUint: OES_element_index_uint | null - oesVertexArrayObject: OES_vertex_array_object | null + instancedArrays: COMPAT_instanced_arrays + standardDerivatives: COMPAT_standard_derivatives + elementIndexUint: COMPAT_element_index_uint | null + vertexArrayObject: COMPAT_vertex_array_object | null } /** A WebGL context object, including the rendering context, resource caches and counts */ export interface Context { - gl: WebGLRenderingContext - extensions: Extensions - pixelRatio: number + readonly gl: GLRenderingContext + readonly isWebGL2: boolean + readonly extensions: Extensions + readonly pixelRatio: number - shaderCache: ShaderCache - programCache: ProgramCache + readonly shaderCache: ShaderCache + readonly programCache: ProgramCache bufferCount: number framebufferCount: number @@ -87,22 +100,24 @@ export interface Context { destroy: () => void } -export function createContext(gl: WebGLRenderingContext): Context { - const angleInstancedArrays = gl.getExtension('ANGLE_instanced_arrays') - if (angleInstancedArrays === null) { - throw new Error('Could not get "ANGLE_instanced_arrays" extension') + + +export function createContext(gl: GLRenderingContext): Context { + const instancedArrays = getInstancedArrays(gl) + if (instancedArrays === null) { + throw new Error('Could not find support for "instanced_arrays"') } - const standardDerivatives = gl.getExtension('OES_standard_derivatives') + const standardDerivatives = getStandardDerivatives(gl) if (standardDerivatives === null) { - throw new Error('Could not get "OES_standard_derivatives" extension') + throw new Error('Could not find support for "standard_derivatives"') } - const oesElementIndexUint = gl.getExtension('OES_element_index_uint') - if (oesElementIndexUint === null) { - console.warn('Could not get "OES_element_index_uint" extension') + const elementIndexUint = getElementIndexUint(gl) + if (elementIndexUint === null) { + console.warn('Could not find support for "element_index_uint"') } - const oesVertexArrayObject = gl.getExtension('OES_vertex_array_object') - if (oesVertexArrayObject === null) { - console.log('Could not get "OES_vertex_array_object" extension') + const vertexArrayObject = getVertexArrayObject(gl) + if (vertexArrayObject === null) { + console.log('Could not find support for "vertex_array_object"') } const shaderCache = createShaderCache() @@ -114,7 +129,8 @@ export function createContext(gl: WebGLRenderingContext): Context { return { gl, - extensions: { angleInstancedArrays, standardDerivatives, oesElementIndexUint, oesVertexArrayObject }, + isWebGL2: isWebGL2(gl), + extensions: { instancedArrays, standardDerivatives, elementIndexUint, vertexArrayObject }, pixelRatio: getPixelRatio(), shaderCache, diff --git a/src/mol-gl/webgl/render-item.ts b/src/mol-gl/webgl/render-item.ts index 9795b85aa7298019ce86a83eaea565e0a00e6633..6f3d3e6c8e1206af37bb731526ff9bf7768c1ab0 100644 --- a/src/mol-gl/webgl/render-item.ts +++ b/src/mol-gl/webgl/render-item.ts @@ -50,7 +50,7 @@ const RenderVariantDefines = { export type RenderVariant = keyof typeof RenderVariantDefines type ProgramVariants = { [k: string]: ReferenceItem<Program> } -type VertexArrayVariants = { [k: string]: WebGLVertexArrayObjectOES | undefined } +type VertexArrayVariants = { [k: string]: WebGLVertexArrayObjectOES | null } interface ValueChanges { attributes: boolean @@ -70,7 +70,7 @@ interface ValueChanges { export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: ShaderCode, schema: RenderableSchema, values: RenderableValues): RenderItem { const id = getNextRenderItemId() const { programCache } = ctx - const { angleInstancedArrays, oesVertexArrayObject } = ctx.extensions + const { instancedArrays, vertexArrayObject } = ctx.extensions const { attributeValues, defineValues, textureValues, uniformValues } = splitValues(schema, values) const versions = getValueVersions(values) @@ -127,8 +127,8 @@ export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: S const program = programs[variant].value const vertexArray = vertexArrays[variant] program.setUniforms(uniformValues) - if (oesVertexArrayObject && vertexArray) { - oesVertexArrayObject.bindVertexArrayOES(vertexArray) + if (vertexArrayObject && vertexArray) { + vertexArrayObject.bindVertexArray(vertexArray) // need to bind elements buffer explicitely since it is not always recorded in the VAO if (elementsBuffer) elementsBuffer.bind() } else { @@ -137,9 +137,9 @@ export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: S } program.bindTextures(textures) if (elementsBuffer) { - angleInstancedArrays.drawElementsInstancedANGLE(glDrawMode, drawCount, elementsBuffer._dataType, 0, instanceCount); + instancedArrays.drawElementsInstanced(glDrawMode, drawCount, elementsBuffer._dataType, 0, instanceCount); } else { - angleInstancedArrays.drawArraysInstancedANGLE(glDrawMode, 0, drawCount, instanceCount) + instancedArrays.drawArraysInstanced(glDrawMode, 0, drawCount, instanceCount) } }, update: () => { diff --git a/src/mol-gl/webgl/texture.ts b/src/mol-gl/webgl/texture.ts index 72f0bc3f9509d71cb9aec1a282249a19dc42f4ca..d7e94cc072f310e30e3e2069a4b8f95bcc20671f 100644 --- a/src/mol-gl/webgl/texture.ts +++ b/src/mol-gl/webgl/texture.ts @@ -104,7 +104,7 @@ export function createTexture(ctx: Context, _format: TextureFormat, _type: Textu // unpack alignment of 1 since we use textures only for data gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); // gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); - gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, format, type, array) + (gl as WebGLRenderingContext).texImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, format, type, array) // TODO remove cast when webgl2 types are fixed _width = width _height = height gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter) diff --git a/src/mol-gl/webgl/uniform.ts b/src/mol-gl/webgl/uniform.ts index b3dfeb4c90e90efe04e59ff6574c52fbc68c5a3b..a8a92ad230100f8574e63857eb863541e023dc89 100644 --- a/src/mol-gl/webgl/uniform.ts +++ b/src/mol-gl/webgl/uniform.ts @@ -39,11 +39,11 @@ function createUniformSetter(ctx: Context, program: WebGLProgram, name: string, switch (kind) { case 'f': return (value: number) => gl.uniform1f(location, value) case 'i': case 't2': return (value: number) => gl.uniform1i(location, value) - case 'v2': return (value: Vec2) => gl.uniform2fv(location, value) - case 'v3': return (value: Vec3) => gl.uniform3fv(location, value) - case 'v4': return (value: Vec4) => gl.uniform4fv(location, value) - case 'm3': return (value: Mat3) => gl.uniformMatrix3fv(location, false, value) - case 'm4': return (value: Mat4) => gl.uniformMatrix4fv(location, false, value) + case 'v2': return (value: Vec2) => (gl as WebGLRenderingContext).uniform2fv(location, value) // TODO remove cast when webgl2 types are fixed + case 'v3': return (value: Vec3) => (gl as WebGLRenderingContext).uniform3fv(location, value) + case 'v4': return (value: Vec4) => (gl as WebGLRenderingContext).uniform4fv(location, value) + case 'm3': return (value: Mat3) => (gl as WebGLRenderingContext).uniformMatrix3fv(location, false, value) + case 'm4': return (value: Mat4) => (gl as WebGLRenderingContext).uniformMatrix4fv(location, false, value) } } diff --git a/src/mol-gl/webgl/vertex-array.ts b/src/mol-gl/webgl/vertex-array.ts index aaf92f2754036dca4b0e5cbb89f13e5e6b748d9a..19a2adc2f1da4492c37f642306a3c253edfb6cd7 100644 --- a/src/mol-gl/webgl/vertex-array.ts +++ b/src/mol-gl/webgl/vertex-array.ts @@ -9,23 +9,23 @@ import { Program } from './program'; import { AttributeBuffers, ElementsBuffer } from './buffer'; export function createVertexArray(ctx: Context, program: Program, attributeBuffers: AttributeBuffers, elementsBuffer?: ElementsBuffer) { - const { oesVertexArrayObject } = ctx.extensions - let vertexArray: WebGLVertexArrayObjectOES | undefined = undefined - if (oesVertexArrayObject) { - vertexArray = oesVertexArrayObject.createVertexArrayOES() - oesVertexArrayObject.bindVertexArrayOES(vertexArray) + const { vertexArrayObject } = ctx.extensions + let vertexArray: WebGLVertexArrayObject | null = null + if (vertexArrayObject) { + vertexArray = vertexArrayObject.createVertexArray() + vertexArrayObject.bindVertexArray(vertexArray) if (elementsBuffer) elementsBuffer.bind() program.bindAttributes(attributeBuffers) ctx.vaoCount += 1 - oesVertexArrayObject.bindVertexArrayOES(null!) + vertexArrayObject.bindVertexArray(null!) } return vertexArray } -export function deleteVertexArray(ctx: Context, vertexArray?: WebGLVertexArrayObjectOES) { - const { oesVertexArrayObject } = ctx.extensions - if (oesVertexArrayObject && vertexArray) { - oesVertexArrayObject.deleteVertexArrayOES(vertexArray) +export function deleteVertexArray(ctx: Context, vertexArray: WebGLVertexArrayObject | null) { + const { vertexArrayObject } = ctx.extensions + if (vertexArrayObject && vertexArray) { + vertexArrayObject.deleteVertexArray(vertexArray) ctx.vaoCount -= 1 } } \ No newline at end of file diff --git a/src/mol-view/viewer.ts b/src/mol-view/viewer.ts index fd4f1ec27d44268c387a77da4d8c7b81cac63d49..91a42cdacca0dd60d7cf26622b78c9af09c60e0c 100644 --- a/src/mol-view/viewer.ts +++ b/src/mol-view/viewer.ts @@ -16,7 +16,7 @@ import TrackballControls from './controls/trackball' import { Viewport } from './camera/util' import { PerspectiveCamera } from './camera/perspective' import { resizeCanvas } from './util'; -import { createContext } from 'mol-gl/webgl/context'; +import { createContext, getGLContext } from 'mol-gl/webgl/context'; import { Representation } from 'mol-geo/representation'; import { createRenderTarget } from 'mol-gl/webgl/render-target'; import Scene from 'mol-gl/scene'; @@ -59,17 +59,6 @@ interface Viewer { dispose: () => void } -function getWebGLContext(canvas: HTMLCanvasElement, contextAttributes?: WebGLContextAttributes) { - function getContext(contextId: 'webgl' | 'experimental-webgl') { - try { - return canvas.getContext(contextId, contextAttributes) - } catch (e) { - return null - } - } - return getContext('webgl') || getContext('experimental-webgl') -} - namespace Viewer { export function create(canvas: HTMLCanvasElement, container: Element): Viewer { const reprMap = new Map<Representation<any>, Set<RenderObject>>() @@ -87,7 +76,7 @@ namespace Viewer { }) // camera.lookAt(Vec3.create(0, 0, 0)) - const gl = getWebGLContext(canvas, { + const gl = getGLContext(canvas, { alpha: false, antialias: true, depth: true, diff --git a/tsconfig.json b/tsconfig.json index 31421faf7852e8d4f240815c857b7b047af3467a..413bfe5853cfed2ef8b0fe98eea7593d816baa03 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,5 +29,8 @@ "mol-view": ["./mol-view"] } }, - "include": [ "**/*" ] + "include": [ "**/*" ], + "files": [ + "./node_modules/@types/webgl2/index.d.ts" + ], } \ No newline at end of file