UNPKG

@plotly/regl

Version:

regl is a fast functional WebGL framework.

1,860 lines (1,644 loc) 313 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.createREGL = factory()); }(this, (function () { 'use strict'; var isTypedArray = function (x) { return ( x instanceof Uint8Array || x instanceof Uint16Array || x instanceof Uint32Array || x instanceof Int8Array || x instanceof Int16Array || x instanceof Int32Array || x instanceof Float32Array || x instanceof Float64Array || x instanceof Uint8ClampedArray ) } var extend = function (base, opts) { var keys = Object.keys(opts) for (var i = 0; i < keys.length; ++i) { base[keys[i]] = opts[keys[i]] } return base } // Error checking and parameter validation. // // Statements for the form `check.someProcedure(...)` get removed by // a browserify transform for optimized/minified bundles. // /* globals atob */ var endl = '\n' // only used for extracting shader names. if atob not present, then errors // will be slightly crappier function decodeB64 (str) { if (typeof atob !== 'undefined') { return atob(str) } return 'base64:' + str } function raise (message) { var error = new Error('(regl) ' + message) console.error(error) throw error } function check (pred, message) { if (!pred) { raise(message) } } function encolon (message) { if (message) { return ': ' + message } return '' } function checkParameter (param, possibilities, message) { if (!(param in possibilities)) { raise('unknown parameter (' + param + ')' + encolon(message) + '. possible values: ' + Object.keys(possibilities).join()) } } function checkIsTypedArray (data, message) { if (!isTypedArray(data)) { raise( 'invalid parameter type' + encolon(message) + '. must be a typed array') } } function standardTypeEh (value, type) { switch (type) { case 'number': return typeof value === 'number' case 'object': return typeof value === 'object' case 'string': return typeof value === 'string' case 'boolean': return typeof value === 'boolean' case 'function': return typeof value === 'function' case 'undefined': return typeof value === 'undefined' case 'symbol': return typeof value === 'symbol' } } function checkTypeOf (value, type, message) { if (!standardTypeEh(value, type)) { raise( 'invalid parameter type' + encolon(message) + '. expected ' + type + ', got ' + (typeof value)) } } function checkNonNegativeInt (value, message) { if (!((value >= 0) && ((value | 0) === value))) { raise('invalid parameter type, (' + value + ')' + encolon(message) + '. must be a nonnegative integer') } } function checkOneOf (value, list, message) { if (list.indexOf(value) < 0) { raise('invalid value' + encolon(message) + '. must be one of: ' + list) } } var constructorKeys = [ 'gl', 'canvas', 'container', 'attributes', 'pixelRatio', 'extensions', 'optionalExtensions', 'profile', 'onDone', 'cachedCode' ] function checkConstructor (obj) { Object.keys(obj).forEach(function (key) { if (constructorKeys.indexOf(key) < 0) { raise('invalid regl constructor argument "' + key + '". must be one of ' + constructorKeys) } }) } function leftPad (str, n) { str = str + '' while (str.length < n) { str = ' ' + str } return str } function ShaderFile () { this.name = 'unknown' this.lines = [] this.index = {} this.hasErrors = false } function ShaderLine (number, line) { this.number = number this.line = line this.errors = [] } function ShaderError (fileNumber, lineNumber, message) { this.file = fileNumber this.line = lineNumber this.message = message } function guessCommand () { var error = new Error() var stack = (error.stack || error).toString() var pat = /compileProcedure.*\n\s*at.*\((.*)\)/.exec(stack) if (pat) { return pat[1] } var pat2 = /compileProcedure.*\n\s*at\s+(.*)(\n|$)/.exec(stack) if (pat2) { return pat2[1] } return 'unknown' } function guessCallSite () { var error = new Error() var stack = (error.stack || error).toString() var pat = /at REGLCommand.*\n\s+at.*\((.*)\)/.exec(stack) if (pat) { return pat[1] } var pat2 = /at REGLCommand.*\n\s+at\s+(.*)\n/.exec(stack) if (pat2) { return pat2[1] } return 'unknown' } function parseSource (source, command) { var lines = source.split('\n') var lineNumber = 1 var fileNumber = 0 var files = { unknown: new ShaderFile(), 0: new ShaderFile() } files.unknown.name = files[0].name = command || guessCommand() files.unknown.lines.push(new ShaderLine(0, '')) for (var i = 0; i < lines.length; ++i) { var line = lines[i] var parts = /^\s*#\s*(\w+)\s+(.+)\s*$/.exec(line) if (parts) { switch (parts[1]) { case 'line': var lineNumberInfo = /(\d+)(\s+\d+)?/.exec(parts[2]) if (lineNumberInfo) { lineNumber = lineNumberInfo[1] | 0 if (lineNumberInfo[2]) { fileNumber = lineNumberInfo[2] | 0 if (!(fileNumber in files)) { files[fileNumber] = new ShaderFile() } } } break case 'define': var nameInfo = /SHADER_NAME(_B64)?\s+(.*)$/.exec(parts[2]) if (nameInfo) { files[fileNumber].name = (nameInfo[1] ? decodeB64(nameInfo[2]) : nameInfo[2]) } break } } files[fileNumber].lines.push(new ShaderLine(lineNumber++, line)) } Object.keys(files).forEach(function (fileNumber) { var file = files[fileNumber] file.lines.forEach(function (line) { file.index[line.number] = line }) }) return files } function parseErrorLog (errLog) { var result = [] errLog.split('\n').forEach(function (errMsg) { if (errMsg.length < 5) { return } var parts = /^ERROR:\s+(\d+):(\d+):\s*(.*)$/.exec(errMsg) if (parts) { result.push(new ShaderError( parts[1] | 0, parts[2] | 0, parts[3].trim())) } else if (errMsg.length > 0) { result.push(new ShaderError('unknown', 0, errMsg)) } }) return result } function annotateFiles (files, errors) { errors.forEach(function (error) { var file = files[error.file] if (file) { var line = file.index[error.line] if (line) { line.errors.push(error) file.hasErrors = true return } } files.unknown.hasErrors = true files.unknown.lines[0].errors.push(error) }) } function checkShaderError (gl, shader, source, type, command) { if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { var errLog = gl.getShaderInfoLog(shader) var typeName = type === gl.FRAGMENT_SHADER ? 'fragment' : 'vertex' checkCommandType(source, 'string', typeName + ' shader source must be a string', command) var files = parseSource(source, command) var errors = parseErrorLog(errLog) annotateFiles(files, errors) Object.keys(files).forEach(function (fileNumber) { var file = files[fileNumber] if (!file.hasErrors) { return } var strings = [''] var styles = [''] function push (str, style) { strings.push(str) styles.push(style || '') } push('file number ' + fileNumber + ': ' + file.name + '\n', 'color:red;text-decoration:underline;font-weight:bold') file.lines.forEach(function (line) { if (line.errors.length > 0) { push(leftPad(line.number, 4) + '| ', 'background-color:yellow; font-weight:bold') push(line.line + endl, 'color:red; background-color:yellow; font-weight:bold') // try to guess token var offset = 0 line.errors.forEach(function (error) { var message = error.message var token = /^\s*'(.*)'\s*:\s*(.*)$/.exec(message) if (token) { var tokenPat = token[1] message = token[2] switch (tokenPat) { case 'assign': tokenPat = '=' break } offset = Math.max(line.line.indexOf(tokenPat, offset), 0) } else { offset = 0 } push(leftPad('| ', 6)) push(leftPad('^^^', offset + 3) + endl, 'font-weight:bold') push(leftPad('| ', 6)) push(message + endl, 'font-weight:bold') }) push(leftPad('| ', 6) + endl) } else { push(leftPad(line.number, 4) + '| ') push(line.line + endl, 'color:red') } }) if (typeof document !== 'undefined' && !window.chrome) { styles[0] = strings.join('%c') console.log.apply(console, styles) } else { console.log(strings.join('')) } }) check.raise('Error compiling ' + typeName + ' shader, ' + files[0].name) } } function checkLinkError (gl, program, fragShader, vertShader, command) { if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { var errLog = gl.getProgramInfoLog(program) var fragParse = parseSource(fragShader, command) var vertParse = parseSource(vertShader, command) var header = 'Error linking program with vertex shader, "' + vertParse[0].name + '", and fragment shader "' + fragParse[0].name + '"' if (typeof document !== 'undefined') { console.log('%c' + header + endl + '%c' + errLog, 'color:red;text-decoration:underline;font-weight:bold', 'color:red') } else { console.log(header + endl + errLog) } check.raise(header) } } function saveCommandRef (object) { object._commandRef = guessCommand() } function saveDrawCommandInfo (opts, uniforms, attributes, stringStore) { saveCommandRef(opts) function id (str) { if (str) { return stringStore.id(str) } return 0 } opts._fragId = id(opts.static.frag) opts._vertId = id(opts.static.vert) function addProps (dict, set) { Object.keys(set).forEach(function (u) { dict[stringStore.id(u)] = true }) } var uniformSet = opts._uniformSet = {} addProps(uniformSet, uniforms.static) addProps(uniformSet, uniforms.dynamic) var attributeSet = opts._attributeSet = {} addProps(attributeSet, attributes.static) addProps(attributeSet, attributes.dynamic) opts._hasCount = ( 'count' in opts.static || 'count' in opts.dynamic || 'elements' in opts.static || 'elements' in opts.dynamic) } function commandRaise (message, command) { var callSite = guessCallSite() raise(message + ' in command ' + (command || guessCommand()) + (callSite === 'unknown' ? '' : ' called from ' + callSite)) } function checkCommand (pred, message, command) { if (!pred) { commandRaise(message, command || guessCommand()) } } function checkParameterCommand (param, possibilities, message, command) { if (!(param in possibilities)) { commandRaise( 'unknown parameter (' + param + ')' + encolon(message) + '. possible values: ' + Object.keys(possibilities).join(), command || guessCommand()) } } function checkCommandType (value, type, message, command) { if (!standardTypeEh(value, type)) { commandRaise( 'invalid parameter type' + encolon(message) + '. expected ' + type + ', got ' + (typeof value), command || guessCommand()) } } function checkOptional (block) { block() } function checkFramebufferFormat (attachment, texFormats, rbFormats) { if (attachment.texture) { checkOneOf( attachment.texture._texture.internalformat, texFormats, 'unsupported texture format for attachment') } else { checkOneOf( attachment.renderbuffer._renderbuffer.format, rbFormats, 'unsupported renderbuffer format for attachment') } } var GL_CLAMP_TO_EDGE = 0x812F var GL_NEAREST = 0x2600 var GL_NEAREST_MIPMAP_NEAREST = 0x2700 var GL_LINEAR_MIPMAP_NEAREST = 0x2701 var GL_NEAREST_MIPMAP_LINEAR = 0x2702 var GL_LINEAR_MIPMAP_LINEAR = 0x2703 var GL_BYTE = 5120 var GL_UNSIGNED_BYTE = 5121 var GL_SHORT = 5122 var GL_UNSIGNED_SHORT = 5123 var GL_INT = 5124 var GL_UNSIGNED_INT = 5125 var GL_FLOAT = 5126 var GL_UNSIGNED_SHORT_4_4_4_4 = 0x8033 var GL_UNSIGNED_SHORT_5_5_5_1 = 0x8034 var GL_UNSIGNED_SHORT_5_6_5 = 0x8363 var GL_UNSIGNED_INT_24_8_WEBGL = 0x84FA var GL_HALF_FLOAT_OES = 0x8D61 var TYPE_SIZE = {} TYPE_SIZE[GL_BYTE] = TYPE_SIZE[GL_UNSIGNED_BYTE] = 1 TYPE_SIZE[GL_SHORT] = TYPE_SIZE[GL_UNSIGNED_SHORT] = TYPE_SIZE[GL_HALF_FLOAT_OES] = TYPE_SIZE[GL_UNSIGNED_SHORT_5_6_5] = TYPE_SIZE[GL_UNSIGNED_SHORT_4_4_4_4] = TYPE_SIZE[GL_UNSIGNED_SHORT_5_5_5_1] = 2 TYPE_SIZE[GL_INT] = TYPE_SIZE[GL_UNSIGNED_INT] = TYPE_SIZE[GL_FLOAT] = TYPE_SIZE[GL_UNSIGNED_INT_24_8_WEBGL] = 4 function pixelSize (type, channels) { if (type === GL_UNSIGNED_SHORT_5_5_5_1 || type === GL_UNSIGNED_SHORT_4_4_4_4 || type === GL_UNSIGNED_SHORT_5_6_5) { return 2 } else if (type === GL_UNSIGNED_INT_24_8_WEBGL) { return 4 } else { return TYPE_SIZE[type] * channels } } function isPow2 (v) { return !(v & (v - 1)) && (!!v) } function checkTexture2D (info, mipData, limits) { var i var w = mipData.width var h = mipData.height var c = mipData.channels // Check texture shape check(w > 0 && w <= limits.maxTextureSize && h > 0 && h <= limits.maxTextureSize, 'invalid texture shape') // check wrap mode if (info.wrapS !== GL_CLAMP_TO_EDGE || info.wrapT !== GL_CLAMP_TO_EDGE) { check(isPow2(w) && isPow2(h), 'incompatible wrap mode for texture, both width and height must be power of 2') } if (mipData.mipmask === 1) { if (w !== 1 && h !== 1) { check( info.minFilter !== GL_NEAREST_MIPMAP_NEAREST && info.minFilter !== GL_NEAREST_MIPMAP_LINEAR && info.minFilter !== GL_LINEAR_MIPMAP_NEAREST && info.minFilter !== GL_LINEAR_MIPMAP_LINEAR, 'min filter requires mipmap') } } else { // texture must be power of 2 check(isPow2(w) && isPow2(h), 'texture must be a square power of 2 to support mipmapping') check(mipData.mipmask === (w << 1) - 1, 'missing or incomplete mipmap data') } if (mipData.type === GL_FLOAT) { if (limits.extensions.indexOf('oes_texture_float_linear') < 0) { check(info.minFilter === GL_NEAREST && info.magFilter === GL_NEAREST, 'filter not supported, must enable oes_texture_float_linear') } check(!info.genMipmaps, 'mipmap generation not supported with float textures') } // check image complete var mipimages = mipData.images for (i = 0; i < 16; ++i) { if (mipimages[i]) { var mw = w >> i var mh = h >> i check(mipData.mipmask & (1 << i), 'missing mipmap data') var img = mipimages[i] check( img.width === mw && img.height === mh, 'invalid shape for mip images') check( img.format === mipData.format && img.internalformat === mipData.internalformat && img.type === mipData.type, 'incompatible type for mip image') if (img.compressed) { // TODO: check size for compressed images } else if (img.data) { // check(img.data.byteLength === mw * mh * // Math.max(pixelSize(img.type, c), img.unpackAlignment), var rowSize = Math.ceil(pixelSize(img.type, c) * mw / img.unpackAlignment) * img.unpackAlignment check(img.data.byteLength === rowSize * mh, 'invalid data for image, buffer size is inconsistent with image format') } else if (img.element) { // TODO: check element can be loaded } else if (img.copy) { // TODO: check compatible format and type } } else if (!info.genMipmaps) { check((mipData.mipmask & (1 << i)) === 0, 'extra mipmap data') } } if (mipData.compressed) { check(!info.genMipmaps, 'mipmap generation for compressed images not supported') } } function checkTextureCube (texture, info, faces, limits) { var w = texture.width var h = texture.height var c = texture.channels // Check texture shape check( w > 0 && w <= limits.maxTextureSize && h > 0 && h <= limits.maxTextureSize, 'invalid texture shape') check( w === h, 'cube map must be square') check( info.wrapS === GL_CLAMP_TO_EDGE && info.wrapT === GL_CLAMP_TO_EDGE, 'wrap mode not supported by cube map') for (var i = 0; i < faces.length; ++i) { var face = faces[i] check( face.width === w && face.height === h, 'inconsistent cube map face shape') if (info.genMipmaps) { check(!face.compressed, 'can not generate mipmap for compressed textures') check(face.mipmask === 1, 'can not specify mipmaps and generate mipmaps') } else { // TODO: check mip and filter mode } var mipmaps = face.images for (var j = 0; j < 16; ++j) { var img = mipmaps[j] if (img) { var mw = w >> j var mh = h >> j check(face.mipmask & (1 << j), 'missing mipmap data') check( img.width === mw && img.height === mh, 'invalid shape for mip images') check( img.format === texture.format && img.internalformat === texture.internalformat && img.type === texture.type, 'incompatible type for mip image') if (img.compressed) { // TODO: check size for compressed images } else if (img.data) { check(img.data.byteLength === mw * mh * Math.max(pixelSize(img.type, c), img.unpackAlignment), 'invalid data for image, buffer size is inconsistent with image format') } else if (img.element) { // TODO: check element can be loaded } else if (img.copy) { // TODO: check compatible format and type } } } } } var check$1 = extend(check, { optional: checkOptional, raise: raise, commandRaise: commandRaise, command: checkCommand, parameter: checkParameter, commandParameter: checkParameterCommand, constructor: checkConstructor, type: checkTypeOf, commandType: checkCommandType, isTypedArray: checkIsTypedArray, nni: checkNonNegativeInt, oneOf: checkOneOf, shaderError: checkShaderError, linkError: checkLinkError, callSite: guessCallSite, saveCommandRef: saveCommandRef, saveDrawInfo: saveDrawCommandInfo, framebufferFormat: checkFramebufferFormat, guessCommand: guessCommand, texture2D: checkTexture2D, textureCube: checkTextureCube }); var VARIABLE_COUNTER = 0 var DYN_FUNC = 0 var DYN_CONSTANT = 5 var DYN_ARRAY = 6 function DynamicVariable (type, data) { this.id = (VARIABLE_COUNTER++) this.type = type this.data = data } function escapeStr (str) { return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"') } function splitParts (str) { if (str.length === 0) { return [] } var firstChar = str.charAt(0) var lastChar = str.charAt(str.length - 1) if (str.length > 1 && firstChar === lastChar && (firstChar === '"' || firstChar === "'")) { return ['"' + escapeStr(str.substr(1, str.length - 2)) + '"'] } var parts = /\[(false|true|null|\d+|'[^']*'|"[^"]*")\]/.exec(str) if (parts) { return ( splitParts(str.substr(0, parts.index)) .concat(splitParts(parts[1])) .concat(splitParts(str.substr(parts.index + parts[0].length))) ) } var subparts = str.split('.') if (subparts.length === 1) { return ['"' + escapeStr(str) + '"'] } var result = [] for (var i = 0; i < subparts.length; ++i) { result = result.concat(splitParts(subparts[i])) } return result } function toAccessorString (str) { return '[' + splitParts(str).join('][') + ']' } function defineDynamic (type, data) { return new DynamicVariable(type, toAccessorString(data + '')) } function isDynamic (x) { return (typeof x === 'function' && !x._reglType) || (x instanceof DynamicVariable) } function unbox (x, path) { if (typeof x === 'function') { return new DynamicVariable(DYN_FUNC, x) } else if (typeof x === 'number' || typeof x === 'boolean') { return new DynamicVariable(DYN_CONSTANT, x) } else if (Array.isArray(x)) { return new DynamicVariable(DYN_ARRAY, x.map(function (y, i) { return unbox(y, path + '[' + i + ']') })) } else if (x instanceof DynamicVariable) { return x } check$1(false, 'invalid option type in uniform ' + path) } var dynamic = { DynamicVariable: DynamicVariable, define: defineDynamic, isDynamic: isDynamic, unbox: unbox, accessor: toAccessorString }; /* globals requestAnimationFrame, cancelAnimationFrame */ var raf = { next: typeof requestAnimationFrame === 'function' ? function (cb) { return requestAnimationFrame(cb) } : function (cb) { return setTimeout(cb, 16) }, cancel: typeof cancelAnimationFrame === 'function' ? function (raf) { return cancelAnimationFrame(raf) } : clearTimeout }; /* globals performance */ var clock = (typeof performance !== 'undefined' && performance.now) ? function () { return performance.now() } : function () { return +(new Date()) }; function createStringStore () { var stringIds = { '': 0 } var stringValues = [''] return { id: function (str) { var result = stringIds[str] if (result) { return result } result = stringIds[str] = stringValues.length stringValues.push(str) return result }, str: function (id) { return stringValues[id] } } } // Context and canvas creation helper functions function createCanvas (element, onDone, pixelRatio) { var canvas = document.createElement('canvas') extend(canvas.style, { border: 0, margin: 0, padding: 0, top: 0, left: 0, width: '100%', height: '100%' }) element.appendChild(canvas) if (element === document.body) { canvas.style.position = 'absolute' extend(element.style, { margin: 0, padding: 0 }) } function resize () { var w = window.innerWidth var h = window.innerHeight if (element !== document.body) { var bounds = canvas.getBoundingClientRect() w = bounds.right - bounds.left h = bounds.bottom - bounds.top } canvas.width = pixelRatio * w canvas.height = pixelRatio * h } var resizeObserver if (element !== document.body && typeof ResizeObserver === 'function') { // ignore 'ResizeObserver' is not defined // eslint-disable-next-line resizeObserver = new ResizeObserver(function () { // setTimeout to avoid flicker setTimeout(resize) }) resizeObserver.observe(element) } else { window.addEventListener('resize', resize, false) } function onDestroy () { if (resizeObserver) { resizeObserver.disconnect() } else { window.removeEventListener('resize', resize) } element.removeChild(canvas) } resize() return { canvas: canvas, onDestroy: onDestroy } } function createContext (canvas, contextAttributes) { function get (name) { try { return canvas.getContext(name, contextAttributes) } catch (e) { return null } } return ( get('webgl') || get('experimental-webgl') || get('webgl-experimental') ) } function isHTMLElement (obj) { return ( typeof obj.nodeName === 'string' && typeof obj.appendChild === 'function' && typeof obj.getBoundingClientRect === 'function' ) } function isWebGLContext (obj) { return ( typeof obj.drawArrays === 'function' || typeof obj.drawElements === 'function' ) } function parseExtensions (input) { if (typeof input === 'string') { return input.split() } check$1(Array.isArray(input), 'invalid extension array') return input } function getElement (desc) { if (typeof desc === 'string') { check$1(typeof document !== 'undefined', 'not supported outside of DOM') return document.querySelector(desc) } return desc } function parseArgs (args_) { var args = args_ || {} var element, container, canvas, gl var contextAttributes = {} var extensions = [] var optionalExtensions = [] var pixelRatio = (typeof window === 'undefined' ? 1 : window.devicePixelRatio) var profile = false var cachedCode = {} var onDone = function (err) { if (err) { check$1.raise(err) } } var onDestroy = function () {} if (typeof args === 'string') { check$1( typeof document !== 'undefined', 'selector queries only supported in DOM environments') element = document.querySelector(args) check$1(element, 'invalid query string for element') } else if (typeof args === 'object') { if (isHTMLElement(args)) { element = args } else if (isWebGLContext(args)) { gl = args canvas = gl.canvas } else { check$1.constructor(args) if ('gl' in args) { gl = args.gl } else if ('canvas' in args) { canvas = getElement(args.canvas) } else if ('container' in args) { container = getElement(args.container) } if ('attributes' in args) { contextAttributes = args.attributes check$1.type(contextAttributes, 'object', 'invalid context attributes') } if ('extensions' in args) { extensions = parseExtensions(args.extensions) } if ('optionalExtensions' in args) { optionalExtensions = parseExtensions(args.optionalExtensions) } if ('onDone' in args) { check$1.type( args.onDone, 'function', 'invalid or missing onDone callback') onDone = args.onDone } if ('profile' in args) { profile = !!args.profile } if ('pixelRatio' in args) { pixelRatio = +args.pixelRatio check$1(pixelRatio > 0, 'invalid pixel ratio') } if ('cachedCode' in args) { check$1.type( args.cachedCode, 'object', 'invalid cachedCode') cachedCode = args.cachedCode } } } else { check$1.raise('invalid arguments to regl') } if (element) { if (element.nodeName.toLowerCase() === 'canvas') { canvas = element } else { container = element } } if (!gl) { if (!canvas) { check$1( typeof document !== 'undefined', 'must manually specify webgl context outside of DOM environments') var result = createCanvas(container || document.body, onDone, pixelRatio) if (!result) { return null } canvas = result.canvas onDestroy = result.onDestroy } // workaround for chromium bug, premultiplied alpha value is platform dependent if (contextAttributes.premultipliedAlpha === undefined) contextAttributes.premultipliedAlpha = true gl = createContext(canvas, contextAttributes) } if (!gl) { onDestroy() onDone('webgl not supported, try upgrading your browser or graphics drivers http://get.webgl.org') return null } return { gl: gl, canvas: canvas, container: container, extensions: extensions, optionalExtensions: optionalExtensions, pixelRatio: pixelRatio, profile: profile, cachedCode: cachedCode, onDone: onDone, onDestroy: onDestroy } } function createExtensionCache (gl, config) { var extensions = {} function tryLoadExtension (name_) { check$1.type(name_, 'string', 'extension name must be string') var name = name_.toLowerCase() var ext try { ext = extensions[name] = gl.getExtension(name) } catch (e) {} return !!ext } for (var i = 0; i < config.extensions.length; ++i) { var name = config.extensions[i] if (!tryLoadExtension(name)) { config.onDestroy() config.onDone('"' + name + '" extension is not supported by the current WebGL context, try upgrading your system or a different browser') return null } } config.optionalExtensions.forEach(tryLoadExtension) return { extensions: extensions, restore: function () { Object.keys(extensions).forEach(function (name) { if (extensions[name] && !tryLoadExtension(name)) { throw new Error('(regl): error restoring extension ' + name) } }) } } } function loop (n, f) { var result = Array(n) for (var i = 0; i < n; ++i) { result[i] = f(i) } return result } var GL_BYTE$1 = 5120 var GL_UNSIGNED_BYTE$2 = 5121 var GL_SHORT$1 = 5122 var GL_UNSIGNED_SHORT$1 = 5123 var GL_INT$1 = 5124 var GL_UNSIGNED_INT$1 = 5125 var GL_FLOAT$2 = 5126 function nextPow16 (v) { for (var i = 16; i <= (1 << 28); i *= 16) { if (v <= i) { return i } } return 0 } function log2 (v) { var r, shift r = (v > 0xFFFF) << 4 v >>>= r shift = (v > 0xFF) << 3 v >>>= shift; r |= shift shift = (v > 0xF) << 2 v >>>= shift; r |= shift shift = (v > 0x3) << 1 v >>>= shift; r |= shift return r | (v >> 1) } function createPool () { var bufferPool = loop(8, function () { return [] }) function alloc (n) { var sz = nextPow16(n) var bin = bufferPool[log2(sz) >> 2] if (bin.length > 0) { return bin.pop() } return new ArrayBuffer(sz) } function free (buf) { bufferPool[log2(buf.byteLength) >> 2].push(buf) } function allocType (type, n) { var result = null switch (type) { case GL_BYTE$1: result = new Int8Array(alloc(n), 0, n) break case GL_UNSIGNED_BYTE$2: result = new Uint8Array(alloc(n), 0, n) break case GL_SHORT$1: result = new Int16Array(alloc(2 * n), 0, n) break case GL_UNSIGNED_SHORT$1: result = new Uint16Array(alloc(2 * n), 0, n) break case GL_INT$1: result = new Int32Array(alloc(4 * n), 0, n) break case GL_UNSIGNED_INT$1: result = new Uint32Array(alloc(4 * n), 0, n) break case GL_FLOAT$2: result = new Float32Array(alloc(4 * n), 0, n) break default: return null } if (result.length !== n) { return result.subarray(0, n) } return result } function freeType (array) { free(array.buffer) } return { alloc: alloc, free: free, allocType: allocType, freeType: freeType } } var pool = createPool() // zero pool for initial zero data pool.zero = createPool() var GL_SUBPIXEL_BITS = 0x0D50 var GL_RED_BITS = 0x0D52 var GL_GREEN_BITS = 0x0D53 var GL_BLUE_BITS = 0x0D54 var GL_ALPHA_BITS = 0x0D55 var GL_DEPTH_BITS = 0x0D56 var GL_STENCIL_BITS = 0x0D57 var GL_ALIASED_POINT_SIZE_RANGE = 0x846D var GL_ALIASED_LINE_WIDTH_RANGE = 0x846E var GL_MAX_TEXTURE_SIZE = 0x0D33 var GL_MAX_VIEWPORT_DIMS = 0x0D3A var GL_MAX_VERTEX_ATTRIBS = 0x8869 var GL_MAX_VERTEX_UNIFORM_VECTORS = 0x8DFB var GL_MAX_VARYING_VECTORS = 0x8DFC var GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS = 0x8B4D var GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS = 0x8B4C var GL_MAX_TEXTURE_IMAGE_UNITS = 0x8872 var GL_MAX_FRAGMENT_UNIFORM_VECTORS = 0x8DFD var GL_MAX_CUBE_MAP_TEXTURE_SIZE = 0x851C var GL_MAX_RENDERBUFFER_SIZE = 0x84E8 var GL_VENDOR = 0x1F00 var GL_RENDERER = 0x1F01 var GL_VERSION = 0x1F02 var GL_SHADING_LANGUAGE_VERSION = 0x8B8C var GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT = 0x84FF var GL_MAX_COLOR_ATTACHMENTS_WEBGL = 0x8CDF var GL_MAX_DRAW_BUFFERS_WEBGL = 0x8824 var GL_TEXTURE_2D = 0x0DE1 var GL_TEXTURE_CUBE_MAP = 0x8513 var GL_TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515 var GL_TEXTURE0 = 0x84C0 var GL_RGBA = 0x1908 var GL_FLOAT$1 = 0x1406 var GL_UNSIGNED_BYTE$1 = 0x1401 var GL_FRAMEBUFFER = 0x8D40 var GL_FRAMEBUFFER_COMPLETE = 0x8CD5 var GL_COLOR_ATTACHMENT0 = 0x8CE0 var GL_COLOR_BUFFER_BIT$1 = 0x4000 var wrapLimits = function (gl, extensions) { var maxAnisotropic = 1 if (extensions.ext_texture_filter_anisotropic) { maxAnisotropic = gl.getParameter(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT) } var maxDrawbuffers = 1 var maxColorAttachments = 1 if (extensions.webgl_draw_buffers) { maxDrawbuffers = gl.getParameter(GL_MAX_DRAW_BUFFERS_WEBGL) maxColorAttachments = gl.getParameter(GL_MAX_COLOR_ATTACHMENTS_WEBGL) } // detect if reading float textures is available (Safari doesn't support) var readFloat = !!extensions.oes_texture_float if (readFloat) { var readFloatTexture = gl.createTexture() gl.bindTexture(GL_TEXTURE_2D, readFloatTexture) gl.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_FLOAT$1, null) var fbo = gl.createFramebuffer() gl.bindFramebuffer(GL_FRAMEBUFFER, fbo) gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, readFloatTexture, 0) gl.bindTexture(GL_TEXTURE_2D, null) if (gl.checkFramebufferStatus(GL_FRAMEBUFFER) !== GL_FRAMEBUFFER_COMPLETE) readFloat = false else { gl.viewport(0, 0, 1, 1) gl.clearColor(1.0, 0.0, 0.0, 1.0) gl.clear(GL_COLOR_BUFFER_BIT$1) var pixels = pool.allocType(GL_FLOAT$1, 4) gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_FLOAT$1, pixels) if (gl.getError()) readFloat = false else { gl.deleteFramebuffer(fbo) gl.deleteTexture(readFloatTexture) readFloat = pixels[0] === 1.0 } pool.freeType(pixels) } } // detect non power of two cube textures support (IE doesn't support) var isIE = typeof navigator !== 'undefined' && (/MSIE/.test(navigator.userAgent) || /Trident\//.test(navigator.appVersion) || /Edge/.test(navigator.userAgent)) var npotTextureCube = true if (!isIE) { var cubeTexture = gl.createTexture() var data = pool.allocType(GL_UNSIGNED_BYTE$1, 36) gl.activeTexture(GL_TEXTURE0) gl.bindTexture(GL_TEXTURE_CUBE_MAP, cubeTexture) gl.texImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA, 3, 3, 0, GL_RGBA, GL_UNSIGNED_BYTE$1, data) pool.freeType(data) gl.bindTexture(GL_TEXTURE_CUBE_MAP, null) gl.deleteTexture(cubeTexture) npotTextureCube = !gl.getError() } return { // drawing buffer bit depth colorBits: [ gl.getParameter(GL_RED_BITS), gl.getParameter(GL_GREEN_BITS), gl.getParameter(GL_BLUE_BITS), gl.getParameter(GL_ALPHA_BITS) ], depthBits: gl.getParameter(GL_DEPTH_BITS), stencilBits: gl.getParameter(GL_STENCIL_BITS), subpixelBits: gl.getParameter(GL_SUBPIXEL_BITS), // supported extensions extensions: Object.keys(extensions).filter(function (ext) { return !!extensions[ext] }), // max aniso samples maxAnisotropic: maxAnisotropic, // max draw buffers maxDrawbuffers: maxDrawbuffers, maxColorAttachments: maxColorAttachments, // point and line size ranges pointSizeDims: gl.getParameter(GL_ALIASED_POINT_SIZE_RANGE), lineWidthDims: gl.getParameter(GL_ALIASED_LINE_WIDTH_RANGE), maxViewportDims: gl.getParameter(GL_MAX_VIEWPORT_DIMS), maxCombinedTextureUnits: gl.getParameter(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS), maxCubeMapSize: gl.getParameter(GL_MAX_CUBE_MAP_TEXTURE_SIZE), maxRenderbufferSize: gl.getParameter(GL_MAX_RENDERBUFFER_SIZE), maxTextureUnits: gl.getParameter(GL_MAX_TEXTURE_IMAGE_UNITS), maxTextureSize: gl.getParameter(GL_MAX_TEXTURE_SIZE), maxAttributes: gl.getParameter(GL_MAX_VERTEX_ATTRIBS), maxVertexUniforms: gl.getParameter(GL_MAX_VERTEX_UNIFORM_VECTORS), maxVertexTextureUnits: gl.getParameter(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS), maxVaryingVectors: gl.getParameter(GL_MAX_VARYING_VECTORS), maxFragmentUniforms: gl.getParameter(GL_MAX_FRAGMENT_UNIFORM_VECTORS), // vendor info glsl: gl.getParameter(GL_SHADING_LANGUAGE_VERSION), renderer: gl.getParameter(GL_RENDERER), vendor: gl.getParameter(GL_VENDOR), version: gl.getParameter(GL_VERSION), // quirks readFloat: readFloat, npotTextureCube: npotTextureCube } } function isNDArrayLike (obj) { return ( !!obj && typeof obj === 'object' && Array.isArray(obj.shape) && Array.isArray(obj.stride) && typeof obj.offset === 'number' && obj.shape.length === obj.stride.length && (Array.isArray(obj.data) || isTypedArray(obj.data))) } var values = function (obj) { return Object.keys(obj).map(function (key) { return obj[key] }) } var flattenUtils = { shape: arrayShape$1, flatten: flattenArray }; function flatten1D (array, nx, out) { for (var i = 0; i < nx; ++i) { out[i] = array[i] } } function flatten2D (array, nx, ny, out) { var ptr = 0 for (var i = 0; i < nx; ++i) { var row = array[i] for (var j = 0; j < ny; ++j) { out[ptr++] = row[j] } } } function flatten3D (array, nx, ny, nz, out, ptr_) { var ptr = ptr_ for (var i = 0; i < nx; ++i) { var row = array[i] for (var j = 0; j < ny; ++j) { var col = row[j] for (var k = 0; k < nz; ++k) { out[ptr++] = col[k] } } } } function flattenRec (array, shape, level, out, ptr) { var stride = 1 for (var i = level + 1; i < shape.length; ++i) { stride *= shape[i] } var n = shape[level] if (shape.length - level === 4) { var nx = shape[level + 1] var ny = shape[level + 2] var nz = shape[level + 3] for (i = 0; i < n; ++i) { flatten3D(array[i], nx, ny, nz, out, ptr) ptr += stride } } else { for (i = 0; i < n; ++i) { flattenRec(array[i], shape, level + 1, out, ptr) ptr += stride } } } function flattenArray (array, shape, type, out_) { var sz = 1 if (shape.length) { for (var i = 0; i < shape.length; ++i) { sz *= shape[i] } } else { sz = 0 } var out = out_ || pool.allocType(type, sz) switch (shape.length) { case 0: break case 1: flatten1D(array, shape[0], out) break case 2: flatten2D(array, shape[0], shape[1], out) break case 3: flatten3D(array, shape[0], shape[1], shape[2], out, 0) break default: flattenRec(array, shape, 0, out, 0) } return out } function arrayShape$1 (array_) { var shape = [] for (var array = array_; array.length; array = array[0]) { shape.push(array.length) } return shape } var arrayTypes = { "[object Int8Array]": 5120, "[object Int16Array]": 5122, "[object Int32Array]": 5124, "[object Uint8Array]": 5121, "[object Uint8ClampedArray]": 5121, "[object Uint16Array]": 5123, "[object Uint32Array]": 5125, "[object Float32Array]": 5126, "[object Float64Array]": 5121, "[object ArrayBuffer]": 5121 }; var int8 = 5120; var int16 = 5122; var int32 = 5124; var uint8 = 5121; var uint16 = 5123; var uint32 = 5125; var float = 5126; var float32 = 5126; var glTypes = { int8: int8, int16: int16, int32: int32, uint8: uint8, uint16: uint16, uint32: uint32, float: float, float32: float32 }; var dynamic$1 = 35048; var stream = 35040; var usageTypes = { dynamic: dynamic$1, stream: stream, "static": 35044 }; var arrayFlatten = flattenUtils.flatten var arrayShape = flattenUtils.shape var GL_STATIC_DRAW = 0x88E4 var GL_STREAM_DRAW = 0x88E0 var GL_UNSIGNED_BYTE$3 = 5121 var GL_FLOAT$3 = 5126 var DTYPES_SIZES = [] DTYPES_SIZES[5120] = 1 // int8 DTYPES_SIZES[5122] = 2 // int16 DTYPES_SIZES[5124] = 4 // int32 DTYPES_SIZES[5121] = 1 // uint8 DTYPES_SIZES[5123] = 2 // uint16 DTYPES_SIZES[5125] = 4 // uint32 DTYPES_SIZES[5126] = 4 // float32 function typedArrayCode (data) { return arrayTypes[Object.prototype.toString.call(data)] | 0 } function copyArray (out, inp) { for (var i = 0; i < inp.length; ++i) { out[i] = inp[i] } } function transpose ( result, data, shapeX, shapeY, strideX, strideY, offset) { var ptr = 0 for (var i = 0; i < shapeX; ++i) { for (var j = 0; j < shapeY; ++j) { result[ptr++] = data[strideX * i + strideY * j + offset] } } } function wrapBufferState (gl, stats, config, destroyBuffer) { var bufferCount = 0 var bufferSet = {} function REGLBuffer (type) { this.id = bufferCount++ this.buffer = gl.createBuffer() this.type = type this.usage = GL_STATIC_DRAW this.byteLength = 0 this.dimension = 1 this.dtype = GL_UNSIGNED_BYTE$3 this.persistentData = null if (config.profile) { this.stats = { size: 0 } } } REGLBuffer.prototype.bind = function () { gl.bindBuffer(this.type, this.buffer) } REGLBuffer.prototype.destroy = function () { destroy(this) } var streamPool = [] function createStream (type, data) { var buffer = streamPool.pop() if (!buffer) { buffer = new REGLBuffer(type) } buffer.bind() initBufferFromData(buffer, data, GL_STREAM_DRAW, 0, 1, false) return buffer } function destroyStream (stream$$1) { streamPool.push(stream$$1) } function initBufferFromTypedArray (buffer, data, usage) { buffer.byteLength = data.byteLength gl.bufferData(buffer.type, data, usage) } function initBufferFromData (buffer, data, usage, dtype, dimension, persist) { var shape buffer.usage = usage if (Array.isArray(data)) { buffer.dtype = dtype || GL_FLOAT$3 if (data.length > 0) { var flatData if (Array.isArray(data[0])) { shape = arrayShape(data) var dim = 1 for (var i = 1; i < shape.length; ++i) { dim *= shape[i] } buffer.dimension = dim flatData = arrayFlatten(data, shape, buffer.dtype) initBufferFromTypedArray(buffer, flatData, usage) if (persist) { buffer.persistentData = flatData } else { pool.freeType(flatData) } } else if (typeof data[0] === 'number') { buffer.dimension = dimension var typedData = pool.allocType(buffer.dtype, data.length) copyArray(typedData, data) initBufferFromTypedArray(buffer, typedData, usage) if (persist) { buffer.persistentData = typedData } else { pool.freeType(typedData) } } else if (isTypedArray(data[0])) { buffer.dimension = data[0].length buffer.dtype = dtype || typedArrayCode(data[0]) || GL_FLOAT$3 flatData = arrayFlatten( data, [data.length, data[0].length], buffer.dtype) initBufferFromTypedArray(buffer, flatData, usage) if (persist) { buffer.persistentData = flatData } else { pool.freeType(flatData) } } else { check$1.raise('invalid buffer data') } } } else if (isTypedArray(data)) { buffer.dtype = dtype || typedArrayCode(data) buffer.dimension = dimension initBufferFromTypedArray(buffer, data, usage) if (persist) { buffer.persistentData = new Uint8Array(new Uint8Array(data.buffer)) } } else if (isNDArrayLike(data)) { shape = data.shape var stride = data.stride var offset = data.offset var shapeX = 0 var shapeY = 0 var strideX = 0 var strideY = 0 if (shape.length === 1) { shapeX = shape[0] shapeY = 1 strideX = stride[0] strideY = 0 } else if (shape.length === 2) { shapeX = shape[0] shapeY = shape[1] strideX = stride[0] strideY = stride[1] } else { check$1.raise('invalid shape') } buffer.dtype = dtype || typedArrayCode(data.data) || GL_FLOAT$3 buffer.dimension = shapeY var transposeData = pool.allocType(buffer.dtype, shapeX * shapeY) transpose(transposeData, data.data, shapeX, shapeY, strideX, strideY, offset) initBufferFromTypedArray(buffer, transposeData, usage) if (persist) { buffer.persistentData = transposeData } else { pool.freeType(transposeData) } } else if (data instanceof ArrayBuffer) { buffer.dtype = GL_UNSIGNED_BYTE$3 buffer.dimension = dimension initBufferFromTypedArray(buffer, data, usage) if (persist) { buffer.persistentData = new Uint8Array(new Uint8Array(data)) } } else { check$1.raise('invalid buffer data') } } function destroy (buffer) { stats.bufferCount-- // remove attribute link destroyBuffer(buffer) var handle = buffer.buffer check$1(handle, 'buffer must not be deleted already') gl.deleteBuffer(handle) buffer.buffer = null delete bufferSet[buffer.id] } function createBuffer (options, type, deferInit, persistent) { stats.bufferCount++ var buffer = new REGLBuffer(type) bufferSet[buffer.id] = buffer function reglBuffer (options) { var usage = GL_STATIC_DRAW var data = null var byteLength = 0 var dtype = 0 var dimension = 1 if (Array.isArray(options) || isTypedArray(options) || isNDArrayLike(options) || options instanceof ArrayBuffer) { data = options } else if (typeof options === 'number') { byteLength = options | 0 } else if (options) { check$1.type( options, 'object', 'buffer arguments must be an object, a number or an array') if ('data' in options) { check$1( data === null || Array.isArray(data) || isTypedArray(data) || isNDArrayLike(data), 'invalid data for buffer') data = options.data } if ('usage' in options) { check$1.parameter(options.usage, usageTypes, 'invalid buffer usage') usage = usageTypes[options.usage] } if ('type' in options) { check$1.parameter(options.type, glTypes, 'invalid buffer type') dtype = glTypes[options.type] } if ('dimension' in options) { check$1.type(options.dimension, 'number', 'invalid dimension') dimension = options.dimension | 0 } if ('length' in options) { check$1.nni(byteLength, 'buffer length must be a nonnegative integer') byteLength = options.length | 0 } } buffer.bind() if (!data) { // #475 if (byteLength) gl.bufferData(buffer.type, byteLength, usage) buffer.dtype = dtype || GL_UNSIGNED_BYTE$3 buffer.usage = usage buffer.dimension = dimension buffer.byteLength = byteLength } else { initBufferFromData(buffer, data, usage, dtype, dimension, persistent) } if (config.profile) { buffer.stats.size = buffer.byteLength * DTYPES_SIZES[buffer.dtype] } return reglBuffer } function setSubData (data, offset) { check$1(offset + data.byteLength <= buffer.byteLength, 'invalid buffer subdata call, buffer is too small. ' + ' Can\'t write data of size ' + data.byteLength + ' starting from offset ' + offset + ' to a buffer of size ' + buffer.byteLength) gl.bufferSubData(buffer.type, offset, data) } function subdata (data, offset_) { var offset = (offset_ || 0) | 0 var shape buffer.bind() if (isTypedArray(data) || data instanceof ArrayBuffer) { setSubData(data, offset) } else if (Array.isArray(data)) { if (data.length > 0) { if (typeof data[0] === 'number') { var converted = pool.allocType(buffer.dtype, data.length) copyArray(converted, data) setSubData(converted, offset) pool.freeType(converted) } else if (Array.isArray(data[0]) || isTypedArray(data[0])) { shape = arrayShape(data) var flatData = arrayFlatten(data, shape, buffer.dtype) setSubData(flatData, offset) pool.freeType(flatData) } else { check$1.raise('invalid buffer data') } } } else if (isNDArrayLike(data)) { shape = data.shape var stride = data.stride var shapeX = 0 var shapeY = 0 var strideX = 0 var strideY = 0 if (shape.length === 1) { shapeX = shape[0] shapeY = 1 strideX = stride[0] strideY = 0 } else if (shape.length === 2) { shapeX = shape[0] shapeY = shape[1] strideX = stride[0] strideY = stride[1] } else { check$1.raise('invalid shape') } var dtype = Array.isArray(data.data) ? buffer.dtype : typedArrayCode(data.data) var transposeData = pool.allocType(dtype, shapeX * shapeY) transpose(transposeData, data.data, shapeX, shapeY, strideX, strideY, data.offset) setSubData(transposeData, offset) pool.freeType(transposeData) } else { check$1.raise('invalid data for buffer subdata') } return reglBuffer } if (!deferInit) { reglBuffer(options) } reglBuffer._reglType = 'buffer' reglBuffer._buffer = buffer reglBuffer.subdata = subdata if (config.profile) { reglBuffer.stats = buffer.stats } reglBuffer.destroy = function () { destroy(buffer) } return reglBuffer } function restoreBuffers () { values(bufferSet).forEach(function (buffer) { buffer.buffer = gl.createBuffer() gl.bindBuffer(buffer.type, buffer.buffer) gl.bufferData( buffer.type, buffer.persistentData || buffer.byteLength, buffer.usage) }) } if (config.profile) { stats.getTotalBufferSize = function () { var total = 0 // TODO: Right now, the streams are not part of the total count. Object.keys(bufferSet).forEach(function (key) { total += bufferSet[key].stats.size }) return total } } return { create: createBuffer, createStream: crea