(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 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 } 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 } } 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() } return input } function getElement (desc) { if (typeof desc === 'string') { 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 onDone = function (err) { if (err) { } } var onDestroy = function () {} if (typeof args === 'string') { element = document.querySelector(args) } else if (typeof args === 'object') { if (isHTMLElement(args)) { element = args } else if (isWebGLContext(args)) { gl = args canvas = gl.canvas } else { 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 } if ('extensions' in args) { extensions = parseExtensions(args.extensions) } if ('optionalExtensions' in args) { optionalExtensions = parseExtensions(args.optionalExtensions) } if ('onDone' in args) { onDone = args.onDone } if ('profile' in args) { profile = !!args.profile } if ('pixelRatio' in args) { pixelRatio = +args.pixelRatio } } } else { } if (element) { if (element.nodeName.toLowerCase() === 'canvas') { canvas = element } else { container = element } } if (!gl) { if (!canvas) { 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, onDone: onDone, onDestroy: onDestroy } } function createExtensionCache (gl, config) { var extensions = {} function tryLoadExtension (name_) { 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 = 5120 var GL_UNSIGNED_BYTE$1 = 5121 var GL_SHORT = 5122 var GL_UNSIGNED_SHORT = 5123 var GL_INT = 5124 var GL_UNSIGNED_INT = 5125 var GL_FLOAT$1 = 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: result = new Int8Array(alloc(n), 0, n) break case GL_UNSIGNED_BYTE$1: result = new Uint8Array(alloc(n), 0, n) break case GL_SHORT: result = new Int16Array(alloc(2 * n), 0, n) break case GL_UNSIGNED_SHORT: result = new Uint16Array(alloc(2 * n), 0, n) break case GL_INT: result = new Int32Array(alloc(4 * n), 0, n) break case GL_UNSIGNED_INT: result = new Uint32Array(alloc(4 * n), 0, n) break case GL_FLOAT$1: 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 = 0x1406 var GL_UNSIGNED_BYTE = 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, 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, 4) gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_FLOAT, 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, 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, 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 } } 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 ) } 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$2 = 5121 var GL_FLOAT$2 = 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$2 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$2 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$2 flatData = arrayFlatten( data, [data.length, data[0].length], buffer.dtype) initBufferFromTypedArray(buffer, flatData, usage) if (persist) { buffer.persistentData = flatData } else { pool.freeType(flatData) } } else { } } } 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 { } buffer.dtype = dtype || typedArrayCode(data.data) || GL_FLOAT$2 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$2 buffer.dimension = dimension initBufferFromTypedArray(buffer, data, usage) if (persist) { buffer.persistentData = new Uint8Array(new Uint8Array(data)) } } else { } } function destroy (buffer) { stats.bufferCount-- // remove attribute link destroyBuffer(buffer) var handle = buffer.buffer 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) { if ('data' in options) { data = options.data } if ('usage' in options) { usage = usageTypes[options.usage] } if ('type' in options) { dtype = glTypes[options.type] } if ('dimension' in options) { dimension = options.dimension | 0 } if ('length' in options) { byteLength = options.length | 0 } } buffer.bind() if (!data) { // #475 if (byteLength) gl.bufferData(buffer.type, byteLength, usage) buffer.dtype = dtype || GL_UNSIGNED_BYTE$2 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) { 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 { } } } 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 { } 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 { } 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: createStream, destroyStream: destroyStream, clear: function () { values(bufferSet).forEach(destroy) streamPool.forEach(destroy) }, getBuffer: function (wrapper) { if (wrapper && wrapper._buffer instanceof REGLBuffer) { return wrapper._buffer } return null }, restore: restoreBuffers, _initBuffer: initBufferFromData } } var points = 0; var point = 0; var lines = 1; var line = 1; var triangles = 4; var triangle = 4; var primTypes = { points: points, point: point, lines: lines, line: line, triangles: triangles, triangle: triangle, "line loop": 2, "line strip": 3, "triangle strip": 5, "triangle fan": 6 }; var GL_POINTS = 0 var GL_LINES = 1 var GL_TRIANGLES = 4 var GL_BYTE$1 = 5120 var GL_UNSIGNED_BYTE$3 = 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_ELEMENT_ARRAY_BUFFER = 34963 var GL_STREAM_DRAW$1 = 0x88E0 var GL_STATIC_DRAW$1 = 0x88E4 function wrapElementsState (gl, extensions, bufferState, stats) { var elementSet = {} var elementCount = 0 var elementTypes = { 'uint8': GL_UNSIGNED_BYTE$3, 'uint16': GL_UNSIGNED_SHORT$1 } if (extensions.oes_element_index_uint) { elementTypes.uint32 = GL_UNSIGNED_INT$1 } function REGLElementBuffer (buffer) { this.id = elementCount++ elementSet[this.id] = this this.buffer = buffer this.primType = GL_TRIANGLES this.vertCount = 0 this.type = 0 } REGLElementBuffer.prototype.bind = function () { this.buffer.bind() } var bufferPool = [] function createElementStream (data) { var result = bufferPool.pop() if (!result) { result = new REGLElementBuffer(bufferState.create( null, GL_ELEMENT_ARRAY_BUFFER, true, false)._buffer) } initElements(result, data, GL_STREAM_DRAW$1, -1, -1, 0, 0) return result } function destroyElementStream (elements) { bufferPool.push(elements) } function initElements ( elements, data, usage, prim, count, byteLength, type) { elements.buffer.bind() var dtype if (data) { var predictedType = type if (!type && ( !isTypedArray(data) || (isNDArrayLike(data) && !isTypedArray(data.data)))) { predictedType = extensions.oes_element_index_uint ? GL_UNSIGNED_INT$1 : GL_UNSIGNED_SHORT$1 } bufferState._initBuffer( elements.buffer, data, usage, predictedType, 3) } else { gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, byteLength, usage) elements.buffer.dtype = dtype || GL_UNSIGNED_BYTE$3 elements.buffer.usage = usage elements.buffer.dimension = 3 elements.buffer.byteLength = byteLength } dtype = type if (!type) { switch (elements.buffer.dtype) { case GL_UNSIGNED_BYTE$3: case GL_BYTE$1: dtype = GL_UNSIGNED_BYTE$3 break case GL_UNSIGNED_SHORT$1: case GL_SHORT$1: dtype = GL_UNSIGNED_SHORT$1 break case GL_UNSIGNED_INT$1: case GL_INT$1: dtype = GL_UNSIGNED_INT$1 break default: } elements.buffer.dtype = dtype } elements.type = dtype // Check oes_element_index_uint extension // try to guess default primitive type and arguments var vertCount = count if (vertCount < 0) { vertCount = elements.buffer.byteLength if (dtype === GL_UNSIGNED_SHORT$1) { vertCount >>= 1 } else if (dtype === GL_UNSIGNED_INT$1) { vertCount >>= 2 } } elements.vertCount = vertCount // try to guess primitive type from cell dimension var primType = prim if (prim < 0) { primType = GL_TRIANGLES var dimension = elements.buffer.dimension if (dimension === 1) primType = GL_POINTS if (dimension === 2) primType = GL_LINES if (dimension === 3) primType = GL_TRIANGLES } elements.primType = primType } function destroyElements (elements) { stats.elementsCount-- delete elementSet[elements.id] elements.buffer.destroy() elements.buffer = null } function createElements (options, persistent) { var buffer = bufferState.create(null, GL_ELEMENT_ARRAY_BUFFER, true) var elements = new REGLElementBuffer(buffer._buffer) stats.elementsCount++ function reglElements (options) { if (!options) { buffer() elements.primType = GL_TRIANGLES elements.vertCount = 0 elements.type = GL_UNSIGNED_BYTE$3 } else if (typeof options === 'number') { buffer(options) elements.primType = GL_TRIANGLES elements.vertCount = options | 0 elements.type = GL_UNSIGNED_BYTE$3 } else { var data = null var usage = GL_STATIC_DRAW$1 var primType = -1 var vertCount = -1 var byteLength = 0 var dtype = 0 if (Array.isArray(options) || isTypedArray(options) || isNDArrayLike(options)) { data = options } else { if ('data' in options) { data = options.data } if ('usage' in options) { usage = usageTypes[options.usage] } if ('primitive' in options) { primType = primTypes[options.primitive] } if ('count' in options) { vertCount = options.count | 0 } if ('type' in options) { dtype = elementTypes[options.type] } if ('length' in options) { byteLength = options.length | 0 } else { byteLength = vertCount if (dtype === GL_UNSIGNED_SHORT$1 || dtype === GL_SHORT$1) { byteLength *= 2 } else if (dtype === GL_UNSIGNED_INT$1 || dtype === GL_INT$1) { byteLength *= 4 } } } initElements( elements, data, usage, primType, vertCount, byteLength, dtype) } return reglElements } reglElements(options) reglElements._reglType = 'elements' reglElements._elements = elements reglElements.subdata = function (data, offset) { buffer.subdata(data, offset) return reglElements } reglElements.destroy = function () { destroyElements(elements) } return reglElements } return { create: createElements, createStream: createElementStream, destroyStream: destroyElementStream, getElements: function (elements) { if (typeof elements === 'function' && elements._elements instanceof REGLElementBuffer) { return elements._elements } return null }, clear: function () { values(elementSet).forEach(destroyElements) } } } var FLOAT = new Float32Array(1) var INT = new Uint32Array(FLOAT.buffer) var GL_UNSIGNED_SHORT$3 = 5123 function convertToHalfFloat (array) { var ushorts = pool.allocType(GL_UNSIGNED_SHORT$3, array.length) for (var i = 0; i < array.length; ++i) { if (isNaN(array[i])) { ushorts[i] = 0xffff } else if (array[i] === Infinity) { ushorts[i] = 0x7c00 } else if (array[i] === -Infinity) { ushorts[i] = 0xfc00 } else { FLOAT[0] = array[i] var x = INT[0] var sgn = (x >>> 31) << 15 var exp = ((x << 1) >>> 24) - 127 var frac = (x >> 13) & ((1 << 10) - 1) if (exp < -24) { // round non-representable denormals to 0 ushorts[i] = sgn } else if (exp < -14) { // handle denormals var s = -14 - exp ushorts[i] = sgn + ((frac + (1 << 10)) >> s) } else if (exp > 15) { // round overflow to +/- Infinity ushorts[i] = sgn + 0x7c00 } else { // otherwise convert directly ushorts[i] = sgn + ((exp + 15) << 10) + frac } } } return ushorts } function isArrayLike (s) { return Array.isArray(s) || isTypedArray(s) } var GL_COMPRESSED_TEXTURE_FORMATS = 0x86A3 var GL_TEXTURE_2D$1 = 0x0DE1 var GL_TEXTURE_CUBE_MAP$1 = 0x8513 var GL_TEXTURE_CUBE_MAP_POSITIVE_X$1 = 0x8515 var GL_RGBA$1 = 0x1908 var GL_ALPHA = 0x1906 var GL_RGB = 0x1907 var GL_LUMINANCE = 0x1909 var GL_LUMINANCE_ALPHA = 0x190A var GL_RGBA4 = 0x8056 var GL_RGB5_A1 = 0x8057 var GL_RGB565 = 0x8D62 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_DEPTH_COMPONENT = 0x1902 var GL_DEPTH_STENCIL = 0x84F9 var GL_SRGB_EXT = 0x8C40 var GL_SRGB_ALPHA_EXT = 0x8C42 var GL_HALF_FLOAT_OES = 0x8D61 var GL_COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0 var GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1 var GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2 var GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3 var GL_COMPRESSED_RGB_ATC_WEBGL = 0x8C92 var GL_COMPRESSED_RGBA_ATC_EXPLICIT_ALPHA_WEBGL = 0x8C93 var GL_COMPRESSED_RGBA_ATC_INTERPOLATED_ALPHA_WEBGL = 0x87EE var GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00 var GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG = 0x8C01 var GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02 var GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG = 0x8C03 var GL_COMPRESSED_RGB_ETC1_WEBGL = 0x8D64 var GL_UNSIGNED_BYTE$4 = 0x1401 var GL_UNSIGNED_SHORT$2 = 0x1403 var GL_UNSIGNED_INT$2 = 0x1405 var GL_FLOAT$3 = 0x1406 var GL_TEXTURE_WRAP_S = 0x2802 var GL_TEXTURE_WRAP_T = 0x2803 var GL_REPEAT = 0x2901 var GL_CLAMP_TO_EDGE = 0x812F var GL_MIRRORED_REPEAT = 0x8370 var GL_TEXTURE_MAG_FILTER = 0x2800 var GL_TEXTURE_MIN_FILTER = 0x2801 var GL_NEAREST = 0x2600 var GL_LINEAR = 0x2601 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_GENERATE_MIPMAP_HINT = 0x8192 var GL_DONT_CARE = 0x1100 var GL_FASTEST = 0x1101 var GL_NICEST = 0x1102 var GL_TEXTURE_MAX_ANISOTROPY_EXT = 0x84FE var GL_UNPACK_ALIGNMENT = 0x0CF5 var GL_UNPACK_FLIP_Y_WEBGL = 0x9240 var GL_UNPACK_PREMULTIPLY_ALPHA_WEBGL = 0x9241 var GL_UNPACK_COLORSPACE_CONVERSION_WEBGL = 0x9243 var GL_BROWSER_DEFAULT_WEBGL = 0x9244 var GL_TEXTURE0$1 = 0x84C0 var MIPMAP_FILTERS = [ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_LINEAR ] var CHANNELS_FORMAT = [ 0, GL_LUMINANCE, GL_LUMINANCE_ALPHA, GL_RGB, GL_RGBA$1 ] var FORMAT_CHANNELS = {} FORMAT_CHANNELS[GL_LUMINANCE] = FORMAT_CHANNELS[GL_ALPHA] = FORMAT_CHANNELS[GL_DEPTH_COMPONENT] = 1 FORMAT_CHANNELS[GL_DEPTH_STENCIL] = FORMAT_CHANNELS[GL_LUMINANCE_ALPHA] = 2 FORMAT_CHANNELS[GL_RGB] = FORMAT_CHANNELS[GL_SRGB_EXT] = 3 FORMAT_CHANNELS[GL_RGBA$1] = FORMAT_CHANNELS[GL_SRGB_ALPHA_EXT] = 4 function objectName (str) { return '[object ' + str + ']' } var CANVAS_CLASS = objectName('HTMLCanvasElement') var OFFSCREENCANVAS_CLASS = objectName('OffscreenCanvas') var CONTEXT2D_CLASS = objectName('CanvasRenderingContext2D') var BITMAP_CLASS = objectName('ImageBitmap') var IMAGE_CLASS = objectName('HTMLImageElement') var VIDEO_CLASS = objectName('HTMLVideoElement') var PIXEL_CLASSES = Object.keys(arrayTypes).concat([ CANVAS_CLASS, OFFSCREENCANVAS_CLASS, CONTEXT2D_CLASS, BITMAP_CLASS, IMAGE_CLASS, VIDEO_CLASS ]) // for every texture type, store // the size in bytes. var TYPE_SIZES = [] TYPE_SIZES[GL_UNSIGNED_BYTE$4] = 1 TYPE_SIZES[GL_FLOAT$3] = 4 TYPE_SIZES[GL_HALF_FLOAT_OES] = 2 TYPE_SIZES[GL_UNSIGNED_SHORT$2] = 2 TYPE_SIZES[GL_UNSIGNED_INT$2] = 4 var FORMAT_SIZES_SPECIAL = [] FORMAT_SIZES_SPECIAL[GL_RGBA4] = 2 FORMAT_SIZES_SPECIAL[GL_RGB5_A1] = 2 FORMAT_SIZES_SPECIAL[GL_RGB565] = 2 FORMAT_SIZES_SPECIAL[GL_DEPTH_STENCIL] = 4 FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGB_S3TC_DXT1_EXT] = 0.5 FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGBA_S3TC_DXT1_EXT] = 0.5 FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGBA_S3TC_DXT3_EXT] = 1 FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGBA_S3TC_DXT5_EXT] = 1 FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGB_ATC_WEBGL] = 0.5 FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGBA_ATC_EXPLICIT_ALPHA_WEBGL] = 1 FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGBA_ATC_INTERPOLATED_ALPHA_WEBGL] = 1 FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG] = 0.5 FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG] = 0.25 FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG] = 0.5 FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG] = 0.25 FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGB_ETC1_WEBGL] = 0.5 function isNumericArray (arr) { return ( Array.isArray(arr) && (arr.length === 0 || typeof arr[0] === 'number')) } function isRectArray (arr) { if (!Array.isArray(arr)) { return false } var width = arr.length if (width === 0 || !isArrayLike(arr[0])) { return false } return true } function classString (x) { return Object.prototype.toString.call(x) } function isCanvasElement (object) { return classString(object) === CANVAS_CLASS } function isOffscreenCanvas (object) { return classString(object) === OFFSCREENCANVAS_CLASS } function isContext2D (object) { return classString(object) === CONTEXT2D_CLASS } function isBitmap (object) { return classString(object) === BITMAP_CLASS } function isImageElement (object) { return classString(object) === IMAGE_CLASS } function isVideoElement (object) { return classString(object) === VIDEO_CLASS } function isPixelData (object) { if (!object) { return false } var className = classString(object) if (PIXEL_CLASSES.indexOf(className) >= 0) { return true } return ( isNumericArray(object) || isRectArray(object) || isNDArrayLike(object)) } function typedArrayCode$1 (data) { return arrayTypes[Object.prototype.toString.call(data)] | 0 } function convertData (result, data) { var n = data.length switch (result.type) { case GL_UNSIGNED_BYTE$4: case GL_UNSIGNED_SHORT$2: case GL_UNSIGNED_INT$2: case GL_FLOAT$3: var converted = pool.allocType(result.type, n) converted.set(data) result.data = converted break case GL_HALF_FLOAT_OES: result.data = convertToHalfFloat(data) break default: } } function preConvert (image, n) { return pool.allocType( image.type === GL_HALF_FLOAT_OES ? GL_FLOAT$3 : image.type, n) } function postConvert (image, data) { if (image.type === GL_HALF_FLOAT_OES) { image.data = convertToHalfFloat(data) pool.freeType(data) } else { image.data = data } } function transposeData (image, array, strideX, strideY, strideC, offset) { var w = image.width var h = image.height var c = image.channels var n = w * h * c var data = preConvert(image, n) var p = 0 for (var i = 0; i < h; ++i) { for (var j = 0; j < w; ++j) { for (var k = 0; k < c; ++k) { data[p++] = array[strideX * j + strideY * i + strideC * k + offset] } } } postConvert(image, data) } function getTextureSize (format, type, width, height, isMipmap, isCube) { var s if (typeof FORMAT_SIZES_SPECIAL[format] !== 'undefined') { // we have a special array for dealing with weird color formats such as RGB5A1 s = FORMAT_SIZES_SPECIAL[format] } else { s = FORMAT_CHANNELS[format] * TYPE_SIZES[type] } if (isCube) { s *= 6 } if (isMipmap) { // compute the total size of all the mipmaps. var total = 0 var w = width while (w >= 1) { // we can only use mipmaps on a square image, // so we can simply use the width and ignore the height: total += s * w * w w /= 2 } return total } else { return s * width * height } } function createTextureSet ( gl, extensions, limits, reglPoll, contextState, stats, config) { // ------------------------------------------------------- // Initialize constants and parameter tables here // ------------------------------------------------------- var mipmapHint = { "don't care": GL_DONT_CARE, 'dont care': GL_DONT_CARE, 'nice': GL_NICEST, 'fast': GL_FASTEST } var wrapModes = { 'repeat': GL_REPEAT, 'clamp': GL_CLAMP_TO_EDGE, 'mirror': GL_MIRRORED_REPEAT } var magFilters = { 'nearest': GL_NEAREST, 'linear': GL_LINEAR } var minFilters = extend({ 'mipmap': GL_LINEAR_MIPMAP_LINEAR, 'nearest mipmap nearest': GL_NEAREST_MIPMAP_NEAREST, 'linear mipmap nearest': GL_LINEAR_MIPMAP_NEAREST, 'nearest mipmap linear': GL_NEAREST_MIPMAP_LINEAR, 'linear mipmap linear': GL_LINEAR_MIPMAP_LINEAR }, magFilters) var colorSpace = { 'none': 0, 'browser': GL_BROWSER_DEFAULT_WEBGL } var textureTypes = { 'uint8': GL_UNSIGNED_BYTE$4, 'rgba4': GL_UNSIGNED_SHORT_4_4_4_4, 'rgb565': GL_UNSIGNED_SHORT_5_6_5, 'rgb5 a1': GL_UNSIGNED_SHORT_5_5_5_1 } var textureFormats = { 'alpha': GL_ALPHA, 'luminance': GL_LUMINANCE, 'luminance alpha': GL_LUMINANCE_ALPHA, 'rgb': GL_RGB, 'rgba': GL_RGBA$1, 'rgba4': GL_RGBA4, 'rgb5 a1': GL_RGB5_A1, 'rgb565': GL_RGB565 } var compressedTextureFormats = {} if (extensions.ext_srgb) { textureFormats.srgb = GL_SRGB_EXT textureFormats.srgba = GL_SRGB_ALPHA_EXT } if (extensions.oes_texture_float) { textureTypes.float32 = textureTypes.float = GL_FLOAT$3 } if (extensions.oes_texture_half_float) { textureTypes['float16'] = textureTypes['half float'] = GL_HALF_FLOAT_OES } if (extensions.webgl_depth_texture) { extend(textureFormats, { 'depth': GL_DEPTH_COMPONENT, 'depth stencil': GL_DEPTH_STENCIL }) extend(textureTypes, { 'uint16': GL_UNSIGNED_SHORT$2, 'uint32': GL_UNSIGNED_INT$2, 'depth stencil': GL_UNSIGNED_INT_24_8_WEBGL }) } if (extensions.webgl_compressed_texture_s3tc) { extend(compressedTextureFormats, { 'rgb s3tc dxt1': GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 'rgba s3tc dxt1': GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, 'rgba s3tc dxt3': GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, 'rgba s3tc dxt5': GL_COMPRESSED_RGBA_S3TC_DXT5_EXT }) } if (extensions.webgl_compressed_texture_atc) { extend(compressedTextureFormats, { 'rgb atc': GL_COMPRESSED_RGB_ATC_WEBGL, 'rgba atc explicit alpha': GL_COMPRESSED_RGBA_ATC_EXPLICIT_ALPHA_WEBGL, 'rgba atc interpolated alpha': GL_COMPRESSED_RGBA_ATC_INTERPOLATED_ALPHA_WEBGL }) } if (extensions.webgl_compressed_texture_pvrtc) { extend(compressedTextureFormats, { 'rgb pvrtc 4bppv1': GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG, 'rgb pvrtc 2bppv1': GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG, 'rgba pvrtc 4bppv1': GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG, 'rgba pvrtc 2bppv1': GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG }) } if (extensions.webgl_compressed_texture_etc1) { compressedTextureFormats['rgb etc1'] = GL_COMPRESSED_RGB_ETC1_WEBGL } // Copy over all texture formats var supportedCompressedFormats = Array.prototype.slice.call( gl.getParameter(GL_COMPRESSED_TEXTURE_FORMATS)) Object.keys(compressedTextureFormats).forEach(function (name) { var format = compressedTextureFormats[name] if (supportedCompressedFormats.indexOf(format) >= 0) { textureFormats[name] = format } }) var supportedFormats = Object.keys(textureFormats) limits.textureFormats = supportedFormats // associate with every format string its // corresponding GL-value. var textureFormatsInvert = [] Object.keys(textureFormats).forEach(function (key) { var val = textureFormats[key] textureFormatsInvert[val] = key }) // associate with every type string its // corresponding GL-value. var textureTypesInvert = [] Object.keys(textureTypes).forEach(function (key) { var val = textureTypes[key] textureTypesInvert[val] = key }) var magFiltersInvert = [] Object.keys(magFilters).forEach(function (key) { var val = magFilters[key] magFiltersInvert[val] = key }) var minFiltersInvert = [] Object.keys(minFilters).forEach(function (key) { var val = minFilters[key] minFiltersInvert[val] = key }) var wrapModesInvert = [] Object.keys(wrapModes).forEach(function (key) { var val = wrapModes[key] wrapModesInvert[val] = key }) // colorFormats[] gives the format (channels) associated to an // internalformat var colorFormats = supportedFormats.reduce(function (color, key) { var glenum = textureFormats[key] if (glenum === GL_LUMINANCE || glenum === GL_ALPHA || glenum === GL_LUMINANCE || glenum === GL_LUMINANCE_ALPHA || glenum === GL_DEPTH_COMPONENT || glenum === GL_DEPTH_STENCIL || (extensions.ext_srgb && (glenum === GL_SRGB_EXT || glenum === GL_SRGB_ALPHA_EXT))) { color[glenum] = glenum } else if (glenum === GL_RGB5_A1 || key.indexOf('rgba') >= 0) { color[glenum] = GL_RGBA$1 } else { color[glenum] = GL_RGB } return color }, {}) function TexFlags () { // format info this.internalformat = GL_RGBA$1 this.format = GL_RGBA$1 this.type = GL_UNSIGNED_BYTE$4 this.compressed = false // pixel storage this.premultiplyAlpha = false this.flipY = false this.unpackAlignment = 1 this.colorSpace = GL_BROWSER_DEFAULT_WEBGL // shape info this.width = 0 this.height = 0 this.channels = 0 } function copyFlags (result, other) { result.internalformat = other.internalformat result.format = other.format result.type = other.type result.compressed = other.compressed result.premultiplyAlpha = other.premultiplyAlpha result.flipY = other.flipY result.unpackAlignment = other.unpackAlignment result.colorSpace = other.colorSpace result.width = other.width result.height = other.height result.channels = other.channels } function parseFlags (flags, options) { if (typeof options !== 'object' || !options) { return } if ('premultiplyAlpha' in options) { flags.premultiplyAlpha = options.premultiplyAlpha } if ('flipY' in options) { flags.flipY = options.flipY } if ('alignment' in options) { flags.unpackAlignment = options.alignment } if ('colorSpace' in options) { flags.colorSpace = colorSpace[options.colorSpace] } if ('type' in options) { var type = options.type flags.type = textureTypes[type] } var w = flags.width var h = flags.height var c = flags.channels var hasChannels = false if ('shape' in options) { w = options.shape[0] h = options.shape[1] if (options.shape.length === 3) { c = options.shape[2] hasChannels = true } } else { if ('radius' in options) { w = h = options.radius } if ('width' in options) { w = options.width } if ('height' in options) { h = options.height } if ('channels' in options) { c = options.channels hasChannels = true } } flags.width = w | 0 flags.height = h | 0 flags.channels = c | 0 var hasFormat = false if ('format' in options) { var formatStr = options.format var internalformat = flags.internalformat = textureFormats[formatStr] flags.format = colorFormats[internalformat] if (formatStr in textureTypes) { if (!('type' in options)) { flags.type = textureTypes[formatStr] } } if (formatStr in compressedTextureFormats) { flags.compressed = true } hasFormat = true } // Reconcile channels and format if (!hasChannels && hasFormat) { flags.channels = FORMAT_CHANNELS[flags.format] } else if (hasChannels && !hasFormat) { if (flags.channels !== CHANNELS_FORMAT[flags.format]) { flags.format = flags.internalformat = CHANNELS_FORMAT[flags.channels] } } else if (hasFormat && hasChannels) { } } function setFlags (flags) { gl.pixelStorei(GL_UNPACK_FLIP_Y_WEBGL, flags.flipY) gl.pixelStorei(GL_UNPACK_PREMULTIPLY_ALPHA_WEBGL, flags.premultiplyAlpha) gl.pixelStorei(GL_UNPACK_COLORSPACE_CONVERSION_WEBGL, flags.colorSpace) gl.pixelStorei(GL_UNPACK_ALIGNMENT, flags.unpackAlignment) } // ------------------------------------------------------- // Tex image data // ------------------------------------------------------- function TexImage () { TexFlags.call(this) this.xOffset = 0 this.yOffset = 0 // data this.data = null this.needsFree = false // html element this.element = null // copyTexImage info this.needsCopy = false } function parseImage (image, options) { var data = null if (isPixelData(options)) { data = options } else if (options) { parseFlags(image, options) if ('x' in options) { image.xOffset = options.x | 0 } if ('y' in options) { image.yOffset = options.y | 0 } if (isPixelData(options.data)) { data = options.data } } if (options.copy) { var viewW = contextState.viewportWidth var viewH = contextState.viewportHeight image.width = image.width || (viewW - image.xOffset) image.height = image.height || (viewH - image.yOffset) image.needsCopy = true } else if (!data) { image.width = image.width || 1 image.height = image.height || 1 image.channels = image.channels || 4 } else if (isTypedArray(data)) { image.channels = image.channels || 4 image.data = data if (!('type' in options) && image.type === GL_UNSIGNED_BYTE$4) { image.type = typedArrayCode$1(data) } } else if (isNumericArray(data)) { image.channels = image.channels || 4 convertData(image, data) image.alignment = 1 image.needsFree = true } else if (isNDArrayLike(data)) { var array = data.data if (!Array.isArray(array) && image.type === GL_UNSIGNED_BYTE$4) { image.type = typedArrayCode$1(array) } var shape = data.shape var stride = data.stride var shapeX, shapeY, shapeC, strideX, strideY, strideC if (shape.length === 3) { shapeC = shape[2] strideC = stride[2] } else { shapeC = 1 strideC = 1 } shapeX = shape[0] shapeY = shape[1] strideX = stride[0] strideY = stride[1] image.alignment = 1 image.width = shapeX image.height = shapeY image.channels = shapeC image.format = image.internalformat = CHANNELS_FORMAT[shapeC] image.needsFree = true transposeData(image, array, strideX, strideY, strideC, data.offset) } else if (isCanvasElement(data) || isOffscreenCanvas(data) || isContext2D(data)) { if (isCanvasElement(data) || isOffscreenCanvas(data)) { image.element = data } else { image.element = data.canvas } image.width = image.element.width image.height = image.element.height image.channels = 4 } else if (isBitmap(data)) { image.element = data image.width = data.width image.height = data.height image.channels = 4 } else if (isImageElement(data)) { image.element = data image.width = data.naturalWidth image.height = data.naturalHeight image.channels = 4 } else if (isVideoElement(data)) { image.element = data image.width = data.videoWidth image.height = data.videoHeight image.channels = 4 } else if (isRectArray(data)) { var w = image.width || data[0].length var h = image.height || data.length var c = image.channels if (isArrayLike(data[0][0])) { c = c || data[0][0].length } else { c = c || 1 } var arrayShape = flattenUtils.shape(data) var n = 1 for (var dd = 0; dd < arrayShape.length; ++dd) { n *= arrayShape[dd] } var allocData = preConvert(image, n) flattenUtils.flatten(data, arrayShape, '', allocData) postConvert(image, allocData) image.alignment = 1 image.width = w image.height = h image.channels = c image.format = image.internalformat = CHANNELS_FORMAT[c] image.needsFree = true } if (image.type === GL_FLOAT$3) { } else if (image.type === GL_HALF_FLOAT_OES) { } // do compressed texture validation here. } function setImage (info, target, miplevel) { var element = info.element var data = info.data var internalformat = info.internalformat var format = info.format var type = info.type var width = info.width var height = info.height setFlags(info) if (element) { gl.texImage2D(target, miplevel, format, format, type, element) } else if (info.compressed) { gl.compressedTexImage2D(target, miplevel, internalformat, width, height, 0, data) } else if (info.needsCopy) { reglPoll() gl.copyTexImage2D( target, miplevel, format, info.xOffset, info.yOffset, width, height, 0) } else { gl.texImage2D(target, miplevel, format, width, height, 0, format, type, data || null) } } function setSubImage (info, target, x, y, miplevel) { var element = info.element var data = info.data var internalformat = info.internalformat var format = info.format var type = info.type var width = info.width var height = info.height setFlags(info) if (element) { gl.texSubImage2D( target, miplevel, x, y, format, type, element) } else if (info.compressed) { gl.compressedTexSubImage2D( target, miplevel, x, y, internalformat, width, height, data) } else if (info.needsCopy) { reglPoll() gl.copyTexSubImage2D( target, miplevel, x, y, info.xOffset, info.yOffset, width, height) } else { gl.texSubImage2D( target, miplevel, x, y, width, height, format, type, data) } } // texImage pool var imagePool = [] function allocImage () { return imagePool.pop() || new TexImage() } function freeImage (image) { if (image.needsFree) { pool.freeType(image.data) } TexImage.call(image) imagePool.push(image) } // ------------------------------------------------------- // Mip map // ------------------------------------------------------- function MipMap () { TexFlags.call(this) this.genMipmaps = false this.mipmapHint = GL_DONT_CARE this.mipmask = 0 this.images = Array(16) } function parseMipMapFromShape (mipmap, width, height) { var img = mipmap.images[0] = allocImage() mipmap.mipmask = 1 img.width = mipmap.width = width img.height = mipmap.height = height img.channels = mipmap.channels = 4 } function parseMipMapFromObject (mipmap, options) { var imgData = null if (isPixelData(options)) { imgData = mipmap.images[0] = allocImage() copyFlags(imgData, mipmap) parseImage(imgData, options) mipmap.mipmask = 1 } else { parseFlags(mipmap, options) if (Array.isArray(options.mipmap)) { var mipData = options.mipmap for (var i = 0; i < mipData.length; ++i) { imgData = mipmap.images[i] = allocImage() copyFlags(imgData, mipmap) imgData.width >>= i imgData.height >>= i parseImage(imgData, mipData[i]) mipmap.mipmask |= (1 << i) } } else { imgData = mipmap.images[0] = allocImage() copyFlags(imgData, mipmap) parseImage(imgData, options) mipmap.mipmask = 1 } } copyFlags(mipmap, mipmap.images[0]) // For textures of the compressed format WEBGL_compressed_texture_s3tc // we must have that // // "When level equals zero width and height must be a multiple of 4. // When level is greater than 0 width and height must be 0, 1, 2 or a multiple of 4. " // // but we do not yet support having multiple mipmap levels for compressed textures, // so we only test for level zero. if ( mipmap.compressed && ( mipmap.internalformat === GL_COMPRESSED_RGB_S3TC_DXT1_EXT || mipmap.internalformat === GL_COMPRESSED_RGBA_S3TC_DXT1_EXT || mipmap.internalformat === GL_COMPRESSED_RGBA_S3TC_DXT3_EXT || mipmap.internalformat === GL_COMPRESSED_RGBA_S3TC_DXT5_EXT ) ) { } } function setMipMap (mipmap, target) { var images = mipmap.images for (var i = 0; i < images.length; ++i) { if (!images[i]) { return } setImage(images[i], target, i) } } var mipPool = [] function allocMipMap () { var result = mipPool.pop() || new MipMap() TexFlags.call(result) result.mipmask = 0 for (var i = 0; i < 16; ++i) { result.images[i] = null } return result } function freeMipMap (mipmap) { var images = mipmap.images for (var i = 0; i < images.length; ++i) { if (images[i]) { freeImage(images[i]) } images[i] = null } mipPool.push(mipmap) } // ------------------------------------------------------- // Tex info // ------------------------------------------------------- function TexInfo () { this.minFilter = GL_NEAREST this.magFilter = GL_NEAREST this.wrapS = GL_CLAMP_TO_EDGE this.wrapT = GL_CLAMP_TO_EDGE this.anisotropic = 1 this.genMipmaps = false this.mipmapHint = GL_DONT_CARE } function parseTexInfo (info, options) { if ('min' in options) { var minFilter = options.min info.minFilter = minFilters[minFilter] if (MIPMAP_FILTERS.indexOf(info.minFilter) >= 0 && !('faces' in options)) { info.genMipmaps = true } } if ('mag' in options) { var magFilter = options.mag info.magFilter = magFilters[magFilter] } var wrapS = info.wrapS var wrapT = info.wrapT if ('wrap' in options) { var wrap = options.wrap if (typeof wrap === 'string') { wrapS = wrapT = wrapModes[wrap] } else if (Array.isArray(wrap)) { wrapS = wrapModes[wrap[0]] wrapT = wrapModes[wrap[1]] } } else { if ('wrapS' in options) { var optWrapS = options.wrapS wrapS = wrapModes[optWrapS] } if ('wrapT' in options) { var optWrapT = options.wrapT wrapT = wrapModes[optWrapT] } } info.wrapS = wrapS info.wrapT = wrapT if ('anisotropic' in options) { var anisotropic = options.anisotropic info.anisotropic = options.anisotropic } if ('mipmap' in options) { var hasMipMap = false switch (typeof options.mipmap) { case 'string': info.mipmapHint = mipmapHint[options.mipmap] info.genMipmaps = true hasMipMap = true break case 'boolean': hasMipMap = info.genMipmaps = options.mipmap break case 'object': info.genMipmaps = false hasMipMap = true break default: } if (hasMipMap && !('min' in options)) { info.minFilter = GL_NEAREST_MIPMAP_NEAREST } } } function setTexInfo (info, target) { gl.texParameteri(target, GL_TEXTURE_MIN_FILTER, info.minFilter) gl.texParameteri(target, GL_TEXTURE_MAG_FILTER, info.magFilter) gl.texParameteri(target, GL_TEXTURE_WRAP_S, info.wrapS) gl.texParameteri(target, GL_TEXTURE_WRAP_T, info.wrapT) if (extensions.ext_texture_filter_anisotropic) { gl.texParameteri(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, info.anisotropic) } if (info.genMipmaps) { gl.hint(GL_GENERATE_MIPMAP_HINT, info.mipmapHint) gl.generateMipmap(target) } } // ------------------------------------------------------- // Full texture object // ------------------------------------------------------- var textureCount = 0 var textureSet = {} var numTexUnits = limits.maxTextureUnits var textureUnits = Array(numTexUnits).map(function () { return null }) function REGLTexture (target) { TexFlags.call(this) this.mipmask = 0 this.internalformat = GL_RGBA$1 this.id = textureCount++ this.refCount = 1 this.target = target this.texture = gl.createTexture() this.unit = -1 this.bindCount = 0 this.texInfo = new TexInfo() if (config.profile) { this.stats = { size: 0 } } } function tempBind (texture) { gl.activeTexture(GL_TEXTURE0$1) gl.bindTexture(texture.target, texture.texture) } function tempRestore () { var prev = textureUnits[0] if (prev) { gl.bindTexture(prev.target, prev.texture) } else { gl.bindTexture(GL_TEXTURE_2D$1, null) } } function destroy (texture) { var handle = texture.texture var unit = texture.unit var target = texture.target if (unit >= 0) { gl.activeTexture(GL_TEXTURE0$1 + unit) gl.bindTexture(target, null) textureUnits[unit] = null } gl.deleteTexture(handle) texture.texture = null texture.params = null texture.pixels = null texture.refCount = 0 delete textureSet[texture.id] stats.textureCount-- } extend(REGLTexture.prototype, { bind: function () { var texture = this texture.bindCount += 1 var unit = texture.unit if (unit < 0) { for (var i = 0; i < numTexUnits; ++i) { var other = textureUnits[i] if (other) { if (other.bindCount > 0) { continue } other.unit = -1 } textureUnits[i] = texture unit = i break } if (unit >= numTexUnits) { } if (config.profile && stats.maxTextureUnits < (unit + 1)) { stats.maxTextureUnits = unit + 1 // +1, since the units are zero-based } texture.unit = unit gl.activeTexture(GL_TEXTURE0$1 + unit) gl.bindTexture(texture.target, texture.texture) } return unit }, unbind: function () { this.bindCount -= 1 }, decRef: function () { if (--this.refCount <= 0) { destroy(this) } } }) function createTexture2D (a, b) { var texture = new REGLTexture(GL_TEXTURE_2D$1) textureSet[texture.id] = texture stats.textureCount++ function reglTexture2D (a, b) { var texInfo = texture.texInfo TexInfo.call(texInfo) var mipData = allocMipMap() if (typeof a === 'number') { if (typeof b === 'number') { parseMipMapFromShape(mipData, a | 0, b | 0) } else { parseMipMapFromShape(mipData, a | 0, a | 0) } } else if (a) { parseTexInfo(texInfo, a) parseMipMapFromObject(mipData, a) } else { // empty textures get assigned a default shape of 1x1 parseMipMapFromShape(mipData, 1, 1) } if (texInfo.genMipmaps) { mipData.mipmask = (mipData.width << 1) - 1 } texture.mipmask = mipData.mipmask copyFlags(texture, mipData) texture.internalformat = mipData.internalformat reglTexture2D.width = mipData.width reglTexture2D.height = mipData.height tempBind(texture) setMipMap(mipData, GL_TEXTURE_2D$1) setTexInfo(texInfo, GL_TEXTURE_2D$1) tempRestore() freeMipMap(mipData) if (config.profile) { texture.stats.size = getTextureSize( texture.internalformat, texture.type, mipData.width, mipData.height, texInfo.genMipmaps, false) } reglTexture2D.format = textureFormatsInvert[texture.internalformat] reglTexture2D.type = textureTypesInvert[texture.type] reglTexture2D.mag = magFiltersInvert[texInfo.magFilter] reglTexture2D.min = minFiltersInvert[texInfo.minFilter] reglTexture2D.wrapS = wrapModesInvert[texInfo.wrapS] reglTexture2D.wrapT = wrapModesInvert[texInfo.wrapT] return reglTexture2D } function subimage (image, x_, y_, level_) { var x = x_ | 0 var y = y_ | 0 var level = level_ | 0 var imageData = allocImage() copyFlags(imageData, texture) imageData.width = 0 imageData.height = 0 parseImage(imageData, image) imageData.width = imageData.width || ((texture.width >> level) - x) imageData.height = imageData.height || ((texture.height >> level) - y) tempBind(texture) setSubImage(imageData, GL_TEXTURE_2D$1, x, y, level) tempRestore() freeImage(imageData) return reglTexture2D } function resize (w_, h_) { var w = w_ | 0 var h = (h_ | 0) || w if (w === texture.width && h === texture.height) { return reglTexture2D } reglTexture2D.width = texture.width = w reglTexture2D.height = texture.height = h tempBind(texture) for (var i = 0; texture.mipmask >> i; ++i) { var _w = w >> i var _h = h >> i if (!_w || !_h) break gl.texImage2D( GL_TEXTURE_2D$1, i, texture.format, _w, _h, 0, texture.format, texture.type, null) } tempRestore() // also, recompute the texture size. if (config.profile) { texture.stats.size = getTextureSize( texture.internalformat, texture.type, w, h, false, false) } return reglTexture2D } reglTexture2D(a, b) reglTexture2D.subimage = subimage reglTexture2D.resize = resize reglTexture2D._reglType = 'texture2d' reglTexture2D._texture = texture if (config.profile) { reglTexture2D.stats = texture.stats } reglTexture2D.destroy = function () { texture.decRef() } return reglTexture2D } function createTextureCube (a0, a1, a2, a3, a4, a5) { var texture = new REGLTexture(GL_TEXTURE_CUBE_MAP$1) textureSet[texture.id] = texture stats.cubeCount++ var faces = new Array(6) function reglTextureCube (a0, a1, a2, a3, a4, a5) { var i var texInfo = texture.texInfo TexInfo.call(texInfo) for (i = 0; i < 6; ++i) { faces[i] = allocMipMap() } if (typeof a0 === 'number' || !a0) { var s = (a0 | 0) || 1 for (i = 0; i < 6; ++i) { parseMipMapFromShape(faces[i], s, s) } } else if (typeof a0 === 'object') { if (a1) { parseMipMapFromObject(faces[0], a0) parseMipMapFromObject(faces[1], a1) parseMipMapFromObject(faces[2], a2) parseMipMapFromObject(faces[3], a3) parseMipMapFromObject(faces[4], a4) parseMipMapFromObject(faces[5], a5) } else { parseTexInfo(texInfo, a0) parseFlags(texture, a0) if ('faces' in a0) { var faceInput = a0.faces for (i = 0; i < 6; ++i) { copyFlags(faces[i], texture) parseMipMapFromObject(faces[i], faceInput[i]) } } else { for (i = 0; i < 6; ++i) { parseMipMapFromObject(faces[i], a0) } } } } else { } copyFlags(texture, faces[0]) if (texInfo.genMipmaps) { texture.mipmask = (faces[0].width << 1) - 1 } else { texture.mipmask = faces[0].mipmask } texture.internalformat = faces[0].internalformat reglTextureCube.width = faces[0].width reglTextureCube.height = faces[0].height tempBind(texture) for (i = 0; i < 6; ++i) { setMipMap(faces[i], GL_TEXTURE_CUBE_MAP_POSITIVE_X$1 + i) } setTexInfo(texInfo, GL_TEXTURE_CUBE_MAP$1) tempRestore() if (config.profile) { texture.stats.size = getTextureSize( texture.internalformat, texture.type, reglTextureCube.width, reglTextureCube.height, texInfo.genMipmaps, true) } reglTextureCube.format = textureFormatsInvert[texture.internalformat] reglTextureCube.type = textureTypesInvert[texture.type] reglTextureCube.mag = magFiltersInvert[texInfo.magFilter] reglTextureCube.min = minFiltersInvert[texInfo.minFilter] reglTextureCube.wrapS = wrapModesInvert[texInfo.wrapS] reglTextureCube.wrapT = wrapModesInvert[texInfo.wrapT] for (i = 0; i < 6; ++i) { freeMipMap(faces[i]) } return reglTextureCube } function subimage (face, image, x_, y_, level_) { var x = x_ | 0 var y = y_ | 0 var level = level_ | 0 var imageData = allocImage() copyFlags(imageData, texture) imageData.width = 0 imageData.height = 0 parseImage(imageData, image) imageData.width = imageData.width || ((texture.width >> level) - x) imageData.height = imageData.height || ((texture.height >> level) - y) tempBind(texture) setSubImage(imageData, GL_TEXTURE_CUBE_MAP_POSITIVE_X$1 + face, x, y, level) tempRestore() freeImage(imageData) return reglTextureCube } function resize (radius_) { var radius = radius_ | 0 if (radius === texture.width) { return } reglTextureCube.width = texture.width = radius reglTextureCube.height = texture.height = radius tempBind(texture) for (var i = 0; i < 6; ++i) { for (var j = 0; texture.mipmask >> j; ++j) { gl.texImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X$1 + i, j, texture.format, radius >> j, radius >> j, 0, texture.format, texture.type, null) } } tempRestore() if (config.profile) { texture.stats.size = getTextureSize( texture.internalformat, texture.type, reglTextureCube.width, reglTextureCube.height, false, true) } return reglTextureCube } reglTextureCube(a0, a1, a2, a3, a4, a5) reglTextureCube.subimage = subimage reglTextureCube.resize = resize reglTextureCube._reglType = 'textureCube' reglTextureCube._texture = texture if (config.profile) { reglTextureCube.stats = texture.stats } reglTextureCube.destroy = function () { texture.decRef() } return reglTextureCube } // Called when regl is destroyed function destroyTextures () { for (var i = 0; i < numTexUnits; ++i) { gl.activeTexture(GL_TEXTURE0$1 + i) gl.bindTexture(GL_TEXTURE_2D$1, null) textureUnits[i] = null } values(textureSet).forEach(destroy) stats.cubeCount = 0 stats.textureCount = 0 } if (config.profile) { stats.getTotalTextureSize = function () { var total = 0 Object.keys(textureSet).forEach(function (key) { total += textureSet[key].stats.size }) return total } } function restoreTextures () { for (var i = 0; i < numTexUnits; ++i) { var tex = textureUnits[i] if (tex) { tex.bindCount = 0 tex.unit = -1 textureUnits[i] = null } } values(textureSet).forEach(function (texture) { texture.texture = gl.createTexture() gl.bindTexture(texture.target, texture.texture) for (var i = 0; i < 32; ++i) { if ((texture.mipmask & (1 << i)) === 0) { continue } if (texture.target === GL_TEXTURE_2D$1) { gl.texImage2D(GL_TEXTURE_2D$1, i, texture.internalformat, texture.width >> i, texture.height >> i, 0, texture.internalformat, texture.type, null) } else { for (var j = 0; j < 6; ++j) { gl.texImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X$1 + j, i, texture.internalformat, texture.width >> i, texture.height >> i, 0, texture.internalformat, texture.type, null) } } } setTexInfo(texture.texInfo, texture.target) }) } function refreshTextures () { for (var i = 0; i < numTexUnits; ++i) { var tex = textureUnits[i] if (tex) { tex.bindCount = 0 tex.unit = -1 textureUnits[i] = null } gl.activeTexture(GL_TEXTURE0$1 + i) gl.bindTexture(GL_TEXTURE_2D$1, null) gl.bindTexture(GL_TEXTURE_CUBE_MAP$1, null) } } return { create2D: createTexture2D, createCube: createTextureCube, clear: destroyTextures, getTexture: function (wrapper) { return null }, restore: restoreTextures, refresh: refreshTextures } } var GL_RENDERBUFFER = 0x8D41 var GL_RGBA4$1 = 0x8056 var GL_RGB5_A1$1 = 0x8057 var GL_RGB565$1 = 0x8D62 var GL_DEPTH_COMPONENT16 = 0x81A5 var GL_STENCIL_INDEX8 = 0x8D48 var GL_DEPTH_STENCIL$1 = 0x84F9 var GL_SRGB8_ALPHA8_EXT = 0x8C43 var GL_RGBA32F_EXT = 0x8814 var GL_RGBA16F_EXT = 0x881A var GL_RGB16F_EXT = 0x881B var FORMAT_SIZES = [] FORMAT_SIZES[GL_RGBA4$1] = 2 FORMAT_SIZES[GL_RGB5_A1$1] = 2 FORMAT_SIZES[GL_RGB565$1] = 2 FORMAT_SIZES[GL_DEPTH_COMPONENT16] = 2 FORMAT_SIZES[GL_STENCIL_INDEX8] = 1 FORMAT_SIZES[GL_DEPTH_STENCIL$1] = 4 FORMAT_SIZES[GL_SRGB8_ALPHA8_EXT] = 4 FORMAT_SIZES[GL_RGBA32F_EXT] = 16 FORMAT_SIZES[GL_RGBA16F_EXT] = 8 FORMAT_SIZES[GL_RGB16F_EXT] = 6 function getRenderbufferSize (format, width, height) { return FORMAT_SIZES[format] * width * height } var wrapRenderbuffers = function (gl, extensions, limits, stats, config) { var formatTypes = { 'rgba4': GL_RGBA4$1, 'rgb565': GL_RGB565$1, 'rgb5 a1': GL_RGB5_A1$1, 'depth': GL_DEPTH_COMPONENT16, 'stencil': GL_STENCIL_INDEX8, 'depth stencil': GL_DEPTH_STENCIL$1 } if (extensions.ext_srgb) { formatTypes['srgba'] = GL_SRGB8_ALPHA8_EXT } if (extensions.ext_color_buffer_half_float) { formatTypes['rgba16f'] = GL_RGBA16F_EXT formatTypes['rgb16f'] = GL_RGB16F_EXT } if (extensions.webgl_color_buffer_float) { formatTypes['rgba32f'] = GL_RGBA32F_EXT } var formatTypesInvert = [] Object.keys(formatTypes).forEach(function (key) { var val = formatTypes[key] formatTypesInvert[val] = key }) var renderbufferCount = 0 var renderbufferSet = {} function REGLRenderbuffer (renderbuffer) { this.id = renderbufferCount++ this.refCount = 1 this.renderbuffer = renderbuffer this.format = GL_RGBA4$1 this.width = 0 this.height = 0 if (config.profile) { this.stats = { size: 0 } } } REGLRenderbuffer.prototype.decRef = function () { if (--this.refCount <= 0) { destroy(this) } } function destroy (rb) { var handle = rb.renderbuffer gl.bindRenderbuffer(GL_RENDERBUFFER, null) gl.deleteRenderbuffer(handle) rb.renderbuffer = null rb.refCount = 0 delete renderbufferSet[rb.id] stats.renderbufferCount-- } function createRenderbuffer (a, b) { var renderbuffer = new REGLRenderbuffer(gl.createRenderbuffer()) renderbufferSet[renderbuffer.id] = renderbuffer stats.renderbufferCount++ function reglRenderbuffer (a, b) { var w = 0 var h = 0 var format = GL_RGBA4$1 if (typeof a === 'object' && a) { var options = a if ('shape' in options) { var shape = options.shape w = shape[0] | 0 h = shape[1] | 0 } else { if ('radius' in options) { w = h = options.radius | 0 } if ('width' in options) { w = options.width | 0 } if ('height' in options) { h = options.height | 0 } } if ('format' in options) { format = formatTypes[options.format] } } else if (typeof a === 'number') { w = a | 0 if (typeof b === 'number') { h = b | 0 } else { h = w } } else if (!a) { w = h = 1 } else { } // check shape if (w === renderbuffer.width && h === renderbuffer.height && format === renderbuffer.format) { return } reglRenderbuffer.width = renderbuffer.width = w reglRenderbuffer.height = renderbuffer.height = h renderbuffer.format = format gl.bindRenderbuffer(GL_RENDERBUFFER, renderbuffer.renderbuffer) gl.renderbufferStorage(GL_RENDERBUFFER, format, w, h) if (config.profile) { renderbuffer.stats.size = getRenderbufferSize(renderbuffer.format, renderbuffer.width, renderbuffer.height) } reglRenderbuffer.format = formatTypesInvert[renderbuffer.format] return reglRenderbuffer } function resize (w_, h_) { var w = w_ | 0 var h = (h_ | 0) || w if (w === renderbuffer.width && h === renderbuffer.height) { return reglRenderbuffer } // check shape reglRenderbuffer.width = renderbuffer.width = w reglRenderbuffer.height = renderbuffer.height = h gl.bindRenderbuffer(GL_RENDERBUFFER, renderbuffer.renderbuffer) gl.renderbufferStorage(GL_RENDERBUFFER, renderbuffer.format, w, h) // also, recompute size. if (config.profile) { renderbuffer.stats.size = getRenderbufferSize( renderbuffer.format, renderbuffer.width, renderbuffer.height) } return reglRenderbuffer } reglRenderbuffer(a, b) reglRenderbuffer.resize = resize reglRenderbuffer._reglType = 'renderbuffer' reglRenderbuffer._renderbuffer = renderbuffer if (config.profile) { reglRenderbuffer.stats = renderbuffer.stats } reglRenderbuffer.destroy = function () { renderbuffer.decRef() } return reglRenderbuffer } if (config.profile) { stats.getTotalRenderbufferSize = function () { var total = 0 Object.keys(renderbufferSet).forEach(function (key) { total += renderbufferSet[key].stats.size }) return total } } function restoreRenderbuffers () { values(renderbufferSet).forEach(function (rb) { rb.renderbuffer = gl.createRenderbuffer() gl.bindRenderbuffer(GL_RENDERBUFFER, rb.renderbuffer) gl.renderbufferStorage(GL_RENDERBUFFER, rb.format, rb.width, rb.height) }) gl.bindRenderbuffer(GL_RENDERBUFFER, null) } return { create: createRenderbuffer, clear: function () { values(renderbufferSet).forEach(destroy) }, restore: restoreRenderbuffers } } // We store these constants so that the minifier can inline them var GL_FRAMEBUFFER$1 = 0x8D40 var GL_RENDERBUFFER$1 = 0x8D41 var GL_TEXTURE_2D$2 = 0x0DE1 var GL_TEXTURE_CUBE_MAP_POSITIVE_X$2 = 0x8515 var GL_COLOR_ATTACHMENT0$1 = 0x8CE0 var GL_DEPTH_ATTACHMENT = 0x8D00 var GL_STENCIL_ATTACHMENT = 0x8D20 var GL_DEPTH_STENCIL_ATTACHMENT = 0x821A var GL_FRAMEBUFFER_COMPLETE$1 = 0x8CD5 var GL_HALF_FLOAT_OES$1 = 0x8D61 var GL_UNSIGNED_BYTE$5 = 0x1401 var GL_FLOAT$4 = 0x1406 var GL_RGB$1 = 0x1907 var GL_RGBA$2 = 0x1908 // for every texture format, store // the number of channels var textureFormatChannels = [] textureFormatChannels[GL_RGBA$2] = 4 textureFormatChannels[GL_RGB$1] = 3 // for every texture type, store // the size in bytes. var textureTypeSizes = [] textureTypeSizes[GL_UNSIGNED_BYTE$5] = 1 textureTypeSizes[GL_FLOAT$4] = 4 textureTypeSizes[GL_HALF_FLOAT_OES$1] = 2 function wrapFBOState ( gl, extensions, limits, textureState, renderbufferState, stats) { var framebufferState = { cur: null, next: null, dirty: false, setFBO: null } var colorTextureFormats = ['rgba'] var colorRenderbufferFormats = ['rgba4', 'rgb565', 'rgb5 a1'] if (extensions.ext_srgb) { colorRenderbufferFormats.push('srgba') } if (extensions.ext_color_buffer_half_float) { colorRenderbufferFormats.push('rgba16f', 'rgb16f') } if (extensions.webgl_color_buffer_float) { colorRenderbufferFormats.push('rgba32f') } var colorTypes = ['uint8'] if (extensions.oes_texture_half_float) { colorTypes.push('half float', 'float16') } if (extensions.oes_texture_float) { colorTypes.push('float', 'float32') } function FramebufferAttachment (target, texture, renderbuffer) { this.target = target this.texture = texture this.renderbuffer = renderbuffer var w = 0 var h = 0 if (texture) { w = texture.width h = texture.height } else if (renderbuffer) { w = renderbuffer.width h = renderbuffer.height } this.width = w this.height = h } function decRef (attachment) { if (attachment) { if (attachment.texture) { attachment.texture._texture.decRef() } if (attachment.renderbuffer) { attachment.renderbuffer._renderbuffer.decRef() } } } function incRefAndCheckShape (attachment, width, height) { if (!attachment) { return } if (attachment.texture) { var texture = attachment.texture._texture var tw = Math.max(1, texture.width) var th = Math.max(1, texture.height) texture.refCount += 1 } else { var renderbuffer = attachment.renderbuffer._renderbuffer renderbuffer.refCount += 1 } } function attach (location, attachment) { if (attachment) { if (attachment.texture) { gl.framebufferTexture2D( GL_FRAMEBUFFER$1, location, attachment.target, attachment.texture._texture.texture, 0) } else { gl.framebufferRenderbuffer( GL_FRAMEBUFFER$1, location, GL_RENDERBUFFER$1, attachment.renderbuffer._renderbuffer.renderbuffer) } } } function parseAttachment (attachment) { var target = GL_TEXTURE_2D$2 var texture = null var renderbuffer = null var data = attachment if (typeof attachment === 'object') { data = attachment.data if ('target' in attachment) { target = attachment.target | 0 } } var type = data._reglType if (type === 'texture2d') { texture = data } else if (type === 'textureCube') { texture = data } else if (type === 'renderbuffer') { renderbuffer = data target = GL_RENDERBUFFER$1 } else { } return new FramebufferAttachment(target, texture, renderbuffer) } function allocAttachment ( width, height, isTexture, format, type) { if (isTexture) { var texture = textureState.create2D({ width: width, height: height, format: format, type: type }) texture._texture.refCount = 0 return new FramebufferAttachment(GL_TEXTURE_2D$2, texture, null) } else { var rb = renderbufferState.create({ width: width, height: height, format: format }) rb._renderbuffer.refCount = 0 return new FramebufferAttachment(GL_RENDERBUFFER$1, null, rb) } } function unwrapAttachment (attachment) { return attachment && (attachment.texture || attachment.renderbuffer) } function resizeAttachment (attachment, w, h) { if (attachment) { if (attachment.texture) { attachment.texture.resize(w, h) } else if (attachment.renderbuffer) { attachment.renderbuffer.resize(w, h) } attachment.width = w attachment.height = h } } var framebufferCount = 0 var framebufferSet = {} function REGLFramebuffer () { this.id = framebufferCount++ framebufferSet[this.id] = this this.framebuffer = gl.createFramebuffer() this.width = 0 this.height = 0 this.colorAttachments = [] this.depthAttachment = null this.stencilAttachment = null this.depthStencilAttachment = null } function decFBORefs (framebuffer) { framebuffer.colorAttachments.forEach(decRef) decRef(framebuffer.depthAttachment) decRef(framebuffer.stencilAttachment) decRef(framebuffer.depthStencilAttachment) } function destroy (framebuffer) { var handle = framebuffer.framebuffer gl.deleteFramebuffer(handle) framebuffer.framebuffer = null stats.framebufferCount-- delete framebufferSet[framebuffer.id] } function updateFramebuffer (framebuffer) { var i gl.bindFramebuffer(GL_FRAMEBUFFER$1, framebuffer.framebuffer) var colorAttachments = framebuffer.colorAttachments for (i = 0; i < colorAttachments.length; ++i) { attach(GL_COLOR_ATTACHMENT0$1 + i, colorAttachments[i]) } for (i = colorAttachments.length; i < limits.maxColorAttachments; ++i) { gl.framebufferTexture2D( GL_FRAMEBUFFER$1, GL_COLOR_ATTACHMENT0$1 + i, GL_TEXTURE_2D$2, null, 0) } gl.framebufferTexture2D( GL_FRAMEBUFFER$1, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D$2, null, 0) gl.framebufferTexture2D( GL_FRAMEBUFFER$1, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D$2, null, 0) gl.framebufferTexture2D( GL_FRAMEBUFFER$1, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D$2, null, 0) attach(GL_DEPTH_ATTACHMENT, framebuffer.depthAttachment) attach(GL_STENCIL_ATTACHMENT, framebuffer.stencilAttachment) attach(GL_DEPTH_STENCIL_ATTACHMENT, framebuffer.depthStencilAttachment) // Check status code var status = gl.checkFramebufferStatus(GL_FRAMEBUFFER$1) if (!gl.isContextLost() && status !== GL_FRAMEBUFFER_COMPLETE$1) { } gl.bindFramebuffer(GL_FRAMEBUFFER$1, framebufferState.next ? framebufferState.next.framebuffer : null) framebufferState.cur = framebufferState.next // FIXME: Clear error code here. This is a work around for a bug in // headless-gl gl.getError() } function createFBO (a0, a1) { var framebuffer = new REGLFramebuffer() stats.framebufferCount++ function reglFramebuffer (a, b) { var i var width = 0 var height = 0 var needsDepth = true var needsStencil = true var colorBuffer = null var colorTexture = true var colorFormat = 'rgba' var colorType = 'uint8' var colorCount = 1 var depthBuffer = null var stencilBuffer = null var depthStencilBuffer = null var depthStencilTexture = false if (typeof a === 'number') { width = a | 0 height = (b | 0) || width } else if (!a) { width = height = 1 } else { var options = a if ('shape' in options) { var shape = options.shape width = shape[0] height = shape[1] } else { if ('radius' in options) { width = height = options.radius } if ('width' in options) { width = options.width } if ('height' in options) { height = options.height } } if ('color' in options || 'colors' in options) { colorBuffer = options.color || options.colors if (Array.isArray(colorBuffer)) { } } if (!colorBuffer) { if ('colorCount' in options) { colorCount = options.colorCount | 0 } if ('colorTexture' in options) { colorTexture = !!options.colorTexture colorFormat = 'rgba4' } if ('colorType' in options) { colorType = options.colorType if (!colorTexture) { if (colorType === 'half float' || colorType === 'float16') { colorFormat = 'rgba16f' } else if (colorType === 'float' || colorType === 'float32') { colorFormat = 'rgba32f' } } else { } } if ('colorFormat' in options) { colorFormat = options.colorFormat if (colorTextureFormats.indexOf(colorFormat) >= 0) { colorTexture = true } else if (colorRenderbufferFormats.indexOf(colorFormat) >= 0) { colorTexture = false } else { } } } if ('depthTexture' in options || 'depthStencilTexture' in options) { depthStencilTexture = !!(options.depthTexture || options.depthStencilTexture) } if ('depth' in options) { if (typeof options.depth === 'boolean') { needsDepth = options.depth } else { depthBuffer = options.depth needsStencil = false } } if ('stencil' in options) { if (typeof options.stencil === 'boolean') { needsStencil = options.stencil } else { stencilBuffer = options.stencil needsDepth = false } } if ('depthStencil' in options) { if (typeof options.depthStencil === 'boolean') { needsDepth = needsStencil = options.depthStencil } else { depthStencilBuffer = options.depthStencil needsDepth = false needsStencil = false } } } // parse attachments var colorAttachments = null var depthAttachment = null var stencilAttachment = null var depthStencilAttachment = null // Set up color attachments if (Array.isArray(colorBuffer)) { colorAttachments = colorBuffer.map(parseAttachment) } else if (colorBuffer) { colorAttachments = [parseAttachment(colorBuffer)] } else { colorAttachments = new Array(colorCount) for (i = 0; i < colorCount; ++i) { colorAttachments[i] = allocAttachment( width, height, colorTexture, colorFormat, colorType) } } width = width || colorAttachments[0].width height = height || colorAttachments[0].height if (depthBuffer) { depthAttachment = parseAttachment(depthBuffer) } else if (needsDepth && !needsStencil) { depthAttachment = allocAttachment( width, height, depthStencilTexture, 'depth', 'uint32') } if (stencilBuffer) { stencilAttachment = parseAttachment(stencilBuffer) } else if (needsStencil && !needsDepth) { stencilAttachment = allocAttachment( width, height, false, 'stencil', 'uint8') } if (depthStencilBuffer) { depthStencilAttachment = parseAttachment(depthStencilBuffer) } else if (!depthBuffer && !stencilBuffer && needsStencil && needsDepth) { depthStencilAttachment = allocAttachment( width, height, depthStencilTexture, 'depth stencil', 'depth stencil') } var commonColorAttachmentSize = null for (i = 0; i < colorAttachments.length; ++i) { incRefAndCheckShape(colorAttachments[i], width, height) if (colorAttachments[i] && colorAttachments[i].texture) { var colorAttachmentSize = textureFormatChannels[colorAttachments[i].texture._texture.format] * textureTypeSizes[colorAttachments[i].texture._texture.type] if (commonColorAttachmentSize === null) { commonColorAttachmentSize = colorAttachmentSize } else { // We need to make sure that all color attachments have the same number of bitplanes // (that is, the same numer of bits per pixel) // This is required by the GLES2.0 standard. See the beginning of Chapter 4 in that document. } } } incRefAndCheckShape(depthAttachment, width, height) incRefAndCheckShape(stencilAttachment, width, height) incRefAndCheckShape(depthStencilAttachment, width, height) // decrement references decFBORefs(framebuffer) framebuffer.width = width framebuffer.height = height framebuffer.colorAttachments = colorAttachments framebuffer.depthAttachment = depthAttachment framebuffer.stencilAttachment = stencilAttachment framebuffer.depthStencilAttachment = depthStencilAttachment reglFramebuffer.color = colorAttachments.map(unwrapAttachment) reglFramebuffer.depth = unwrapAttachment(depthAttachment) reglFramebuffer.stencil = unwrapAttachment(stencilAttachment) reglFramebuffer.depthStencil = unwrapAttachment(depthStencilAttachment) reglFramebuffer.width = framebuffer.width reglFramebuffer.height = framebuffer.height updateFramebuffer(framebuffer) return reglFramebuffer } function resize (w_, h_) { var w = Math.max(w_ | 0, 1) var h = Math.max((h_ | 0) || w, 1) if (w === framebuffer.width && h === framebuffer.height) { return reglFramebuffer } // resize all buffers var colorAttachments = framebuffer.colorAttachments for (var i = 0; i < colorAttachments.length; ++i) { resizeAttachment(colorAttachments[i], w, h) } resizeAttachment(framebuffer.depthAttachment, w, h) resizeAttachment(framebuffer.stencilAttachment, w, h) resizeAttachment(framebuffer.depthStencilAttachment, w, h) framebuffer.width = reglFramebuffer.width = w framebuffer.height = reglFramebuffer.height = h updateFramebuffer(framebuffer) return reglFramebuffer } reglFramebuffer(a0, a1) return extend(reglFramebuffer, { resize: resize, _reglType: 'framebuffer', _framebuffer: framebuffer, destroy: function () { destroy(framebuffer) decFBORefs(framebuffer) }, use: function (block) { framebufferState.setFBO({ framebuffer: reglFramebuffer }, block) } }) } function createCubeFBO (options) { var faces = Array(6) function reglFramebufferCube (a) { var i var params = { color: null } var radius = 0 var colorBuffer = null var colorFormat = 'rgba' var colorType = 'uint8' var colorCount = 1 if (typeof a === 'number') { radius = a | 0 } else if (!a) { radius = 1 } else { var options = a if ('shape' in options) { var shape = options.shape radius = shape[0] } else { if ('radius' in options) { radius = options.radius | 0 } if ('width' in options) { radius = options.width | 0 if ('height' in options) { } } else if ('height' in options) { radius = options.height | 0 } } if ('color' in options || 'colors' in options) { colorBuffer = options.color || options.colors if (Array.isArray(colorBuffer)) { } } if (!colorBuffer) { if ('colorCount' in options) { colorCount = options.colorCount | 0 } if ('colorType' in options) { colorType = options.colorType } if ('colorFormat' in options) { colorFormat = options.colorFormat } } if ('depth' in options) { params.depth = options.depth } if ('stencil' in options) { params.stencil = options.stencil } if ('depthStencil' in options) { params.depthStencil = options.depthStencil } } var colorCubes if (colorBuffer) { if (Array.isArray(colorBuffer)) { colorCubes = [] for (i = 0; i < colorBuffer.length; ++i) { colorCubes[i] = colorBuffer[i] } } else { colorCubes = [ colorBuffer ] } } else { colorCubes = Array(colorCount) var cubeMapParams = { radius: radius, format: colorFormat, type: colorType } for (i = 0; i < colorCount; ++i) { colorCubes[i] = textureState.createCube(cubeMapParams) } } // Check color cubes params.color = Array(colorCubes.length) for (i = 0; i < colorCubes.length; ++i) { var cube = colorCubes[i] radius = radius || cube.width params.color[i] = { target: GL_TEXTURE_CUBE_MAP_POSITIVE_X$2, data: colorCubes[i] } } for (i = 0; i < 6; ++i) { for (var j = 0; j < colorCubes.length; ++j) { params.color[j].target = GL_TEXTURE_CUBE_MAP_POSITIVE_X$2 + i } // reuse depth-stencil attachments across all cube maps if (i > 0) { params.depth = faces[0].depth params.stencil = faces[0].stencil params.depthStencil = faces[0].depthStencil } if (faces[i]) { (faces[i])(params) } else { faces[i] = createFBO(params) } } return extend(reglFramebufferCube, { width: radius, height: radius, color: colorCubes }) } function resize (radius_) { var i var radius = radius_ | 0 if (radius === reglFramebufferCube.width) { return reglFramebufferCube } var colors = reglFramebufferCube.color for (i = 0; i < colors.length; ++i) { colors[i].resize(radius) } for (i = 0; i < 6; ++i) { faces[i].resize(radius) } reglFramebufferCube.width = reglFramebufferCube.height = radius return reglFramebufferCube } reglFramebufferCube(options) return extend(reglFramebufferCube, { faces: faces, resize: resize, _reglType: 'framebufferCube', destroy: function () { faces.forEach(function (f) { f.destroy() }) } }) } function restoreFramebuffers () { framebufferState.cur = null framebufferState.next = null framebufferState.dirty = true values(framebufferSet).forEach(function (fb) { fb.framebuffer = gl.createFramebuffer() updateFramebuffer(fb) }) } return extend(framebufferState, { getFramebuffer: function (object) { if (typeof object === 'function' && object._reglType === 'framebuffer') { var fbo = object._framebuffer if (fbo instanceof REGLFramebuffer) { return fbo } } return null }, create: createFBO, createCube: createCubeFBO, clear: function () { values(framebufferSet).forEach(destroy) }, restore: restoreFramebuffers }) } var GL_FLOAT$5 = 5126 var GL_ARRAY_BUFFER$1 = 34962 var GL_ELEMENT_ARRAY_BUFFER$1 = 34963 function AttributeRecord () { this.state = 0 this.x = 0.0 this.y = 0.0 this.z = 0.0 this.w = 0.0 this.buffer = null this.size = 0 this.normalized = false this.type = GL_FLOAT$5 this.offset = 0 this.stride = 0 this.divisor = 0 } function wrapAttributeState ( gl, extensions, limits, stats, bufferState, elementState, drawState) { var NUM_ATTRIBUTES = limits.maxAttributes var attributeBindings = new Array(NUM_ATTRIBUTES) for (var i = 0; i < NUM_ATTRIBUTES; ++i) { attributeBindings[i] = new AttributeRecord() } var vaoCount = 0 var vaoSet = {} var state = { Record: AttributeRecord, scope: {}, state: attributeBindings, currentVAO: null, targetVAO: null, restore: extVAO() ? restoreVAO : function () {}, createVAO: createVAO, getVAO: getVAO, destroyBuffer: destroyBuffer, setVAO: extVAO() ? setVAOEXT : setVAOEmulated, clear: extVAO() ? destroyVAOEXT : function () {} } function destroyBuffer (buffer) { for (var i = 0; i < attributeBindings.length; ++i) { var record = attributeBindings[i] if (record.buffer === buffer) { gl.disableVertexAttribArray(i) record.buffer = null } } } function extVAO () { return extensions.oes_vertex_array_object } function extInstanced () { return extensions.angle_instanced_arrays } function getVAO (vao) { if (typeof vao === 'function' && vao._vao) { return vao._vao } return null } function setVAOEXT (vao) { if (vao === state.currentVAO) { return } var ext = extVAO() if (vao) { ext.bindVertexArrayOES(vao.vao) } else { ext.bindVertexArrayOES(null) } state.currentVAO = vao } function setVAOEmulated (vao) { if (vao === state.currentVAO) { return } if (vao) { vao.bindAttrs() } else { var exti = extInstanced() for (var i = 0; i < attributeBindings.length; ++i) { var binding = attributeBindings[i] if (binding.buffer) { gl.enableVertexAttribArray(i) binding.buffer.bind() gl.vertexAttribPointer(i, binding.size, binding.type, binding.normalized, binding.stride, binding.offfset) if (exti && binding.divisor) { exti.vertexAttribDivisorANGLE(i, binding.divisor) } } else { gl.disableVertexAttribArray(i) gl.vertexAttrib4f(i, binding.x, binding.y, binding.z, binding.w) } } if (drawState.elements) { gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER$1, drawState.elements.buffer.buffer) } else { gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER$1, null) } } state.currentVAO = vao } function destroyVAOEXT () { values(vaoSet).forEach(function (vao) { vao.destroy() }) } function REGLVAO () { this.id = ++vaoCount this.attributes = [] this.elements = null this.ownsElements = false this.count = 0 this.offset = 0 this.instances = -1 this.primitive = 4 var extension = extVAO() if (extension) { this.vao = extension.createVertexArrayOES() } else { this.vao = null } vaoSet[this.id] = this this.buffers = [] } REGLVAO.prototype.bindAttrs = function () { var exti = extInstanced() var attributes = this.attributes for (var i = 0; i < attributes.length; ++i) { var attr = attributes[i] if (attr.buffer) { gl.enableVertexAttribArray(i) gl.bindBuffer(GL_ARRAY_BUFFER$1, attr.buffer.buffer) gl.vertexAttribPointer(i, attr.size, attr.type, attr.normalized, attr.stride, attr.offset) if (exti && attr.divisor) { exti.vertexAttribDivisorANGLE(i, attr.divisor) } } else { gl.disableVertexAttribArray(i) gl.vertexAttrib4f(i, attr.x, attr.y, attr.z, attr.w) } } for (var j = attributes.length; j < NUM_ATTRIBUTES; ++j) { gl.disableVertexAttribArray(j) } var elements = elementState.getElements(this.elements) if (elements) { gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER$1, elements.buffer.buffer) } else { gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER$1, null) } } REGLVAO.prototype.refresh = function () { var ext = extVAO() if (ext) { ext.bindVertexArrayOES(this.vao) this.bindAttrs() state.currentVAO = null ext.bindVertexArrayOES(null) } } REGLVAO.prototype.destroy = function () { if (this.vao) { var extension = extVAO() if (this === state.currentVAO) { state.currentVAO = null extension.bindVertexArrayOES(null) } extension.deleteVertexArrayOES(this.vao) this.vao = null } if (this.ownsElements) { this.elements.destroy() this.elements = null this.ownsElements = false } if (vaoSet[this.id]) { delete vaoSet[this.id] stats.vaoCount -= 1 } } function restoreVAO () { var ext = extVAO() if (ext) { values(vaoSet).forEach(function (vao) { vao.refresh() }) } } function createVAO (_attr) { var vao = new REGLVAO() stats.vaoCount += 1 function updateVAO (options) { var attributes if (Array.isArray(options)) { attributes = options if (vao.elements && vao.ownsElements) { vao.elements.destroy() } vao.elements = null vao.ownsElements = false vao.offset = 0 vao.count = 0 vao.instances = -1 vao.primitive = 4 } else { if (options.elements) { var elements = options.elements if (vao.ownsElements) { if (typeof elements === 'function' && elements._reglType === 'elements') { vao.elements.destroy() vao.ownsElements = false } else { vao.elements(elements) vao.ownsElements = false } } else if (elementState.getElements(options.elements)) { vao.elements = options.elements vao.ownsElements = false } else { vao.elements = elementState.create(options.elements) vao.ownsElements = true } } else { vao.elements = null vao.ownsElements = false } attributes = options.attributes // set default vao vao.offset = 0 vao.count = -1 vao.instances = -1 vao.primitive = 4 // copy element properties if (vao.elements) { vao.count = vao.elements._elements.vertCount vao.primitive = vao.elements._elements.primType } if ('offset' in options) { vao.offset = options.offset | 0 } if ('count' in options) { vao.count = options.count | 0 } if ('instances' in options) { vao.instances = options.instances | 0 } if ('primitive' in options) { vao.primitive = primTypes[options.primitive] } } var bufUpdated = {} var nattributes = vao.attributes nattributes.length = attributes.length for (var i = 0; i < attributes.length; ++i) { var spec = attributes[i] var rec = nattributes[i] = new AttributeRecord() var data = spec.data || spec if (Array.isArray(data) || isTypedArray(data) || isNDArrayLike(data)) { var buf if (vao.buffers[i]) { buf = vao.buffers[i] if (isTypedArray(data) && buf._buffer.byteLength >= data.byteLength) { buf.subdata(data) } else { buf.destroy() vao.buffers[i] = null } } if (!vao.buffers[i]) { buf = vao.buffers[i] = bufferState.create(spec, GL_ARRAY_BUFFER$1, false, true) } rec.buffer = bufferState.getBuffer(buf) rec.size = rec.buffer.dimension | 0 rec.normalized = false rec.type = rec.buffer.dtype rec.offset = 0 rec.stride = 0 rec.divisor = 0 rec.state = 1 bufUpdated[i] = 1 } else if (bufferState.getBuffer(spec)) { rec.buffer = bufferState.getBuffer(spec) rec.size = rec.buffer.dimension | 0 rec.normalized = false rec.type = rec.buffer.dtype rec.offset = 0 rec.stride = 0 rec.divisor = 0 rec.state = 1 } else if (bufferState.getBuffer(spec.buffer)) { rec.buffer = bufferState.getBuffer(spec.buffer) rec.size = ((+spec.size) || rec.buffer.dimension) | 0 rec.normalized = !!spec.normalized || false if ('type' in spec) { rec.type = glTypes[spec.type] } else { rec.type = rec.buffer.dtype } rec.offset = (spec.offset || 0) | 0 rec.stride = (spec.stride || 0) | 0 rec.divisor = (spec.divisor || 0) | 0 rec.state = 1 } else if ('x' in spec) { rec.x = +spec.x || 0 rec.y = +spec.y || 0 rec.z = +spec.z || 0 rec.w = +spec.w || 0 rec.state = 2 } else { } } // retire unused buffers for (var j = 0; j < vao.buffers.length; ++j) { if (!bufUpdated[j] && vao.buffers[j]) { vao.buffers[j].destroy() vao.buffers[j] = null } } vao.refresh() return updateVAO } updateVAO.destroy = function () { for (var j = 0; j < vao.buffers.length; ++j) { if (vao.buffers[j]) { vao.buffers[j].destroy() } } vao.buffers.length = 0 if (vao.ownsElements) { vao.elements.destroy() vao.elements = null vao.ownsElements = false } vao.destroy() } updateVAO._vao = vao updateVAO._reglType = 'vao' return updateVAO(_attr) } return state } var GL_FRAGMENT_SHADER = 35632 var GL_VERTEX_SHADER = 35633 var GL_ACTIVE_UNIFORMS = 0x8B86 var GL_ACTIVE_ATTRIBUTES = 0x8B89 function wrapShaderState (gl, stringStore, stats, config) { // =================================================== // glsl compilation and linking // =================================================== var fragShaders = {} var vertShaders = {} function ActiveInfo (name, id, location, info) { this.name = name this.id = id this.location = location this.info = info } function insertActiveInfo (list, info) { for (var i = 0; i < list.length; ++i) { if (list[i].id === info.id) { list[i].location = info.location return } } list.push(info) } function getShader (type, id, command) { var cache = type === GL_FRAGMENT_SHADER ? fragShaders : vertShaders var shader = cache[id] if (!shader) { var source = stringStore.str(id) shader = gl.createShader(type) gl.shaderSource(shader, source) gl.compileShader(shader) cache[id] = shader } return shader } // =================================================== // program linking // =================================================== var programCache = {} var programList = [] var PROGRAM_COUNTER = 0 function REGLProgram (fragId, vertId) { this.id = PROGRAM_COUNTER++ this.fragId = fragId this.vertId = vertId this.program = null this.uniforms = [] this.attributes = [] this.refCount = 1 if (config.profile) { this.stats = { uniformsCount: 0, attributesCount: 0 } } } function linkProgram (desc, command, attributeLocations) { var i, info // ------------------------------- // compile & link // ------------------------------- var fragShader = getShader(GL_FRAGMENT_SHADER, desc.fragId) var vertShader = getShader(GL_VERTEX_SHADER, desc.vertId) var program = desc.program = gl.createProgram() gl.attachShader(program, fragShader) gl.attachShader(program, vertShader) if (attributeLocations) { for (i = 0; i < attributeLocations.length; ++i) { var binding = attributeLocations[i] gl.bindAttribLocation(program, binding[0], binding[1]) } } gl.linkProgram(program) // ------------------------------- // grab uniforms // ------------------------------- var numUniforms = gl.getProgramParameter(program, GL_ACTIVE_UNIFORMS) if (config.profile) { desc.stats.uniformsCount = numUniforms } var uniforms = desc.uniforms for (i = 0; i < numUniforms; ++i) { info = gl.getActiveUniform(program, i) if (info) { if (info.size > 1) { for (var j = 0; j < info.size; ++j) { var name = info.name.replace('[0]', '[' + j + ']') insertActiveInfo(uniforms, new ActiveInfo( name, stringStore.id(name), gl.getUniformLocation(program, name), info)) } } var uniName = info.name if (info.size > 1) { uniName = uniName.replace('[0]', '') } insertActiveInfo(uniforms, new ActiveInfo( uniName, stringStore.id(uniName), gl.getUniformLocation(program, uniName), info)) } } // ------------------------------- // grab attributes // ------------------------------- var numAttributes = gl.getProgramParameter(program, GL_ACTIVE_ATTRIBUTES) if (config.profile) { desc.stats.attributesCount = numAttributes } var attributes = desc.attributes for (i = 0; i < numAttributes; ++i) { info = gl.getActiveAttrib(program, i) if (info) { insertActiveInfo(attributes, new ActiveInfo( info.name, stringStore.id(info.name), gl.getAttribLocation(program, info.name), info)) } } } if (config.profile) { stats.getMaxUniformsCount = function () { var m = 0 programList.forEach(function (desc) { if (desc.stats.uniformsCount > m) { m = desc.stats.uniformsCount } }) return m } stats.getMaxAttributesCount = function () { var m = 0 programList.forEach(function (desc) { if (desc.stats.attributesCount > m) { m = desc.stats.attributesCount } }) return m } } function restoreShaders () { fragShaders = {} vertShaders = {} for (var i = 0; i < programList.length; ++i) { linkProgram(programList[i], null, programList[i].attributes.map(function (info) { return [info.location, info.name] })) } } return { clear: function () { var deleteShader = gl.deleteShader.bind(gl) values(fragShaders).forEach(deleteShader) fragShaders = {} values(vertShaders).forEach(deleteShader) vertShaders = {} programList.forEach(function (desc) { gl.deleteProgram(desc.program) }) programList.length = 0 programCache = {} stats.shaderCount = 0 }, program: function (vertId, fragId, command, attribLocations) { var cache = programCache[fragId] if (!cache) { cache = programCache[fragId] = {} } var prevProgram = cache[vertId] if (prevProgram) { prevProgram.refCount++ if (!attribLocations) { return prevProgram } } var program = new REGLProgram(fragId, vertId) stats.shaderCount++ linkProgram(program, command, attribLocations) if (!prevProgram) { cache[vertId] = program } programList.push(program) return extend(program, { destroy: function () { program.refCount-- if (program.refCount <= 0) { gl.deleteProgram(program.program) var idx = programList.indexOf(program) programList.splice(idx, 1) stats.shaderCount-- } // no program is linked to this vert anymore if (cache[program.vertId].refCount <= 0) { gl.deleteShader(vertShaders[program.vertId]) delete vertShaders[program.vertId] delete programCache[program.fragId][program.vertId] } // no program is linked to this frag anymore if (!Object.keys(programCache[program.fragId]).length) { gl.deleteShader(fragShaders[program.fragId]) delete fragShaders[program.fragId] delete programCache[program.fragId] } } }) }, restore: restoreShaders, shader: getShader, frag: -1, vert: -1 } } var GL_RGBA$3 = 6408 var GL_UNSIGNED_BYTE$6 = 5121 var GL_PACK_ALIGNMENT = 0x0D05 var GL_FLOAT$6 = 0x1406 // 5126 function wrapReadPixels ( gl, framebufferState, reglPoll, context, glAttributes, extensions, limits) { function readPixelsImpl (input) { var type if (framebufferState.next === null) { type = GL_UNSIGNED_BYTE$6 } else { type = framebufferState.next.colorAttachments[0].texture._texture.type } var x = 0 var y = 0 var width = context.framebufferWidth var height = context.framebufferHeight var data = null if (isTypedArray(input)) { data = input } else if (input) { x = input.x | 0 y = input.y | 0 width = (input.width || (context.framebufferWidth - x)) | 0 height = (input.height || (context.framebufferHeight - y)) | 0 data = input.data || null } // sanity check input.data if (data) { if (type === GL_UNSIGNED_BYTE$6) { } else if (type === GL_FLOAT$6) { } } // Update WebGL state reglPoll() // Compute size var size = width * height * 4 // Allocate data if (!data) { if (type === GL_UNSIGNED_BYTE$6) { data = new Uint8Array(size) } else if (type === GL_FLOAT$6) { data = data || new Float32Array(size) } } // Type check // Run read pixels gl.pixelStorei(GL_PACK_ALIGNMENT, 4) gl.readPixels(x, y, width, height, GL_RGBA$3, type, data) return data } function readPixelsFBO (options) { var result framebufferState.setFBO({ framebuffer: options.framebuffer }, function () { result = readPixelsImpl(options) }) return result } function readPixels (options) { if (!options || !('framebuffer' in options)) { return readPixelsImpl(options) } else { return readPixelsFBO(options) } } return readPixels } function slice (x) { return Array.prototype.slice.call(x) } function join (x) { return slice(x).join('') } function createEnvironment () { // Unique variable id counter var varCounter = 0 // Linked values are passed from this scope into the generated code block // Calling link() passes a value into the generated scope and returns // the variable name which it is bound to var linkedNames = [] var linkedValues = [] function link (value) { for (var i = 0; i < linkedValues.length; ++i) { if (linkedValues[i] === value) { return linkedNames[i] } } var name = 'g' + (varCounter++) linkedNames.push(name) linkedValues.push(value) return name } // create a code block function block () { var code = [] function push () { code.push.apply(code, slice(arguments)) } var vars = [] function def () { var name = 'v' + (varCounter++) vars.push(name) if (arguments.length > 0) { code.push(name, '=') code.push.apply(code, slice(arguments)) code.push(';') } return name } return extend(push, { def: def, toString: function () { return join([ (vars.length > 0 ? 'var ' + vars.join(',') + ';' : ''), join(code) ]) } }) } function scope () { var entry = block() var exit = block() var entryToString = entry.toString var exitToString = exit.toString function save (object, prop) { exit(object, prop, '=', entry.def(object, prop), ';') } return extend(function () { entry.apply(entry, slice(arguments)) }, { def: entry.def, entry: entry, exit: exit, save: save, set: function (object, prop, value) { save(object, prop) entry(object, prop, '=', value, ';') }, toString: function () { return entryToString() + exitToString() } }) } function conditional () { var pred = join(arguments) var thenBlock = scope() var elseBlock = scope() var thenToString = thenBlock.toString var elseToString = elseBlock.toString return extend(thenBlock, { then: function () { thenBlock.apply(thenBlock, slice(arguments)) return this }, else: function () { elseBlock.apply(elseBlock, slice(arguments)) return this }, toString: function () { var elseClause = elseToString() if (elseClause) { elseClause = 'else{' + elseClause + '}' } return join([ 'if(', pred, '){', thenToString(), '}', elseClause ]) } }) } // procedure list var globalBlock = block() var procedures = {} function proc (name, count) { var args = [] function arg () { var name = 'a' + args.length args.push(name) return name } count = count || 0 for (var i = 0; i < count; ++i) { arg() } var body = scope() var bodyToString = body.toString var result = procedures[name] = extend(body, { arg: arg, toString: function () { return join([ 'function(', args.join(), '){', bodyToString(), '}' ]) } }) return result } function compile () { var code = ['"use strict";', globalBlock, 'return {'] Object.keys(procedures).forEach(function (name) { code.push('"', name, '":', procedures[name].toString(), ',') }) code.push('}') var src = join(code) .replace(/;/g, ';\n') .replace(/}/g, '}\n') .replace(/{/g, '{\n') var proc = Function.apply(null, linkedNames.concat(src)) return proc.apply(null, linkedValues) } return { global: globalBlock, link: link, block: block, proc: proc, scope: scope, cond: conditional, compile: compile } } // "cute" names for vector components var CUTE_COMPONENTS = 'xyzw'.split('') var GL_UNSIGNED_BYTE$7 = 5121 var ATTRIB_STATE_POINTER = 1 var ATTRIB_STATE_CONSTANT = 2 var DYN_FUNC$1 = 0 var DYN_PROP$1 = 1 var DYN_CONTEXT$1 = 2 var DYN_STATE$1 = 3 var DYN_THUNK = 4 var DYN_CONSTANT$1 = 5 var DYN_ARRAY$1 = 6 var S_DITHER = 'dither' var S_BLEND_ENABLE = 'blend.enable' var S_BLEND_COLOR = 'blend.color' var S_BLEND_EQUATION = 'blend.equation' var S_BLEND_FUNC = 'blend.func' var S_DEPTH_ENABLE = 'depth.enable' var S_DEPTH_FUNC = 'depth.func' var S_DEPTH_RANGE = 'depth.range' var S_DEPTH_MASK = 'depth.mask' var S_COLOR_MASK = 'colorMask' var S_CULL_ENABLE = 'cull.enable' var S_CULL_FACE = 'cull.face' var S_FRONT_FACE = 'frontFace' var S_LINE_WIDTH = 'lineWidth' var S_POLYGON_OFFSET_ENABLE = 'polygonOffset.enable' var S_POLYGON_OFFSET_OFFSET = 'polygonOffset.offset' var S_SAMPLE_ALPHA = 'sample.alpha' var S_SAMPLE_ENABLE = 'sample.enable' var S_SAMPLE_COVERAGE = 'sample.coverage' var S_STENCIL_ENABLE = 'stencil.enable' var S_STENCIL_MASK = 'stencil.mask' var S_STENCIL_FUNC = 'stencil.func' var S_STENCIL_OPFRONT = 'stencil.opFront' var S_STENCIL_OPBACK = 'stencil.opBack' var S_SCISSOR_ENABLE = 'scissor.enable' var S_SCISSOR_BOX = 'scissor.box' var S_VIEWPORT = 'viewport' var S_PROFILE = 'profile' var S_FRAMEBUFFER = 'framebuffer' var S_VERT = 'vert' var S_FRAG = 'frag' var S_ELEMENTS = 'elements' var S_PRIMITIVE = 'primitive' var S_COUNT = 'count' var S_OFFSET = 'offset' var S_INSTANCES = 'instances' var S_VAO = 'vao' var SUFFIX_WIDTH = 'Width' var SUFFIX_HEIGHT = 'Height' var S_FRAMEBUFFER_WIDTH = S_FRAMEBUFFER + SUFFIX_WIDTH var S_FRAMEBUFFER_HEIGHT = S_FRAMEBUFFER + SUFFIX_HEIGHT var S_VIEWPORT_WIDTH = S_VIEWPORT + SUFFIX_WIDTH var S_VIEWPORT_HEIGHT = S_VIEWPORT + SUFFIX_HEIGHT var S_DRAWINGBUFFER = 'drawingBuffer' var S_DRAWINGBUFFER_WIDTH = S_DRAWINGBUFFER + SUFFIX_WIDTH var S_DRAWINGBUFFER_HEIGHT = S_DRAWINGBUFFER + SUFFIX_HEIGHT var NESTED_OPTIONS = [ S_BLEND_FUNC, S_BLEND_EQUATION, S_STENCIL_FUNC, S_STENCIL_OPFRONT, S_STENCIL_OPBACK, S_SAMPLE_COVERAGE, S_VIEWPORT, S_SCISSOR_BOX, S_POLYGON_OFFSET_OFFSET ] var GL_ARRAY_BUFFER$2 = 34962 var GL_ELEMENT_ARRAY_BUFFER$2 = 34963 var GL_CULL_FACE = 0x0B44 var GL_BLEND = 0x0BE2 var GL_DITHER = 0x0BD0 var GL_STENCIL_TEST = 0x0B90 var GL_DEPTH_TEST = 0x0B71 var GL_SCISSOR_TEST = 0x0C11 var GL_POLYGON_OFFSET_FILL = 0x8037 var GL_SAMPLE_ALPHA_TO_COVERAGE = 0x809E var GL_SAMPLE_COVERAGE = 0x80A0 var GL_FLOAT$7 = 5126 var GL_FLOAT_VEC2 = 35664 var GL_FLOAT_VEC3 = 35665 var GL_FLOAT_VEC4 = 35666 var GL_INT$2 = 5124 var GL_INT_VEC2 = 35667 var GL_INT_VEC3 = 35668 var GL_INT_VEC4 = 35669 var GL_BOOL = 35670 var GL_BOOL_VEC2 = 35671 var GL_BOOL_VEC3 = 35672 var GL_BOOL_VEC4 = 35673 var GL_FLOAT_MAT2 = 35674 var GL_FLOAT_MAT3 = 35675 var GL_FLOAT_MAT4 = 35676 var GL_SAMPLER_2D = 35678 var GL_SAMPLER_CUBE = 35680 var GL_TRIANGLES$1 = 4 var GL_FRONT = 1028 var GL_BACK = 1029 var GL_CW = 0x0900 var GL_CCW = 0x0901 var GL_MIN_EXT = 0x8007 var GL_MAX_EXT = 0x8008 var GL_ALWAYS = 519 var GL_KEEP = 7680 var GL_ZERO = 0 var GL_ONE = 1 var GL_FUNC_ADD = 0x8006 var GL_LESS = 513 var GL_FRAMEBUFFER$2 = 0x8D40 var GL_COLOR_ATTACHMENT0$2 = 0x8CE0 var blendFuncs = { '0': 0, '1': 1, 'zero': 0, 'one': 1, 'src color': 768, 'one minus src color': 769, 'src alpha': 770, 'one minus src alpha': 771, 'dst color': 774, 'one minus dst color': 775, 'dst alpha': 772, 'one minus dst alpha': 773, 'constant color': 32769, 'one minus constant color': 32770, 'constant alpha': 32771, 'one minus constant alpha': 32772, 'src alpha saturate': 776 } var compareFuncs = { 'never': 512, 'less': 513, '<': 513, 'equal': 514, '=': 514, '==': 514, '===': 514, 'lequal': 515, '<=': 515, 'greater': 516, '>': 516, 'notequal': 517, '!=': 517, '!==': 517, 'gequal': 518, '>=': 518, 'always': 519 } var stencilOps = { '0': 0, 'zero': 0, 'keep': 7680, 'replace': 7681, 'increment': 7682, 'decrement': 7683, 'increment wrap': 34055, 'decrement wrap': 34056, 'invert': 5386 } var orientationType = { 'cw': GL_CW, 'ccw': GL_CCW } function isBufferArgs (x) { return Array.isArray(x) || isTypedArray(x) || isNDArrayLike(x) } // Make sure viewport is processed first function sortState (state) { return state.sort(function (a, b) { if (a === S_VIEWPORT) { return -1 } else if (b === S_VIEWPORT) { return 1 } return (a < b) ? -1 : 1 }) } function Declaration (thisDep, contextDep, propDep, append) { this.thisDep = thisDep this.contextDep = contextDep this.propDep = propDep this.append = append } function isStatic (decl) { return decl && !(decl.thisDep || decl.contextDep || decl.propDep) } function createStaticDecl (append) { return new Declaration(false, false, false, append) } function createDynamicDecl (dyn, append) { var type = dyn.type if (type === DYN_FUNC$1) { var numArgs = dyn.data.length return new Declaration( true, numArgs >= 1, numArgs >= 2, append) } else if (type === DYN_THUNK) { var data = dyn.data return new Declaration( data.thisDep, data.contextDep, data.propDep, append) } else if (type === DYN_CONSTANT$1) { return new Declaration( false, false, false, append) } else if (type === DYN_ARRAY$1) { var thisDep = false var contextDep = false var propDep = false for (var i = 0; i < dyn.data.length; ++i) { var subDyn = dyn.data[i] if (subDyn.type === DYN_PROP$1) { propDep = true } else if (subDyn.type === DYN_CONTEXT$1) { contextDep = true } else if (subDyn.type === DYN_STATE$1) { thisDep = true } else if (subDyn.type === DYN_FUNC$1) { thisDep = true var subArgs = subDyn.data if (subArgs >= 1) { contextDep = true } if (subArgs >= 2) { propDep = true } } else if (subDyn.type === DYN_THUNK) { thisDep = thisDep || subDyn.data.thisDep contextDep = contextDep || subDyn.data.contextDep propDep = propDep || subDyn.data.propDep } } return new Declaration( thisDep, contextDep, propDep, append) } else { return new Declaration( type === DYN_STATE$1, type === DYN_CONTEXT$1, type === DYN_PROP$1, append) } } var SCOPE_DECL = new Declaration(false, false, false, function () {}) function reglCore ( gl, stringStore, extensions, limits, bufferState, elementState, textureState, framebufferState, uniformState, attributeState, shaderState, drawState, contextState, timer, config) { var AttributeRecord = attributeState.Record var blendEquations = { 'add': 32774, 'subtract': 32778, 'reverse subtract': 32779 } if (extensions.ext_blend_minmax) { blendEquations.min = GL_MIN_EXT blendEquations.max = GL_MAX_EXT } var extInstancing = extensions.angle_instanced_arrays var extDrawBuffers = extensions.webgl_draw_buffers var extVertexArrays = extensions.oes_vertex_array_object // =================================================== // =================================================== // WEBGL STATE // =================================================== // =================================================== var currentState = { dirty: true, profile: config.profile } var nextState = {} var GL_STATE_NAMES = [] var GL_FLAGS = {} var GL_VARIABLES = {} function propName (name) { return name.replace('.', '_') } function stateFlag (sname, cap, init) { var name = propName(sname) GL_STATE_NAMES.push(sname) nextState[name] = currentState[name] = !!init GL_FLAGS[name] = cap } function stateVariable (sname, func, init) { var name = propName(sname) GL_STATE_NAMES.push(sname) if (Array.isArray(init)) { currentState[name] = init.slice() nextState[name] = init.slice() } else { currentState[name] = nextState[name] = init } GL_VARIABLES[name] = func } // Dithering stateFlag(S_DITHER, GL_DITHER) // Blending stateFlag(S_BLEND_ENABLE, GL_BLEND) stateVariable(S_BLEND_COLOR, 'blendColor', [0, 0, 0, 0]) stateVariable(S_BLEND_EQUATION, 'blendEquationSeparate', [GL_FUNC_ADD, GL_FUNC_ADD]) stateVariable(S_BLEND_FUNC, 'blendFuncSeparate', [GL_ONE, GL_ZERO, GL_ONE, GL_ZERO]) // Depth stateFlag(S_DEPTH_ENABLE, GL_DEPTH_TEST, true) stateVariable(S_DEPTH_FUNC, 'depthFunc', GL_LESS) stateVariable(S_DEPTH_RANGE, 'depthRange', [0, 1]) stateVariable(S_DEPTH_MASK, 'depthMask', true) // Color mask stateVariable(S_COLOR_MASK, S_COLOR_MASK, [true, true, true, true]) // Face culling stateFlag(S_CULL_ENABLE, GL_CULL_FACE) stateVariable(S_CULL_FACE, 'cullFace', GL_BACK) // Front face orientation stateVariable(S_FRONT_FACE, S_FRONT_FACE, GL_CCW) // Line width stateVariable(S_LINE_WIDTH, S_LINE_WIDTH, 1) // Polygon offset stateFlag(S_POLYGON_OFFSET_ENABLE, GL_POLYGON_OFFSET_FILL) stateVariable(S_POLYGON_OFFSET_OFFSET, 'polygonOffset', [0, 0]) // Sample coverage stateFlag(S_SAMPLE_ALPHA, GL_SAMPLE_ALPHA_TO_COVERAGE) stateFlag(S_SAMPLE_ENABLE, GL_SAMPLE_COVERAGE) stateVariable(S_SAMPLE_COVERAGE, 'sampleCoverage', [1, false]) // Stencil stateFlag(S_STENCIL_ENABLE, GL_STENCIL_TEST) stateVariable(S_STENCIL_MASK, 'stencilMask', -1) stateVariable(S_STENCIL_FUNC, 'stencilFunc', [GL_ALWAYS, 0, -1]) stateVariable(S_STENCIL_OPFRONT, 'stencilOpSeparate', [GL_FRONT, GL_KEEP, GL_KEEP, GL_KEEP]) stateVariable(S_STENCIL_OPBACK, 'stencilOpSeparate', [GL_BACK, GL_KEEP, GL_KEEP, GL_KEEP]) // Scissor stateFlag(S_SCISSOR_ENABLE, GL_SCISSOR_TEST) stateVariable(S_SCISSOR_BOX, 'scissor', [0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight]) // Viewport stateVariable(S_VIEWPORT, S_VIEWPORT, [0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight]) // =================================================== // =================================================== // ENVIRONMENT // =================================================== // =================================================== var sharedState = { gl: gl, context: contextState, strings: stringStore, next: nextState, current: currentState, draw: drawState, elements: elementState, buffer: bufferState, shader: shaderState, attributes: attributeState.state, vao: attributeState, uniforms: uniformState, framebuffer: framebufferState, extensions: extensions, timer: timer, isBufferArgs: isBufferArgs } var sharedConstants = { primTypes: primTypes, compareFuncs: compareFuncs, blendFuncs: blendFuncs, blendEquations: blendEquations, stencilOps: stencilOps, glTypes: glTypes, orientationType: orientationType } if (extDrawBuffers) { sharedConstants.backBuffer = [GL_BACK] sharedConstants.drawBuffer = loop(limits.maxDrawbuffers, function (i) { if (i === 0) { return [0] } return loop(i, function (j) { return GL_COLOR_ATTACHMENT0$2 + j }) }) } var drawCallCounter = 0 function createREGLEnvironment () { var env = createEnvironment() var link = env.link var global = env.global env.id = drawCallCounter++ env.batchId = '0' // link shared state var SHARED = link(sharedState) var shared = env.shared = { props: 'a0' } Object.keys(sharedState).forEach(function (prop) { shared[prop] = global.def(SHARED, '.', prop) }) // Inject runtime assertion stuff for debug builds // Copy GL state variables over var nextVars = env.next = {} var currentVars = env.current = {} Object.keys(GL_VARIABLES).forEach(function (variable) { if (Array.isArray(currentState[variable])) { nextVars[variable] = global.def(shared.next, '.', variable) currentVars[variable] = global.def(shared.current, '.', variable) } }) // Initialize shared constants var constants = env.constants = {} Object.keys(sharedConstants).forEach(function (name) { constants[name] = global.def(JSON.stringify(sharedConstants[name])) }) // Helper function for calling a block env.invoke = function (block, x) { switch (x.type) { case DYN_FUNC$1: var argList = [ 'this', shared.context, shared.props, env.batchId ] return block.def( link(x.data), '.call(', argList.slice(0, Math.max(x.data.length + 1, 4)), ')') case DYN_PROP$1: return block.def(shared.props, x.data) case DYN_CONTEXT$1: return block.def(shared.context, x.data) case DYN_STATE$1: return block.def('this', x.data) case DYN_THUNK: x.data.append(env, block) return x.data.ref case DYN_CONSTANT$1: return x.data.toString() case DYN_ARRAY$1: return x.data.map(function (y) { return env.invoke(block, y) }) } } env.attribCache = {} var scopeAttribs = {} env.scopeAttrib = function (name) { var id = stringStore.id(name) if (id in scopeAttribs) { return scopeAttribs[id] } var binding = attributeState.scope[id] if (!binding) { binding = attributeState.scope[id] = new AttributeRecord() } var result = scopeAttribs[id] = link(binding) return result } return env } // =================================================== // =================================================== // PARSING // =================================================== // =================================================== function parseProfile (options) { var staticOptions = options.static var dynamicOptions = options.dynamic var profileEnable if (S_PROFILE in staticOptions) { var value = !!staticOptions[S_PROFILE] profileEnable = createStaticDecl(function (env, scope) { return value }) profileEnable.enable = value } else if (S_PROFILE in dynamicOptions) { var dyn = dynamicOptions[S_PROFILE] profileEnable = createDynamicDecl(dyn, function (env, scope) { return env.invoke(scope, dyn) }) } return profileEnable } function parseFramebuffer (options, env) { var staticOptions = options.static var dynamicOptions = options.dynamic if (S_FRAMEBUFFER in staticOptions) { var framebuffer = staticOptions[S_FRAMEBUFFER] if (framebuffer) { framebuffer = framebufferState.getFramebuffer(framebuffer) return createStaticDecl(function (env, block) { var FRAMEBUFFER = env.link(framebuffer) var shared = env.shared block.set( shared.framebuffer, '.next', FRAMEBUFFER) var CONTEXT = shared.context block.set( CONTEXT, '.' + S_FRAMEBUFFER_WIDTH, FRAMEBUFFER + '.width') block.set( CONTEXT, '.' + S_FRAMEBUFFER_HEIGHT, FRAMEBUFFER + '.height') return FRAMEBUFFER }) } else { return createStaticDecl(function (env, scope) { var shared = env.shared scope.set( shared.framebuffer, '.next', 'null') var CONTEXT = shared.context scope.set( CONTEXT, '.' + S_FRAMEBUFFER_WIDTH, CONTEXT + '.' + S_DRAWINGBUFFER_WIDTH) scope.set( CONTEXT, '.' + S_FRAMEBUFFER_HEIGHT, CONTEXT + '.' + S_DRAWINGBUFFER_HEIGHT) return 'null' }) } } else if (S_FRAMEBUFFER in dynamicOptions) { var dyn = dynamicOptions[S_FRAMEBUFFER] return createDynamicDecl(dyn, function (env, scope) { var FRAMEBUFFER_FUNC = env.invoke(scope, dyn) var shared = env.shared var FRAMEBUFFER_STATE = shared.framebuffer var FRAMEBUFFER = scope.def( FRAMEBUFFER_STATE, '.getFramebuffer(', FRAMEBUFFER_FUNC, ')') scope.set( FRAMEBUFFER_STATE, '.next', FRAMEBUFFER) var CONTEXT = shared.context scope.set( CONTEXT, '.' + S_FRAMEBUFFER_WIDTH, FRAMEBUFFER + '?' + FRAMEBUFFER + '.width:' + CONTEXT + '.' + S_DRAWINGBUFFER_WIDTH) scope.set( CONTEXT, '.' + S_FRAMEBUFFER_HEIGHT, FRAMEBUFFER + '?' + FRAMEBUFFER + '.height:' + CONTEXT + '.' + S_DRAWINGBUFFER_HEIGHT) return FRAMEBUFFER }) } else { return null } } function parseViewportScissor (options, framebuffer, env) { var staticOptions = options.static var dynamicOptions = options.dynamic function parseBox (param) { if (param in staticOptions) { var box = staticOptions[param] var isStatic = true var x = box.x | 0 var y = box.y | 0 var w, h if ('width' in box) { w = box.width | 0 } else { isStatic = false } if ('height' in box) { h = box.height | 0 } else { isStatic = false } return new Declaration( !isStatic && framebuffer && framebuffer.thisDep, !isStatic && framebuffer && framebuffer.contextDep, !isStatic && framebuffer && framebuffer.propDep, function (env, scope) { var CONTEXT = env.shared.context var BOX_W = w if (!('width' in box)) { BOX_W = scope.def(CONTEXT, '.', S_FRAMEBUFFER_WIDTH, '-', x) } var BOX_H = h if (!('height' in box)) { BOX_H = scope.def(CONTEXT, '.', S_FRAMEBUFFER_HEIGHT, '-', y) } return [x, y, BOX_W, BOX_H] }) } else if (param in dynamicOptions) { var dynBox = dynamicOptions[param] var result = createDynamicDecl(dynBox, function (env, scope) { var BOX = env.invoke(scope, dynBox) var CONTEXT = env.shared.context var BOX_X = scope.def(BOX, '.x|0') var BOX_Y = scope.def(BOX, '.y|0') var BOX_W = scope.def( '"width" in ', BOX, '?', BOX, '.width|0:', '(', CONTEXT, '.', S_FRAMEBUFFER_WIDTH, '-', BOX_X, ')') var BOX_H = scope.def( '"height" in ', BOX, '?', BOX, '.height|0:', '(', CONTEXT, '.', S_FRAMEBUFFER_HEIGHT, '-', BOX_Y, ')') return [BOX_X, BOX_Y, BOX_W, BOX_H] }) if (framebuffer) { result.thisDep = result.thisDep || framebuffer.thisDep result.contextDep = result.contextDep || framebuffer.contextDep result.propDep = result.propDep || framebuffer.propDep } return result } else if (framebuffer) { return new Declaration( framebuffer.thisDep, framebuffer.contextDep, framebuffer.propDep, function (env, scope) { var CONTEXT = env.shared.context return [ 0, 0, scope.def(CONTEXT, '.', S_FRAMEBUFFER_WIDTH), scope.def(CONTEXT, '.', S_FRAMEBUFFER_HEIGHT)] }) } else { return null } } var viewport = parseBox(S_VIEWPORT) if (viewport) { var prevViewport = viewport viewport = new Declaration( viewport.thisDep, viewport.contextDep, viewport.propDep, function (env, scope) { var VIEWPORT = prevViewport.append(env, scope) var CONTEXT = env.shared.context scope.set( CONTEXT, '.' + S_VIEWPORT_WIDTH, VIEWPORT[2]) scope.set( CONTEXT, '.' + S_VIEWPORT_HEIGHT, VIEWPORT[3]) return VIEWPORT }) } return { viewport: viewport, scissor_box: parseBox(S_SCISSOR_BOX) } } function parseAttribLocations (options, attributes) { var staticOptions = options.static var staticProgram = typeof staticOptions[S_FRAG] === 'string' && typeof staticOptions[S_VERT] === 'string' if (staticProgram) { if (Object.keys(attributes.dynamic).length > 0) { return null } var staticAttributes = attributes.static var sAttributes = Object.keys(staticAttributes) if (sAttributes.length > 0 && typeof staticAttributes[sAttributes[0]] === 'number') { var bindings = [] for (var i = 0; i < sAttributes.length; ++i) { bindings.push([staticAttributes[sAttributes[i]] | 0, sAttributes[i]]) } return bindings } } return null } function parseProgram (options, env, attribLocations) { var staticOptions = options.static var dynamicOptions = options.dynamic function parseShader (name) { if (name in staticOptions) { var id = stringStore.id(staticOptions[name]) var result = createStaticDecl(function () { return id }) result.id = id return result } else if (name in dynamicOptions) { var dyn = dynamicOptions[name] return createDynamicDecl(dyn, function (env, scope) { var str = env.invoke(scope, dyn) var id = scope.def(env.shared.strings, '.id(', str, ')') return id }) } return null } var frag = parseShader(S_FRAG) var vert = parseShader(S_VERT) var program = null var progVar if (isStatic(frag) && isStatic(vert)) { program = shaderState.program(vert.id, frag.id, null, attribLocations) progVar = createStaticDecl(function (env, scope) { return env.link(program) }) } else { progVar = new Declaration( (frag && frag.thisDep) || (vert && vert.thisDep), (frag && frag.contextDep) || (vert && vert.contextDep), (frag && frag.propDep) || (vert && vert.propDep), function (env, scope) { var SHADER_STATE = env.shared.shader var fragId if (frag) { fragId = frag.append(env, scope) } else { fragId = scope.def(SHADER_STATE, '.', S_FRAG) } var vertId if (vert) { vertId = vert.append(env, scope) } else { vertId = scope.def(SHADER_STATE, '.', S_VERT) } var progDef = SHADER_STATE + '.program(' + vertId + ',' + fragId return scope.def(progDef + ')') }) } return { frag: frag, vert: vert, progVar: progVar, program: program } } function parseDraw (options, env) { var staticOptions = options.static var dynamicOptions = options.dynamic // TODO: should use VAO to get default values for offset properties // should move vao parse into here and out of the old stuff var staticDraw = {} var vaoActive = false function parseVAO () { if (S_VAO in staticOptions) { var vao = staticOptions[S_VAO] if (vao !== null && attributeState.getVAO(vao) === null) { vao = attributeState.createVAO(vao) } vaoActive = true staticDraw.vao = vao return createStaticDecl(function (env) { var vaoRef = attributeState.getVAO(vao) if (vaoRef) { return env.link(vaoRef) } else { return 'null' } }) } else if (S_VAO in dynamicOptions) { vaoActive = true var dyn = dynamicOptions[S_VAO] return createDynamicDecl(dyn, function (env, scope) { var vaoRef = env.invoke(scope, dyn) return scope.def(env.shared.vao + '.getVAO(' + vaoRef + ')') }) } return null } var vao = parseVAO() var elementsActive = false function parseElements () { if (S_ELEMENTS in staticOptions) { var elements = staticOptions[S_ELEMENTS] staticDraw.elements = elements if (isBufferArgs(elements)) { var e = staticDraw.elements = elementState.create(elements, true) elements = elementState.getElements(e) elementsActive = true } else if (elements) { elements = elementState.getElements(elements) elementsActive = true } var result = createStaticDecl(function (env, scope) { if (elements) { var result = env.link(elements) env.ELEMENTS = result return result } env.ELEMENTS = null return null }) result.value = elements return result } else if (S_ELEMENTS in dynamicOptions) { elementsActive = true var dyn = dynamicOptions[S_ELEMENTS] return createDynamicDecl(dyn, function (env, scope) { var shared = env.shared var IS_BUFFER_ARGS = shared.isBufferArgs var ELEMENT_STATE = shared.elements var elementDefn = env.invoke(scope, dyn) var elements = scope.def('null') var elementStream = scope.def(IS_BUFFER_ARGS, '(', elementDefn, ')') var ifte = env.cond(elementStream) .then(elements, '=', ELEMENT_STATE, '.createStream(', elementDefn, ');') .else(elements, '=', ELEMENT_STATE, '.getElements(', elementDefn, ');') scope.entry(ifte) scope.exit( env.cond(elementStream) .then(ELEMENT_STATE, '.destroyStream(', elements, ');')) env.ELEMENTS = elements return elements }) } else if (vaoActive) { return new Declaration( vao.thisDep, vao.contextDep, vao.propDep, function (env, scope) { return scope.def(env.shared.vao + '.currentVAO?' + env.shared.elements + '.getElements(' + env.shared.vao + '.currentVAO.elements):null') }) } return null } var elements = parseElements() function parsePrimitive () { if (S_PRIMITIVE in staticOptions) { var primitive = staticOptions[S_PRIMITIVE] staticDraw.primitive = primitive return createStaticDecl(function (env, scope) { return primTypes[primitive] }) } else if (S_PRIMITIVE in dynamicOptions) { var dynPrimitive = dynamicOptions[S_PRIMITIVE] return createDynamicDecl(dynPrimitive, function (env, scope) { var PRIM_TYPES = env.constants.primTypes var prim = env.invoke(scope, dynPrimitive) return scope.def(PRIM_TYPES, '[', prim, ']') }) } else if (elementsActive) { if (isStatic(elements)) { if (elements.value) { return createStaticDecl(function (env, scope) { return scope.def(env.ELEMENTS, '.primType') }) } else { return createStaticDecl(function () { return GL_TRIANGLES$1 }) } } else { return new Declaration( elements.thisDep, elements.contextDep, elements.propDep, function (env, scope) { var elements = env.ELEMENTS return scope.def(elements, '?', elements, '.primType:', GL_TRIANGLES$1) }) } } else if (vaoActive) { return new Declaration( vao.thisDep, vao.contextDep, vao.propDep, function (env, scope) { return scope.def(env.shared.vao + '.currentVAO?' + env.shared.vao + '.currentVAO.primitive:' + GL_TRIANGLES$1) }) } return null } function parseParam (param, isOffset) { if (param in staticOptions) { var value = staticOptions[param] | 0 if (isOffset) { staticDraw.offset = value } else { staticDraw.instances = value } return createStaticDecl(function (env, scope) { if (isOffset) { env.OFFSET = value } return value }) } else if (param in dynamicOptions) { var dynValue = dynamicOptions[param] return createDynamicDecl(dynValue, function (env, scope) { var result = env.invoke(scope, dynValue) if (isOffset) { env.OFFSET = result } return result }) } else if (isOffset) { if (elementsActive) { return createStaticDecl(function (env, scope) { env.OFFSET = 0 return 0 }) } else if (vaoActive) { return new Declaration( vao.thisDep, vao.contextDep, vao.propDep, function (env, scope) { return scope.def(env.shared.vao + '.currentVAO?' + env.shared.vao + '.currentVAO.offset:0') }) } } else if (vaoActive) { return new Declaration( vao.thisDep, vao.contextDep, vao.propDep, function (env, scope) { return scope.def(env.shared.vao + '.currentVAO?' + env.shared.vao + '.currentVAO.instances:-1') }) } return null } var OFFSET = parseParam(S_OFFSET, true) function parseVertCount () { if (S_COUNT in staticOptions) { var count = staticOptions[S_COUNT] | 0 staticDraw.count = count return createStaticDecl(function () { return count }) } else if (S_COUNT in dynamicOptions) { var dynCount = dynamicOptions[S_COUNT] return createDynamicDecl(dynCount, function (env, scope) { var result = env.invoke(scope, dynCount) return result }) } else if (elementsActive) { if (isStatic(elements)) { if (elements) { if (OFFSET) { return new Declaration( OFFSET.thisDep, OFFSET.contextDep, OFFSET.propDep, function (env, scope) { var result = scope.def( env.ELEMENTS, '.vertCount-', env.OFFSET) return result }) } else { return createStaticDecl(function (env, scope) { return scope.def(env.ELEMENTS, '.vertCount') }) } } else { var result = createStaticDecl(function () { return -1 }) return result } } else { var variable = new Declaration( elements.thisDep || OFFSET.thisDep, elements.contextDep || OFFSET.contextDep, elements.propDep || OFFSET.propDep, function (env, scope) { var elements = env.ELEMENTS if (env.OFFSET) { return scope.def(elements, '?', elements, '.vertCount-', env.OFFSET, ':-1') } return scope.def(elements, '?', elements, '.vertCount:-1') }) return variable } } else if (vaoActive) { var countVariable = new Declaration( vao.thisDep, vao.contextDep, vao.propDep, function (env, scope) { return scope.def(env.shared.vao, '.currentVAO?', env.shared.vao, '.currentVAO.count:-1') }) return countVariable } return null } var primitive = parsePrimitive() var count = parseVertCount() var instances = parseParam(S_INSTANCES, false) return { elements: elements, primitive: primitive, count: count, instances: instances, offset: OFFSET, vao: vao, vaoActive: vaoActive, elementsActive: elementsActive, // static draw props static: staticDraw } } function parseGLState (options, env) { var staticOptions = options.static var dynamicOptions = options.dynamic var STATE = {} GL_STATE_NAMES.forEach(function (prop) { var param = propName(prop) function parseParam (parseStatic, parseDynamic) { if (prop in staticOptions) { var value = parseStatic(staticOptions[prop]) STATE[param] = createStaticDecl(function () { return value }) } else if (prop in dynamicOptions) { var dyn = dynamicOptions[prop] STATE[param] = createDynamicDecl(dyn, function (env, scope) { return parseDynamic(env, scope, env.invoke(scope, dyn)) }) } } switch (prop) { case S_CULL_ENABLE: case S_BLEND_ENABLE: case S_DITHER: case S_STENCIL_ENABLE: case S_DEPTH_ENABLE: case S_SCISSOR_ENABLE: case S_POLYGON_OFFSET_ENABLE: case S_SAMPLE_ALPHA: case S_SAMPLE_ENABLE: case S_DEPTH_MASK: return parseParam( function (value) { return value }, function (env, scope, value) { return value }) case S_DEPTH_FUNC: return parseParam( function (value) { return compareFuncs[value] }, function (env, scope, value) { var COMPARE_FUNCS = env.constants.compareFuncs return scope.def(COMPARE_FUNCS, '[', value, ']') }) case S_DEPTH_RANGE: return parseParam( function (value) { return value }, function (env, scope, value) { var Z_NEAR = scope.def('+', value, '[0]') var Z_FAR = scope.def('+', value, '[1]') return [Z_NEAR, Z_FAR] }) case S_BLEND_FUNC: return parseParam( function (value) { var srcRGB = ('srcRGB' in value ? value.srcRGB : value.src) var srcAlpha = ('srcAlpha' in value ? value.srcAlpha : value.src) var dstRGB = ('dstRGB' in value ? value.dstRGB : value.dst) var dstAlpha = ('dstAlpha' in value ? value.dstAlpha : value.dst) return [ blendFuncs[srcRGB], blendFuncs[dstRGB], blendFuncs[srcAlpha], blendFuncs[dstAlpha] ] }, function (env, scope, value) { var BLEND_FUNCS = env.constants.blendFuncs function read (prefix, suffix) { var func = scope.def( '"', prefix, suffix, '" in ', value, '?', value, '.', prefix, suffix, ':', value, '.', prefix) return func } var srcRGB = read('src', 'RGB') var dstRGB = read('dst', 'RGB') var SRC_RGB = scope.def(BLEND_FUNCS, '[', srcRGB, ']') var SRC_ALPHA = scope.def(BLEND_FUNCS, '[', read('src', 'Alpha'), ']') var DST_RGB = scope.def(BLEND_FUNCS, '[', dstRGB, ']') var DST_ALPHA = scope.def(BLEND_FUNCS, '[', read('dst', 'Alpha'), ']') return [SRC_RGB, DST_RGB, SRC_ALPHA, DST_ALPHA] }) case S_BLEND_EQUATION: return parseParam( function (value) { if (typeof value === 'string') { return [ blendEquations[value], blendEquations[value] ] } else if (typeof value === 'object') { return [ blendEquations[value.rgb], blendEquations[value.alpha] ] } else { } }, function (env, scope, value) { var BLEND_EQUATIONS = env.constants.blendEquations var RGB = scope.def() var ALPHA = scope.def() var ifte = env.cond('typeof ', value, '==="string"') ifte.then( RGB, '=', ALPHA, '=', BLEND_EQUATIONS, '[', value, '];') ifte.else( RGB, '=', BLEND_EQUATIONS, '[', value, '.rgb];', ALPHA, '=', BLEND_EQUATIONS, '[', value, '.alpha];') scope(ifte) return [RGB, ALPHA] }) case S_BLEND_COLOR: return parseParam( function (value) { return loop(4, function (i) { return +value[i] }) }, function (env, scope, value) { return loop(4, function (i) { return scope.def('+', value, '[', i, ']') }) }) case S_STENCIL_MASK: return parseParam( function (value) { return value | 0 }, function (env, scope, value) { return scope.def(value, '|0') }) case S_STENCIL_FUNC: return parseParam( function (value) { var cmp = value.cmp || 'keep' var ref = value.ref || 0 var mask = 'mask' in value ? value.mask : -1 return [ compareFuncs[cmp], ref, mask ] }, function (env, scope, value) { var COMPARE_FUNCS = env.constants.compareFuncs var cmp = scope.def( '"cmp" in ', value, '?', COMPARE_FUNCS, '[', value, '.cmp]', ':', GL_KEEP) var ref = scope.def(value, '.ref|0') var mask = scope.def( '"mask" in ', value, '?', value, '.mask|0:-1') return [cmp, ref, mask] }) case S_STENCIL_OPFRONT: case S_STENCIL_OPBACK: return parseParam( function (value) { var fail = value.fail || 'keep' var zfail = value.zfail || 'keep' var zpass = value.zpass || 'keep' return [ prop === S_STENCIL_OPBACK ? GL_BACK : GL_FRONT, stencilOps[fail], stencilOps[zfail], stencilOps[zpass] ] }, function (env, scope, value) { var STENCIL_OPS = env.constants.stencilOps function read (name) { return scope.def( '"', name, '" in ', value, '?', STENCIL_OPS, '[', value, '.', name, ']:', GL_KEEP) } return [ prop === S_STENCIL_OPBACK ? GL_BACK : GL_FRONT, read('fail'), read('zfail'), read('zpass') ] }) case S_POLYGON_OFFSET_OFFSET: return parseParam( function (value) { var factor = value.factor | 0 var units = value.units | 0 return [factor, units] }, function (env, scope, value) { var FACTOR = scope.def(value, '.factor|0') var UNITS = scope.def(value, '.units|0') return [FACTOR, UNITS] }) case S_CULL_FACE: return parseParam( function (value) { var face = 0 if (value === 'front') { face = GL_FRONT } else if (value === 'back') { face = GL_BACK } return face }, function (env, scope, value) { return scope.def(value, '==="front"?', GL_FRONT, ':', GL_BACK) }) case S_LINE_WIDTH: return parseParam( function (value) { return value }, function (env, scope, value) { return value }) case S_FRONT_FACE: return parseParam( function (value) { return orientationType[value] }, function (env, scope, value) { return scope.def(value + '==="cw"?' + GL_CW + ':' + GL_CCW) }) case S_COLOR_MASK: return parseParam( function (value) { return value.map(function (v) { return !!v }) }, function (env, scope, value) { return loop(4, function (i) { return '!!' + value + '[' + i + ']' }) }) case S_SAMPLE_COVERAGE: return parseParam( function (value) { var sampleValue = 'value' in value ? value.value : 1 var sampleInvert = !!value.invert return [sampleValue, sampleInvert] }, function (env, scope, value) { var VALUE = scope.def( '"value" in ', value, '?+', value, '.value:1') var INVERT = scope.def('!!', value, '.invert') return [VALUE, INVERT] }) } }) return STATE } function parseUniforms (uniforms, env) { var staticUniforms = uniforms.static var dynamicUniforms = uniforms.dynamic var UNIFORMS = {} Object.keys(staticUniforms).forEach(function (name) { var value = staticUniforms[name] var result if (typeof value === 'number' || typeof value === 'boolean') { result = createStaticDecl(function () { return value }) } else if (typeof value === 'function') { var reglType = value._reglType if (reglType === 'texture2d' || reglType === 'textureCube') { result = createStaticDecl(function (env) { return env.link(value) }) } else if (reglType === 'framebuffer' || reglType === 'framebufferCube') { result = createStaticDecl(function (env) { return env.link(value.color[0]) }) } else { } } else if (isArrayLike(value)) { result = createStaticDecl(function (env) { var ITEM = env.global.def('[', loop(value.length, function (i) { return value[i] }), ']') return ITEM }) } else { } result.value = value UNIFORMS[name] = result }) Object.keys(dynamicUniforms).forEach(function (key) { var dyn = dynamicUniforms[key] UNIFORMS[key] = createDynamicDecl(dyn, function (env, scope) { return env.invoke(scope, dyn) }) }) return UNIFORMS } function parseAttributes (attributes, env) { var staticAttributes = attributes.static var dynamicAttributes = attributes.dynamic var attributeDefs = {} Object.keys(staticAttributes).forEach(function (attribute) { var value = staticAttributes[attribute] var id = stringStore.id(attribute) var record = new AttributeRecord() if (isBufferArgs(value)) { record.state = ATTRIB_STATE_POINTER record.buffer = bufferState.getBuffer( bufferState.create(value, GL_ARRAY_BUFFER$2, false, true)) record.type = 0 } else { var buffer = bufferState.getBuffer(value) if (buffer) { record.state = ATTRIB_STATE_POINTER record.buffer = buffer record.type = 0 } else { if ('constant' in value) { var constant = value.constant record.buffer = 'null' record.state = ATTRIB_STATE_CONSTANT if (typeof constant === 'number') { record.x = constant } else { CUTE_COMPONENTS.forEach(function (c, i) { if (i < constant.length) { record[c] = constant[i] } }) } } else { if (isBufferArgs(value.buffer)) { buffer = bufferState.getBuffer( bufferState.create(value.buffer, GL_ARRAY_BUFFER$2, false, true)) } else { buffer = bufferState.getBuffer(value.buffer) } var offset = value.offset | 0 var stride = value.stride | 0 var size = value.size | 0 var normalized = !!value.normalized var type = 0 if ('type' in value) { type = glTypes[value.type] } var divisor = value.divisor | 0 record.buffer = buffer record.state = ATTRIB_STATE_POINTER record.size = size record.normalized = normalized record.type = type || buffer.dtype record.offset = offset record.stride = stride record.divisor = divisor } } } attributeDefs[attribute] = createStaticDecl(function (env, scope) { var cache = env.attribCache if (id in cache) { return cache[id] } var result = { isStream: false } Object.keys(record).forEach(function (key) { result[key] = record[key] }) if (record.buffer) { result.buffer = env.link(record.buffer) result.type = result.type || (result.buffer + '.dtype') } cache[id] = result return result }) }) Object.keys(dynamicAttributes).forEach(function (attribute) { var dyn = dynamicAttributes[attribute] function appendAttributeCode (env, block) { var VALUE = env.invoke(block, dyn) var shared = env.shared var constants = env.constants var IS_BUFFER_ARGS = shared.isBufferArgs var BUFFER_STATE = shared.buffer // Perform validation on attribute // allocate names for result var result = { isStream: block.def(false) } var defaultRecord = new AttributeRecord() defaultRecord.state = ATTRIB_STATE_POINTER Object.keys(defaultRecord).forEach(function (key) { result[key] = block.def('' + defaultRecord[key]) }) var BUFFER = result.buffer var TYPE = result.type block( 'if(', IS_BUFFER_ARGS, '(', VALUE, ')){', result.isStream, '=true;', BUFFER, '=', BUFFER_STATE, '.createStream(', GL_ARRAY_BUFFER$2, ',', VALUE, ');', TYPE, '=', BUFFER, '.dtype;', '}else{', BUFFER, '=', BUFFER_STATE, '.getBuffer(', VALUE, ');', 'if(', BUFFER, '){', TYPE, '=', BUFFER, '.dtype;', '}else if("constant" in ', VALUE, '){', result.state, '=', ATTRIB_STATE_CONSTANT, ';', 'if(typeof ' + VALUE + '.constant === "number"){', result[CUTE_COMPONENTS[0]], '=', VALUE, '.constant;', CUTE_COMPONENTS.slice(1).map(function (n) { return result[n] }).join('='), '=0;', '}else{', CUTE_COMPONENTS.map(function (name, i) { return ( result[name] + '=' + VALUE + '.constant.length>' + i + '?' + VALUE + '.constant[' + i + ']:0;' ) }).join(''), '}}else{', 'if(', IS_BUFFER_ARGS, '(', VALUE, '.buffer)){', BUFFER, '=', BUFFER_STATE, '.createStream(', GL_ARRAY_BUFFER$2, ',', VALUE, '.buffer);', '}else{', BUFFER, '=', BUFFER_STATE, '.getBuffer(', VALUE, '.buffer);', '}', TYPE, '="type" in ', VALUE, '?', constants.glTypes, '[', VALUE, '.type]:', BUFFER, '.dtype;', result.normalized, '=!!', VALUE, '.normalized;') function emitReadRecord (name) { block(result[name], '=', VALUE, '.', name, '|0;') } emitReadRecord('size') emitReadRecord('offset') emitReadRecord('stride') emitReadRecord('divisor') block('}}') block.exit( 'if(', result.isStream, '){', BUFFER_STATE, '.destroyStream(', BUFFER, ');', '}') return result } attributeDefs[attribute] = createDynamicDecl(dyn, appendAttributeCode) }) return attributeDefs } function parseContext (context) { var staticContext = context.static var dynamicContext = context.dynamic var result = {} Object.keys(staticContext).forEach(function (name) { var value = staticContext[name] result[name] = createStaticDecl(function (env, scope) { if (typeof value === 'number' || typeof value === 'boolean') { return '' + value } else { return env.link(value) } }) }) Object.keys(dynamicContext).forEach(function (name) { var dyn = dynamicContext[name] result[name] = createDynamicDecl(dyn, function (env, scope) { return env.invoke(scope, dyn) }) }) return result } function parseArguments (options, attributes, uniforms, context, env) { var staticOptions = options.static var dynamicOptions = options.dynamic var attribLocations = parseAttribLocations(options, attributes) var framebuffer = parseFramebuffer(options, env) var viewportAndScissor = parseViewportScissor(options, framebuffer, env) var draw = parseDraw(options, env) var state = parseGLState(options, env) var shader = parseProgram(options, env, attribLocations) function copyBox (name) { var defn = viewportAndScissor[name] if (defn) { state[name] = defn } } copyBox(S_VIEWPORT) copyBox(propName(S_SCISSOR_BOX)) var dirty = Object.keys(state).length > 0 var result = { framebuffer: framebuffer, draw: draw, shader: shader, state: state, dirty: dirty, scopeVAO: null, drawVAO: null, useVAO: false, attributes: {} } result.profile = parseProfile(options, env) result.uniforms = parseUniforms(uniforms, env) result.drawVAO = result.scopeVAO = draw.vao // special case: check if we can statically allocate a vertex array object for this program if (!result.drawVAO && shader.program && !attribLocations && extensions.angle_instanced_arrays && draw.static.elements) { var useVAO = true var staticBindings = shader.program.attributes.map(function (attr) { var binding = attributes.static[attr] useVAO = useVAO && !!binding return binding }) if (useVAO && staticBindings.length > 0) { var vao = attributeState.getVAO(attributeState.createVAO({ attributes: staticBindings, elements: draw.static.elements })) result.drawVAO = new Declaration(null, null, null, function (env, scope) { return env.link(vao) }) result.useVAO = true } } if (attribLocations) { result.useVAO = true } else { result.attributes = parseAttributes(attributes, env) } result.context = parseContext(context, env) return result } // =================================================== // =================================================== // COMMON UPDATE FUNCTIONS // =================================================== // =================================================== function emitContext (env, scope, context) { var shared = env.shared var CONTEXT = shared.context var contextEnter = env.scope() Object.keys(context).forEach(function (name) { scope.save(CONTEXT, '.' + name) var defn = context[name] var value = defn.append(env, scope) if (Array.isArray(value)) { contextEnter(CONTEXT, '.', name, '=[', value.join(), '];') } else { contextEnter(CONTEXT, '.', name, '=', value, ';') } }) scope(contextEnter) } // =================================================== // =================================================== // COMMON DRAWING FUNCTIONS // =================================================== // =================================================== function emitPollFramebuffer (env, scope, framebuffer, skipCheck) { var shared = env.shared var GL = shared.gl var FRAMEBUFFER_STATE = shared.framebuffer var EXT_DRAW_BUFFERS if (extDrawBuffers) { EXT_DRAW_BUFFERS = scope.def(shared.extensions, '.webgl_draw_buffers') } var constants = env.constants var DRAW_BUFFERS = constants.drawBuffer var BACK_BUFFER = constants.backBuffer var NEXT if (framebuffer) { NEXT = framebuffer.append(env, scope) } else { NEXT = scope.def(FRAMEBUFFER_STATE, '.next') } if (!skipCheck) { scope('if(', NEXT, '!==', FRAMEBUFFER_STATE, '.cur){') } scope( 'if(', NEXT, '){', GL, '.bindFramebuffer(', GL_FRAMEBUFFER$2, ',', NEXT, '.framebuffer);') if (extDrawBuffers) { scope(EXT_DRAW_BUFFERS, '.drawBuffersWEBGL(', DRAW_BUFFERS, '[', NEXT, '.colorAttachments.length]);') } scope('}else{', GL, '.bindFramebuffer(', GL_FRAMEBUFFER$2, ',null);') if (extDrawBuffers) { scope(EXT_DRAW_BUFFERS, '.drawBuffersWEBGL(', BACK_BUFFER, ');') } scope( '}', FRAMEBUFFER_STATE, '.cur=', NEXT, ';') if (!skipCheck) { scope('}') } } function emitPollState (env, scope, args) { var shared = env.shared var GL = shared.gl var CURRENT_VARS = env.current var NEXT_VARS = env.next var CURRENT_STATE = shared.current var NEXT_STATE = shared.next var block = env.cond(CURRENT_STATE, '.dirty') GL_STATE_NAMES.forEach(function (prop) { var param = propName(prop) if (param in args.state) { return } var NEXT, CURRENT if (param in NEXT_VARS) { NEXT = NEXT_VARS[param] CURRENT = CURRENT_VARS[param] var parts = loop(currentState[param].length, function (i) { return block.def(NEXT, '[', i, ']') }) block(env.cond(parts.map(function (p, i) { return p + '!==' + CURRENT + '[' + i + ']' }).join('||')) .then( GL, '.', GL_VARIABLES[param], '(', parts, ');', parts.map(function (p, i) { return CURRENT + '[' + i + ']=' + p }).join(';'), ';')) } else { NEXT = block.def(NEXT_STATE, '.', param) var ifte = env.cond(NEXT, '!==', CURRENT_STATE, '.', param) block(ifte) if (param in GL_FLAGS) { ifte( env.cond(NEXT) .then(GL, '.enable(', GL_FLAGS[param], ');') .else(GL, '.disable(', GL_FLAGS[param], ');'), CURRENT_STATE, '.', param, '=', NEXT, ';') } else { ifte( GL, '.', GL_VARIABLES[param], '(', NEXT, ');', CURRENT_STATE, '.', param, '=', NEXT, ';') } } }) if (Object.keys(args.state).length === 0) { block(CURRENT_STATE, '.dirty=false;') } scope(block) } function emitSetOptions (env, scope, options, filter) { var shared = env.shared var CURRENT_VARS = env.current var CURRENT_STATE = shared.current var GL = shared.gl sortState(Object.keys(options)).forEach(function (param) { var defn = options[param] if (filter && !filter(defn)) { return } var variable = defn.append(env, scope) if (GL_FLAGS[param]) { var flag = GL_FLAGS[param] if (isStatic(defn)) { if (variable) { scope(GL, '.enable(', flag, ');') } else { scope(GL, '.disable(', flag, ');') } } else { scope(env.cond(variable) .then(GL, '.enable(', flag, ');') .else(GL, '.disable(', flag, ');')) } scope(CURRENT_STATE, '.', param, '=', variable, ';') } else if (isArrayLike(variable)) { var CURRENT = CURRENT_VARS[param] scope( GL, '.', GL_VARIABLES[param], '(', variable, ');', variable.map(function (v, i) { return CURRENT + '[' + i + ']=' + v }).join(';'), ';') } else { scope( GL, '.', GL_VARIABLES[param], '(', variable, ');', CURRENT_STATE, '.', param, '=', variable, ';') } }) } function injectExtensions (env, scope) { if (extInstancing) { env.instancing = scope.def( env.shared.extensions, '.angle_instanced_arrays') } } function emitProfile (env, scope, args, useScope, incrementCounter) { var shared = env.shared var STATS = env.stats var CURRENT_STATE = shared.current var TIMER = shared.timer var profileArg = args.profile function perfCounter () { if (typeof performance === 'undefined') { return 'Date.now()' } else { return 'performance.now()' } } var CPU_START, QUERY_COUNTER function emitProfileStart (block) { CPU_START = scope.def() block(CPU_START, '=', perfCounter(), ';') if (typeof incrementCounter === 'string') { block(STATS, '.count+=', incrementCounter, ';') } else { block(STATS, '.count++;') } if (timer) { if (useScope) { QUERY_COUNTER = scope.def() block(QUERY_COUNTER, '=', TIMER, '.getNumPendingQueries();') } else { block(TIMER, '.beginQuery(', STATS, ');') } } } function emitProfileEnd (block) { block(STATS, '.cpuTime+=', perfCounter(), '-', CPU_START, ';') if (timer) { if (useScope) { block(TIMER, '.pushScopeStats(', QUERY_COUNTER, ',', TIMER, '.getNumPendingQueries(),', STATS, ');') } else { block(TIMER, '.endQuery();') } } } function scopeProfile (value) { var prev = scope.def(CURRENT_STATE, '.profile') scope(CURRENT_STATE, '.profile=', value, ';') scope.exit(CURRENT_STATE, '.profile=', prev, ';') } var USE_PROFILE if (profileArg) { if (isStatic(profileArg)) { if (profileArg.enable) { emitProfileStart(scope) emitProfileEnd(scope.exit) scopeProfile('true') } else { scopeProfile('false') } return } USE_PROFILE = profileArg.append(env, scope) scopeProfile(USE_PROFILE) } else { USE_PROFILE = scope.def(CURRENT_STATE, '.profile') } var start = env.block() emitProfileStart(start) scope('if(', USE_PROFILE, '){', start, '}') var end = env.block() emitProfileEnd(end) scope.exit('if(', USE_PROFILE, '){', end, '}') } function emitAttributes (env, scope, args, attributes, filter) { var shared = env.shared function typeLength (x) { switch (x) { case GL_FLOAT_VEC2: case GL_INT_VEC2: case GL_BOOL_VEC2: return 2 case GL_FLOAT_VEC3: case GL_INT_VEC3: case GL_BOOL_VEC3: return 3 case GL_FLOAT_VEC4: case GL_INT_VEC4: case GL_BOOL_VEC4: return 4 default: return 1 } } function emitBindAttribute (ATTRIBUTE, size, record) { var GL = shared.gl var LOCATION = scope.def(ATTRIBUTE, '.location') var BINDING = scope.def(shared.attributes, '[', LOCATION, ']') var STATE = record.state var BUFFER = record.buffer var CONST_COMPONENTS = [ record.x, record.y, record.z, record.w ] var COMMON_KEYS = [ 'buffer', 'normalized', 'offset', 'stride' ] function emitBuffer () { scope( 'if(!', BINDING, '.buffer){', GL, '.enableVertexAttribArray(', LOCATION, ');}') var TYPE = record.type var SIZE if (!record.size) { SIZE = size } else { SIZE = scope.def(record.size, '||', size) } scope('if(', BINDING, '.type!==', TYPE, '||', BINDING, '.size!==', SIZE, '||', COMMON_KEYS.map(function (key) { return BINDING + '.' + key + '!==' + record[key] }).join('||'), '){', GL, '.bindBuffer(', GL_ARRAY_BUFFER$2, ',', BUFFER, '.buffer);', GL, '.vertexAttribPointer(', [ LOCATION, SIZE, TYPE, record.normalized, record.stride, record.offset ], ');', BINDING, '.type=', TYPE, ';', BINDING, '.size=', SIZE, ';', COMMON_KEYS.map(function (key) { return BINDING + '.' + key + '=' + record[key] + ';' }).join(''), '}') if (extInstancing) { var DIVISOR = record.divisor scope( 'if(', BINDING, '.divisor!==', DIVISOR, '){', env.instancing, '.vertexAttribDivisorANGLE(', [LOCATION, DIVISOR], ');', BINDING, '.divisor=', DIVISOR, ';}') } } function emitConstant () { scope( 'if(', BINDING, '.buffer){', GL, '.disableVertexAttribArray(', LOCATION, ');', BINDING, '.buffer=null;', '}if(', CUTE_COMPONENTS.map(function (c, i) { return BINDING + '.' + c + '!==' + CONST_COMPONENTS[i] }).join('||'), '){', GL, '.vertexAttrib4f(', LOCATION, ',', CONST_COMPONENTS, ');', CUTE_COMPONENTS.map(function (c, i) { return BINDING + '.' + c + '=' + CONST_COMPONENTS[i] + ';' }).join(''), '}') } if (STATE === ATTRIB_STATE_POINTER) { emitBuffer() } else if (STATE === ATTRIB_STATE_CONSTANT) { emitConstant() } else { scope('if(', STATE, '===', ATTRIB_STATE_POINTER, '){') emitBuffer() scope('}else{') emitConstant() scope('}') } } attributes.forEach(function (attribute) { var name = attribute.name var arg = args.attributes[name] var record if (arg) { if (!filter(arg)) { return } record = arg.append(env, scope) } else { if (!filter(SCOPE_DECL)) { return } var scopeAttrib = env.scopeAttrib(name) record = {} Object.keys(new AttributeRecord()).forEach(function (key) { record[key] = scope.def(scopeAttrib, '.', key) }) } emitBindAttribute( env.link(attribute), typeLength(attribute.info.type), record) }) } function emitUniforms (env, scope, args, uniforms, filter, isBatchInnerLoop) { var shared = env.shared var GL = shared.gl var definedArrUniforms = {} var infix for (var i = 0; i < uniforms.length; ++i) { var uniform = uniforms[i] var name = uniform.name var type = uniform.info.type var size = uniform.info.size var arg = args.uniforms[name] if (size > 1) { // either foo[n] or foos, avoid define both if (!arg) { continue } var arrUniformName = name.replace('[0]', '') if (definedArrUniforms[arrUniformName]) { continue } definedArrUniforms[arrUniformName] = 1 } var UNIFORM = env.link(uniform) var LOCATION = UNIFORM + '.location' var VALUE if (arg) { if (!filter(arg)) { continue } if (isStatic(arg)) { var value = arg.value if (type === GL_SAMPLER_2D || type === GL_SAMPLER_CUBE) { var TEX_VALUE = env.link(value._texture || value.color[0]._texture) scope(GL, '.uniform1i(', LOCATION, ',', TEX_VALUE + '.bind());') scope.exit(TEX_VALUE, '.unbind();') } else if ( type === GL_FLOAT_MAT2 || type === GL_FLOAT_MAT3 || type === GL_FLOAT_MAT4) { var MAT_VALUE = env.global.def('new Float32Array([' + Array.prototype.slice.call(value) + '])') var dim = 2 if (type === GL_FLOAT_MAT3) { dim = 3 } else if (type === GL_FLOAT_MAT4) { dim = 4 } scope( GL, '.uniformMatrix', dim, 'fv(', LOCATION, ',false,', MAT_VALUE, ');') } else { switch (type) { case GL_FLOAT$7: if (size === 1) { } else { } infix = '1f' break case GL_FLOAT_VEC2: infix = '2f' break case GL_FLOAT_VEC3: infix = '3f' break case GL_FLOAT_VEC4: infix = '4f' break case GL_BOOL: if (size === 1) { } else { } infix = '1i' break case GL_INT$2: if (size === 1) { } else { } infix = '1i' break case GL_BOOL_VEC2: infix = '2i' break case GL_INT_VEC2: infix = '2i' break case GL_BOOL_VEC3: infix = '3i' break case GL_INT_VEC3: infix = '3i' break case GL_BOOL_VEC4: infix = '4i' break case GL_INT_VEC4: infix = '4i' break } if (size > 1) { infix += 'v' value = env.global.def('[' + Array.prototype.slice.call(value) + ']') } else { value = isArrayLike(value) ? Array.prototype.slice.call(value) : value } scope(GL, '.uniform', infix, '(', LOCATION, ',', value, ');') } continue } else { VALUE = arg.append(env, scope) } } else { if (!filter(SCOPE_DECL)) { continue } VALUE = scope.def(shared.uniforms, '[', stringStore.id(name), ']') } if (type === GL_SAMPLER_2D) { scope( 'if(', VALUE, '&&', VALUE, '._reglType==="framebuffer"){', VALUE, '=', VALUE, '.color[0];', '}') } else if (type === GL_SAMPLER_CUBE) { scope( 'if(', VALUE, '&&', VALUE, '._reglType==="framebufferCube"){', VALUE, '=', VALUE, '.color[0];', '}') } // perform type validation var unroll = 1 switch (type) { case GL_SAMPLER_2D: case GL_SAMPLER_CUBE: var TEX = scope.def(VALUE, '._texture') scope(GL, '.uniform1i(', LOCATION, ',', TEX, '.bind());') scope.exit(TEX, '.unbind();') continue case GL_INT$2: case GL_BOOL: infix = '1i' break case GL_INT_VEC2: case GL_BOOL_VEC2: infix = '2i' unroll = 2 break case GL_INT_VEC3: case GL_BOOL_VEC3: infix = '3i' unroll = 3 break case GL_INT_VEC4: case GL_BOOL_VEC4: infix = '4i' unroll = 4 break case GL_FLOAT$7: infix = '1f' break case GL_FLOAT_VEC2: infix = '2f' unroll = 2 break case GL_FLOAT_VEC3: infix = '3f' unroll = 3 break case GL_FLOAT_VEC4: infix = '4f' unroll = 4 break case GL_FLOAT_MAT2: infix = 'Matrix2fv' break case GL_FLOAT_MAT3: infix = 'Matrix3fv' break case GL_FLOAT_MAT4: infix = 'Matrix4fv' break } if (infix.indexOf('Matrix') === -1 && size > 1) { infix += 'v' unroll = 1 } if (infix.charAt(0) === 'M') { scope(GL, '.uniform', infix, '(', LOCATION, ',') var matSize = Math.pow(type - GL_FLOAT_MAT2 + 2, 2) var STORAGE = env.global.def('new Float32Array(', matSize, ')') if (Array.isArray(VALUE)) { scope( 'false,(', loop(matSize, function (i) { return STORAGE + '[' + i + ']=' + VALUE[i] }), ',', STORAGE, ')') } else { scope( 'false,(Array.isArray(', VALUE, ')||', VALUE, ' instanceof Float32Array)?', VALUE, ':(', loop(matSize, function (i) { return STORAGE + '[' + i + ']=' + VALUE + '[' + i + ']' }), ',', STORAGE, ')') } scope(');') } else if (unroll > 1) { var prev = [] var cur = [] for (var j = 0; j < unroll; ++j) { if (Array.isArray(VALUE)) { cur.push(VALUE[j]) } else { cur.push(scope.def(VALUE + '[' + j + ']')) } if (isBatchInnerLoop) { prev.push(scope.def()) } } if (isBatchInnerLoop) { scope('if(!', env.batchId, '||', prev.map(function (p, i) { return p + '!==' + cur[i] }).join('||'), '){', prev.map(function (p, i) { return p + '=' + cur[i] + ';' }).join('')) } scope(GL, '.uniform', infix, '(', LOCATION, ',', cur.join(','), ');') if (isBatchInnerLoop) { scope('}') } } else { if (isBatchInnerLoop) { var prevS = scope.def() scope('if(!', env.batchId, '||', prevS, '!==', VALUE, '){', prevS, '=', VALUE, ';') } scope(GL, '.uniform', infix, '(', LOCATION, ',', VALUE, ');') if (isBatchInnerLoop) { scope('}') } } } } function emitDraw (env, outer, inner, args) { var shared = env.shared var GL = shared.gl var DRAW_STATE = shared.draw var drawOptions = args.draw function emitElements () { var defn = drawOptions.elements var ELEMENTS var scope = outer if (defn) { if ((defn.contextDep && args.contextDynamic) || defn.propDep) { scope = inner } ELEMENTS = defn.append(env, scope) if (drawOptions.elementsActive) { scope( 'if(' + ELEMENTS + ')' + GL + '.bindBuffer(' + GL_ELEMENT_ARRAY_BUFFER$2 + ',' + ELEMENTS + '.buffer.buffer);') } } else { ELEMENTS = scope.def() scope( ELEMENTS, '=', DRAW_STATE, '.', S_ELEMENTS, ';', 'if(', ELEMENTS, '){', GL, '.bindBuffer(', GL_ELEMENT_ARRAY_BUFFER$2, ',', ELEMENTS, '.buffer.buffer);}', 'else if(', shared.vao, '.currentVAO){', ELEMENTS, '=', env.shared.elements + '.getElements(' + shared.vao, '.currentVAO.elements);', (!extVertexArrays ? 'if(' + ELEMENTS + ')' + GL + '.bindBuffer(' + GL_ELEMENT_ARRAY_BUFFER$2 + ',' + ELEMENTS + '.buffer.buffer);' : ''), '}') } return ELEMENTS } function emitCount () { var defn = drawOptions.count var COUNT var scope = outer if (defn) { if ((defn.contextDep && args.contextDynamic) || defn.propDep) { scope = inner } COUNT = defn.append(env, scope) } else { COUNT = scope.def(DRAW_STATE, '.', S_COUNT) } return COUNT } var ELEMENTS = emitElements() function emitValue (name) { var defn = drawOptions[name] if (defn) { if ((defn.contextDep && args.contextDynamic) || defn.propDep) { return defn.append(env, inner) } else { return defn.append(env, outer) } } else { return outer.def(DRAW_STATE, '.', name) } } var PRIMITIVE = emitValue(S_PRIMITIVE) var OFFSET = emitValue(S_OFFSET) var COUNT = emitCount() if (typeof COUNT === 'number') { if (COUNT === 0) { return } } else { inner('if(', COUNT, '){') inner.exit('}') } var INSTANCES, EXT_INSTANCING if (extInstancing) { INSTANCES = emitValue(S_INSTANCES) EXT_INSTANCING = env.instancing } var ELEMENT_TYPE = ELEMENTS + '.type' var elementsStatic = drawOptions.elements && isStatic(drawOptions.elements) && !drawOptions.vaoActive function emitInstancing () { function drawElements () { inner(EXT_INSTANCING, '.drawElementsInstancedANGLE(', [ PRIMITIVE, COUNT, ELEMENT_TYPE, OFFSET + '<<((' + ELEMENT_TYPE + '-' + GL_UNSIGNED_BYTE$7 + ')>>1)', INSTANCES ], ');') } function drawArrays () { inner(EXT_INSTANCING, '.drawArraysInstancedANGLE(', [PRIMITIVE, OFFSET, COUNT, INSTANCES], ');') } if (ELEMENTS && ELEMENTS !== 'null') { if (!elementsStatic) { inner('if(', ELEMENTS, '){') drawElements() inner('}else{') drawArrays() inner('}') } else { drawElements() } } else { drawArrays() } } function emitRegular () { function drawElements () { inner(GL + '.drawElements(' + [ PRIMITIVE, COUNT, ELEMENT_TYPE, OFFSET + '<<((' + ELEMENT_TYPE + '-' + GL_UNSIGNED_BYTE$7 + ')>>1)' ] + ');') } function drawArrays () { inner(GL + '.drawArrays(' + [PRIMITIVE, OFFSET, COUNT] + ');') } if (ELEMENTS && ELEMENTS !== 'null') { if (!elementsStatic) { inner('if(', ELEMENTS, '){') drawElements() inner('}else{') drawArrays() inner('}') } else { drawElements() } } else { drawArrays() } } if (extInstancing && (typeof INSTANCES !== 'number' || INSTANCES >= 0)) { if (typeof INSTANCES === 'string') { inner('if(', INSTANCES, '>0){') emitInstancing() inner('}else if(', INSTANCES, '<0){') emitRegular() inner('}') } else { emitInstancing() } } else { emitRegular() } } function createBody (emitBody, parentEnv, args, program, count) { var env = createREGLEnvironment() var scope = env.proc('body', count) if (extInstancing) { env.instancing = scope.def( env.shared.extensions, '.angle_instanced_arrays') } emitBody(env, scope, args, program) return env.compile().body } // =================================================== // =================================================== // DRAW PROC // =================================================== // =================================================== function emitDrawBody (env, draw, args, program) { injectExtensions(env, draw) if (args.useVAO) { if (args.drawVAO) { draw(env.shared.vao, '.setVAO(', args.drawVAO.append(env, draw), ');') } else { draw(env.shared.vao, '.setVAO(', env.shared.vao, '.targetVAO);') } } else { draw(env.shared.vao, '.setVAO(null);') emitAttributes(env, draw, args, program.attributes, function () { return true }) } emitUniforms(env, draw, args, program.uniforms, function () { return true }, false) emitDraw(env, draw, draw, args) } function emitDrawProc (env, args) { var draw = env.proc('draw', 1) injectExtensions(env, draw) emitContext(env, draw, args.context) emitPollFramebuffer(env, draw, args.framebuffer) emitPollState(env, draw, args) emitSetOptions(env, draw, args.state) emitProfile(env, draw, args, false, true) var program = args.shader.progVar.append(env, draw) draw(env.shared.gl, '.useProgram(', program, '.program);') if (args.shader.program) { emitDrawBody(env, draw, args, args.shader.program) } else { draw(env.shared.vao, '.setVAO(null);') var drawCache = env.global.def('{}') var PROG_ID = draw.def(program, '.id') var CACHED_PROC = draw.def(drawCache, '[', PROG_ID, ']') draw( env.cond(CACHED_PROC) .then(CACHED_PROC, '.call(this,a0);') .else( CACHED_PROC, '=', drawCache, '[', PROG_ID, ']=', env.link(function (program) { return createBody(emitDrawBody, env, args, program, 1) }), '(', program, ');', CACHED_PROC, '.call(this,a0);')) } if (Object.keys(args.state).length > 0) { draw(env.shared.current, '.dirty=true;') } if (env.shared.vao) { draw(env.shared.vao, '.setVAO(null);') } } // =================================================== // =================================================== // BATCH PROC // =================================================== // =================================================== function emitBatchDynamicShaderBody (env, scope, args, program) { env.batchId = 'a1' injectExtensions(env, scope) function all () { return true } emitAttributes(env, scope, args, program.attributes, all) emitUniforms(env, scope, args, program.uniforms, all, false) emitDraw(env, scope, scope, args) } function emitBatchBody (env, scope, args, program) { injectExtensions(env, scope) var contextDynamic = args.contextDep var BATCH_ID = scope.def() var PROP_LIST = 'a0' var NUM_PROPS = 'a1' var PROPS = scope.def() env.shared.props = PROPS env.batchId = BATCH_ID var outer = env.scope() var inner = env.scope() scope( outer.entry, 'for(', BATCH_ID, '=0;', BATCH_ID, '<', NUM_PROPS, ';++', BATCH_ID, '){', PROPS, '=', PROP_LIST, '[', BATCH_ID, '];', inner, '}', outer.exit) function isInnerDefn (defn) { return ((defn.contextDep && contextDynamic) || defn.propDep) } function isOuterDefn (defn) { return !isInnerDefn(defn) } if (args.needsContext) { emitContext(env, inner, args.context) } if (args.needsFramebuffer) { emitPollFramebuffer(env, inner, args.framebuffer) } emitSetOptions(env, inner, args.state, isInnerDefn) if (args.profile && isInnerDefn(args.profile)) { emitProfile(env, inner, args, false, true) } if (!program) { var progCache = env.global.def('{}') var PROGRAM = args.shader.progVar.append(env, inner) var PROG_ID = inner.def(PROGRAM, '.id') var CACHED_PROC = inner.def(progCache, '[', PROG_ID, ']') inner( env.shared.gl, '.useProgram(', PROGRAM, '.program);', 'if(!', CACHED_PROC, '){', CACHED_PROC, '=', progCache, '[', PROG_ID, ']=', env.link(function (program) { return createBody( emitBatchDynamicShaderBody, env, args, program, 2) }), '(', PROGRAM, ');}', CACHED_PROC, '.call(this,a0[', BATCH_ID, '],', BATCH_ID, ');') } else { if (args.useVAO) { if (args.drawVAO) { if (isInnerDefn(args.drawVAO)) { // vao is a prop inner(env.shared.vao, '.setVAO(', args.drawVAO.append(env, inner), ');') } else { // vao is invariant outer(env.shared.vao, '.setVAO(', args.drawVAO.append(env, outer), ');') } } else { // scoped vao binding outer(env.shared.vao, '.setVAO(', env.shared.vao, '.targetVAO);') } } else { outer(env.shared.vao, '.setVAO(null);') emitAttributes(env, outer, args, program.attributes, isOuterDefn) emitAttributes(env, inner, args, program.attributes, isInnerDefn) } emitUniforms(env, outer, args, program.uniforms, isOuterDefn, false) emitUniforms(env, inner, args, program.uniforms, isInnerDefn, true) emitDraw(env, outer, inner, args) } } function emitBatchProc (env, args) { var batch = env.proc('batch', 2) env.batchId = '0' injectExtensions(env, batch) // Check if any context variables depend on props var contextDynamic = false var needsContext = true Object.keys(args.context).forEach(function (name) { contextDynamic = contextDynamic || args.context[name].propDep }) if (!contextDynamic) { emitContext(env, batch, args.context) needsContext = false } // framebuffer state affects framebufferWidth/height context vars var framebuffer = args.framebuffer var needsFramebuffer = false if (framebuffer) { if (framebuffer.propDep) { contextDynamic = needsFramebuffer = true } else if (framebuffer.contextDep && contextDynamic) { needsFramebuffer = true } if (!needsFramebuffer) { emitPollFramebuffer(env, batch, framebuffer) } } else { emitPollFramebuffer(env, batch, null) } // viewport is weird because it can affect context vars if (args.state.viewport && args.state.viewport.propDep) { contextDynamic = true } function isInnerDefn (defn) { return (defn.contextDep && contextDynamic) || defn.propDep } // set webgl options emitPollState(env, batch, args) emitSetOptions(env, batch, args.state, function (defn) { return !isInnerDefn(defn) }) if (!args.profile || !isInnerDefn(args.profile)) { emitProfile(env, batch, args, false, 'a1') } // Save these values to args so that the batch body routine can use them args.contextDep = contextDynamic args.needsContext = needsContext args.needsFramebuffer = needsFramebuffer // determine if shader is dynamic var progDefn = args.shader.progVar if ((progDefn.contextDep && contextDynamic) || progDefn.propDep) { emitBatchBody( env, batch, args, null) } else { var PROGRAM = progDefn.append(env, batch) batch(env.shared.gl, '.useProgram(', PROGRAM, '.program);') if (args.shader.program) { emitBatchBody( env, batch, args, args.shader.program) } else { batch(env.shared.vao, '.setVAO(null);') var batchCache = env.global.def('{}') var PROG_ID = batch.def(PROGRAM, '.id') var CACHED_PROC = batch.def(batchCache, '[', PROG_ID, ']') batch( env.cond(CACHED_PROC) .then(CACHED_PROC, '.call(this,a0,a1);') .else( CACHED_PROC, '=', batchCache, '[', PROG_ID, ']=', env.link(function (program) { return createBody(emitBatchBody, env, args, program, 2) }), '(', PROGRAM, ');', CACHED_PROC, '.call(this,a0,a1);')) } } if (Object.keys(args.state).length > 0) { batch(env.shared.current, '.dirty=true;') } if (env.shared.vao) { batch(env.shared.vao, '.setVAO(null);') } } // =================================================== // =================================================== // SCOPE COMMAND // =================================================== // =================================================== function emitScopeProc (env, args) { var scope = env.proc('scope', 3) env.batchId = 'a2' var shared = env.shared var CURRENT_STATE = shared.current emitContext(env, scope, args.context) if (args.framebuffer) { args.framebuffer.append(env, scope) } sortState(Object.keys(args.state)).forEach(function (name) { var defn = args.state[name] var value = defn.append(env, scope) if (isArrayLike(value)) { value.forEach(function (v, i) { scope.set(env.next[name], '[' + i + ']', v) }) } else { scope.set(shared.next, '.' + name, value) } }) emitProfile(env, scope, args, true, true) ;[S_ELEMENTS, S_OFFSET, S_COUNT, S_INSTANCES, S_PRIMITIVE].forEach( function (opt) { var variable = args.draw[opt] if (!variable) { return } scope.set(shared.draw, '.' + opt, '' + variable.append(env, scope)) }) Object.keys(args.uniforms).forEach(function (opt) { var value = args.uniforms[opt].append(env, scope) if (Array.isArray(value)) { value = '[' + value.join() + ']' } scope.set( shared.uniforms, '[' + stringStore.id(opt) + ']', value) }) Object.keys(args.attributes).forEach(function (name) { var record = args.attributes[name].append(env, scope) var scopeAttrib = env.scopeAttrib(name) Object.keys(new AttributeRecord()).forEach(function (prop) { scope.set(scopeAttrib, '.' + prop, record[prop]) }) }) if (args.scopeVAO) { scope.set(shared.vao, '.targetVAO', args.scopeVAO.append(env, scope)) } function saveShader (name) { var shader = args.shader[name] if (shader) { scope.set(shared.shader, '.' + name, shader.append(env, scope)) } } saveShader(S_VERT) saveShader(S_FRAG) if (Object.keys(args.state).length > 0) { scope(CURRENT_STATE, '.dirty=true;') scope.exit(CURRENT_STATE, '.dirty=true;') } scope('a1(', env.shared.context, ',a0,', env.batchId, ');') } function isDynamicObject (object) { if (typeof object !== 'object' || isArrayLike(object)) { return } var props = Object.keys(object) for (var i = 0; i < props.length; ++i) { if (dynamic.isDynamic(object[props[i]])) { return true } } return false } function splatObject (env, options, name) { var object = options.static[name] if (!object || !isDynamicObject(object)) { return } var globals = env.global var keys = Object.keys(object) var thisDep = false var contextDep = false var propDep = false var objectRef = env.global.def('{}') keys.forEach(function (key) { var value = object[key] if (dynamic.isDynamic(value)) { if (typeof value === 'function') { value = object[key] = dynamic.unbox(value) } var deps = createDynamicDecl(value, null) thisDep = thisDep || deps.thisDep propDep = propDep || deps.propDep contextDep = contextDep || deps.contextDep } else { globals(objectRef, '.', key, '=') switch (typeof value) { case 'number': globals(value) break case 'string': globals('"', value, '"') break case 'object': if (Array.isArray(value)) { globals('[', value.join(), ']') } break default: globals(env.link(value)) break } globals(';') } }) function appendBlock (env, block) { keys.forEach(function (key) { var value = object[key] if (!dynamic.isDynamic(value)) { return } var ref = env.invoke(block, value) block(objectRef, '.', key, '=', ref, ';') }) } options.dynamic[name] = new dynamic.DynamicVariable(DYN_THUNK, { thisDep: thisDep, contextDep: contextDep, propDep: propDep, ref: objectRef, append: appendBlock }) delete options.static[name] } // =========================================================================== // =========================================================================== // MAIN DRAW COMMAND // =========================================================================== // =========================================================================== function compileCommand (options, attributes, uniforms, context, stats) { var env = createREGLEnvironment() // link stats, so that we can easily access it in the program. env.stats = env.link(stats) // splat options and attributes to allow for dynamic nested properties Object.keys(attributes.static).forEach(function (key) { splatObject(env, attributes, key) }) NESTED_OPTIONS.forEach(function (name) { splatObject(env, options, name) }) var args = parseArguments(options, attributes, uniforms, context, env) emitDrawProc(env, args) emitScopeProc(env, args) emitBatchProc(env, args) return extend(env.compile(), { destroy: function () { args.shader.program.destroy() } }) } // =========================================================================== // =========================================================================== // POLL / REFRESH // =========================================================================== // =========================================================================== return { next: nextState, current: currentState, procs: (function () { var env = createREGLEnvironment() var poll = env.proc('poll') var refresh = env.proc('refresh') var common = env.block() poll(common) refresh(common) var shared = env.shared var GL = shared.gl var NEXT_STATE = shared.next var CURRENT_STATE = shared.current common(CURRENT_STATE, '.dirty=false;') emitPollFramebuffer(env, poll) emitPollFramebuffer(env, refresh, null, true) // Refresh updates all attribute state changes var INSTANCING if (extInstancing) { INSTANCING = env.link(extInstancing) } // update vertex array bindings if (extensions.oes_vertex_array_object) { refresh(env.link(extensions.oes_vertex_array_object), '.bindVertexArrayOES(null);') } for (var i = 0; i < limits.maxAttributes; ++i) { var BINDING = refresh.def(shared.attributes, '[', i, ']') var ifte = env.cond(BINDING, '.buffer') ifte.then( GL, '.enableVertexAttribArray(', i, ');', GL, '.bindBuffer(', GL_ARRAY_BUFFER$2, ',', BINDING, '.buffer.buffer);', GL, '.vertexAttribPointer(', i, ',', BINDING, '.size,', BINDING, '.type,', BINDING, '.normalized,', BINDING, '.stride,', BINDING, '.offset);' ).else( GL, '.disableVertexAttribArray(', i, ');', GL, '.vertexAttrib4f(', i, ',', BINDING, '.x,', BINDING, '.y,', BINDING, '.z,', BINDING, '.w);', BINDING, '.buffer=null;') refresh(ifte) if (extInstancing) { refresh( INSTANCING, '.vertexAttribDivisorANGLE(', i, ',', BINDING, '.divisor);') } } refresh( env.shared.vao, '.currentVAO=null;', env.shared.vao, '.setVAO(', env.shared.vao, '.targetVAO);') Object.keys(GL_FLAGS).forEach(function (flag) { var cap = GL_FLAGS[flag] var NEXT = common.def(NEXT_STATE, '.', flag) var block = env.block() block('if(', NEXT, '){', GL, '.enable(', cap, ')}else{', GL, '.disable(', cap, ')}', CURRENT_STATE, '.', flag, '=', NEXT, ';') refresh(block) poll( 'if(', NEXT, '!==', CURRENT_STATE, '.', flag, '){', block, '}') }) Object.keys(GL_VARIABLES).forEach(function (name) { var func = GL_VARIABLES[name] var init = currentState[name] var NEXT, CURRENT var block = env.block() block(GL, '.', func, '(') if (isArrayLike(init)) { var n = init.length NEXT = env.global.def(NEXT_STATE, '.', name) CURRENT = env.global.def(CURRENT_STATE, '.', name) block( loop(n, function (i) { return NEXT + '[' + i + ']' }), ');', loop(n, function (i) { return CURRENT + '[' + i + ']=' + NEXT + '[' + i + '];' }).join('')) poll( 'if(', loop(n, function (i) { return NEXT + '[' + i + ']!==' + CURRENT + '[' + i + ']' }).join('||'), '){', block, '}') } else { NEXT = common.def(NEXT_STATE, '.', name) CURRENT = common.def(CURRENT_STATE, '.', name) block( NEXT, ');', CURRENT_STATE, '.', name, '=', NEXT, ';') poll( 'if(', NEXT, '!==', CURRENT, '){', block, '}') } refresh(block) }) return env.compile() })(), compile: compileCommand } } function stats () { return { vaoCount: 0, bufferCount: 0, elementsCount: 0, framebufferCount: 0, shaderCount: 0, textureCount: 0, cubeCount: 0, renderbufferCount: 0, maxTextureUnits: 0 } } var GL_QUERY_RESULT_EXT = 0x8866 var GL_QUERY_RESULT_AVAILABLE_EXT = 0x8867 var GL_TIME_ELAPSED_EXT = 0x88BF var createTimer = function (gl, extensions) { if (!extensions.ext_disjoint_timer_query) { return null } // QUERY POOL BEGIN var queryPool = [] function allocQuery () { return queryPool.pop() || extensions.ext_disjoint_timer_query.createQueryEXT() } function freeQuery (query) { queryPool.push(query) } // QUERY POOL END var pendingQueries = [] function beginQuery (stats) { var query = allocQuery() extensions.ext_disjoint_timer_query.beginQueryEXT(GL_TIME_ELAPSED_EXT, query) pendingQueries.push(query) pushScopeStats(pendingQueries.length - 1, pendingQueries.length, stats) } function endQuery () { extensions.ext_disjoint_timer_query.endQueryEXT(GL_TIME_ELAPSED_EXT) } // // Pending stats pool. // function PendingStats () { this.startQueryIndex = -1 this.endQueryIndex = -1 this.sum = 0 this.stats = null } var pendingStatsPool = [] function allocPendingStats () { return pendingStatsPool.pop() || new PendingStats() } function freePendingStats (pendingStats) { pendingStatsPool.push(pendingStats) } // Pending stats pool end var pendingStats = [] function pushScopeStats (start, end, stats) { var ps = allocPendingStats() ps.startQueryIndex = start ps.endQueryIndex = end ps.sum = 0 ps.stats = stats pendingStats.push(ps) } // we should call this at the beginning of the frame, // in order to update gpuTime var timeSum = [] var queryPtr = [] function update () { var ptr, i var n = pendingQueries.length if (n === 0) { return } // Reserve space queryPtr.length = Math.max(queryPtr.length, n + 1) timeSum.length = Math.max(timeSum.length, n + 1) timeSum[0] = 0 queryPtr[0] = 0 // Update all pending timer queries var queryTime = 0 ptr = 0 for (i = 0; i < pendingQueries.length; ++i) { var query = pendingQueries[i] if (extensions.ext_disjoint_timer_query.getQueryObjectEXT(query, GL_QUERY_RESULT_AVAILABLE_EXT)) { queryTime += extensions.ext_disjoint_timer_query.getQueryObjectEXT(query, GL_QUERY_RESULT_EXT) freeQuery(query) } else { pendingQueries[ptr++] = query } timeSum[i + 1] = queryTime queryPtr[i + 1] = ptr } pendingQueries.length = ptr // Update all pending stat queries ptr = 0 for (i = 0; i < pendingStats.length; ++i) { var stats = pendingStats[i] var start = stats.startQueryIndex var end = stats.endQueryIndex stats.sum += timeSum[end] - timeSum[start] var startPtr = queryPtr[start] var endPtr = queryPtr[end] if (endPtr === startPtr) { stats.stats.gpuTime += stats.sum / 1e6 freePendingStats(stats) } else { stats.startQueryIndex = startPtr stats.endQueryIndex = endPtr pendingStats[ptr++] = stats } } pendingStats.length = ptr } return { beginQuery: beginQuery, endQuery: endQuery, pushScopeStats: pushScopeStats, update: update, getNumPendingQueries: function () { return pendingQueries.length }, clear: function () { queryPool.push.apply(queryPool, pendingQueries) for (var i = 0; i < queryPool.length; i++) { extensions.ext_disjoint_timer_query.deleteQueryEXT(queryPool[i]) } pendingQueries.length = 0 queryPool.length = 0 }, restore: function () { pendingQueries.length = 0 queryPool.length = 0 } } } var GL_COLOR_BUFFER_BIT = 16384 var GL_DEPTH_BUFFER_BIT = 256 var GL_STENCIL_BUFFER_BIT = 1024 var GL_ARRAY_BUFFER = 34962 var CONTEXT_LOST_EVENT = 'webglcontextlost' var CONTEXT_RESTORED_EVENT = 'webglcontextrestored' var DYN_PROP = 1 var DYN_CONTEXT = 2 var DYN_STATE = 3 function find (haystack, needle) { for (var i = 0; i < haystack.length; ++i) { if (haystack[i] === needle) { return i } } return -1 } function wrapREGL (args) { var config = parseArgs(args) if (!config) { return null } var gl = config.gl var glAttributes = gl.getContextAttributes() var contextLost = gl.isContextLost() var extensionState = createExtensionCache(gl, config) if (!extensionState) { return null } var stringStore = createStringStore() var stats$$1 = stats() var extensions = extensionState.extensions var timer = createTimer(gl, extensions) var START_TIME = clock() var WIDTH = gl.drawingBufferWidth var HEIGHT = gl.drawingBufferHeight var contextState = { tick: 0, time: 0, viewportWidth: WIDTH, viewportHeight: HEIGHT, framebufferWidth: WIDTH, framebufferHeight: HEIGHT, drawingBufferWidth: WIDTH, drawingBufferHeight: HEIGHT, pixelRatio: config.pixelRatio } var uniformState = {} var drawState = { elements: null, primitive: 4, // GL_TRIANGLES count: -1, offset: 0, instances: -1 } var limits = wrapLimits(gl, extensions) var bufferState = wrapBufferState( gl, stats$$1, config, destroyBuffer) var elementState = wrapElementsState(gl, extensions, bufferState, stats$$1) var attributeState = wrapAttributeState( gl, extensions, limits, stats$$1, bufferState, elementState, drawState) function destroyBuffer (buffer) { return attributeState.destroyBuffer(buffer) } var shaderState = wrapShaderState(gl, stringStore, stats$$1, config) var textureState = createTextureSet( gl, extensions, limits, function () { core.procs.poll() }, contextState, stats$$1, config) var renderbufferState = wrapRenderbuffers(gl, extensions, limits, stats$$1, config) var framebufferState = wrapFBOState( gl, extensions, limits, textureState, renderbufferState, stats$$1) var core = reglCore( gl, stringStore, extensions, limits, bufferState, elementState, textureState, framebufferState, uniformState, attributeState, shaderState, drawState, contextState, timer, config) var readPixels = wrapReadPixels( gl, framebufferState, core.procs.poll, contextState, glAttributes, extensions, limits) var nextState = core.next var canvas = gl.canvas var rafCallbacks = [] var lossCallbacks = [] var restoreCallbacks = [] var destroyCallbacks = [config.onDestroy] var activeRAF = null function handleRAF () { if (rafCallbacks.length === 0) { if (timer) { timer.update() } activeRAF = null return } // schedule next animation frame activeRAF = raf.next(handleRAF) // poll for changes poll() // fire a callback for all pending rafs for (var i = rafCallbacks.length - 1; i >= 0; --i) { var cb = rafCallbacks[i] if (cb) { cb(contextState, null, 0) } } // flush all pending webgl calls gl.flush() // poll GPU timers *after* gl.flush so we don't delay command dispatch if (timer) { timer.update() } } function startRAF () { if (!activeRAF && rafCallbacks.length > 0) { activeRAF = raf.next(handleRAF) } } function stopRAF () { if (activeRAF) { raf.cancel(handleRAF) activeRAF = null } } function handleContextLoss (event) { event.preventDefault() // set context lost flag contextLost = true // pause request animation frame stopRAF() // lose context lossCallbacks.forEach(function (cb) { cb() }) } function handleContextRestored (event) { // clear error code gl.getError() // clear context lost flag contextLost = false // refresh state extensionState.restore() shaderState.restore() bufferState.restore() textureState.restore() renderbufferState.restore() framebufferState.restore() attributeState.restore() if (timer) { timer.restore() } // refresh state core.procs.refresh() // restart RAF startRAF() // restore context restoreCallbacks.forEach(function (cb) { cb() }) } if (canvas) { canvas.addEventListener(CONTEXT_LOST_EVENT, handleContextLoss, false) canvas.addEventListener(CONTEXT_RESTORED_EVENT, handleContextRestored, false) } function destroy () { rafCallbacks.length = 0 stopRAF() if (canvas) { canvas.removeEventListener(CONTEXT_LOST_EVENT, handleContextLoss) canvas.removeEventListener(CONTEXT_RESTORED_EVENT, handleContextRestored) } shaderState.clear() framebufferState.clear() renderbufferState.clear() attributeState.clear() textureState.clear() elementState.clear() bufferState.clear() if (timer) { timer.clear() } destroyCallbacks.forEach(function (cb) { cb() }) } function compileProcedure (options) { function flattenNestedOptions (options) { var result = extend({}, options) delete result.uniforms delete result.attributes delete result.context delete result.vao if ('stencil' in result && result.stencil.op) { result.stencil.opBack = result.stencil.opFront = result.stencil.op delete result.stencil.op } function merge (name) { if (name in result) { var child = result[name] delete result[name] Object.keys(child).forEach(function (prop) { result[name + '.' + prop] = child[prop] }) } } merge('blend') merge('depth') merge('cull') merge('stencil') merge('polygonOffset') merge('scissor') merge('sample') if ('vao' in options) { result.vao = options.vao } return result } function separateDynamic (object, useArrays) { var staticItems = {} var dynamicItems = {} Object.keys(object).forEach(function (option) { var value = object[option] if (dynamic.isDynamic(value)) { dynamicItems[option] = dynamic.unbox(value, option) return } else if (useArrays && Array.isArray(value)) { for (var i = 0; i < value.length; ++i) { if (dynamic.isDynamic(value[i])) { dynamicItems[option] = dynamic.unbox(value, option) return } } } staticItems[option] = value }) return { dynamic: dynamicItems, static: staticItems } } // Treat context variables separate from other dynamic variables var context = separateDynamic(options.context || {}, true) var uniforms = separateDynamic(options.uniforms || {}, true) var attributes = separateDynamic(options.attributes || {}, false) var opts = separateDynamic(flattenNestedOptions(options), false) var stats$$1 = { gpuTime: 0.0, cpuTime: 0.0, count: 0 } var compiled = core.compile(opts, attributes, uniforms, context, stats$$1) var draw = compiled.draw var batch = compiled.batch var scope = compiled.scope // FIXME: we should modify code generation for batch commands so this // isn't necessary var EMPTY_ARRAY = [] function reserve (count) { while (EMPTY_ARRAY.length < count) { EMPTY_ARRAY.push(null) } return EMPTY_ARRAY } function REGLCommand (args, body) { var i if (contextLost) { } if (typeof args === 'function') { return scope.call(this, null, args, 0) } else if (typeof body === 'function') { if (typeof args === 'number') { for (i = 0; i < args; ++i) { scope.call(this, null, body, i) } } else if (Array.isArray(args)) { for (i = 0; i < args.length; ++i) { scope.call(this, args[i], body, i) } } else { return scope.call(this, args, body, 0) } } else if (typeof args === 'number') { if (args > 0) { return batch.call(this, reserve(args | 0), args | 0) } } else if (Array.isArray(args)) { if (args.length) { return batch.call(this, args, args.length) } } else { return draw.call(this, args) } } return extend(REGLCommand, { stats: stats$$1, destroy: function () { compiled.destroy() } }) } var setFBO = framebufferState.setFBO = compileProcedure({ framebuffer: dynamic.define.call(null, DYN_PROP, 'framebuffer') }) function clearImpl (_, options) { var clearFlags = 0 core.procs.poll() var c = options.color if (c) { gl.clearColor(+c[0] || 0, +c[1] || 0, +c[2] || 0, +c[3] || 0) clearFlags |= GL_COLOR_BUFFER_BIT } if ('depth' in options) { gl.clearDepth(+options.depth) clearFlags |= GL_DEPTH_BUFFER_BIT } if ('stencil' in options) { gl.clearStencil(options.stencil | 0) clearFlags |= GL_STENCIL_BUFFER_BIT } gl.clear(clearFlags) } function clear (options) { if ('framebuffer' in options) { if (options.framebuffer && options.framebuffer_reglType === 'framebufferCube') { for (var i = 0; i < 6; ++i) { setFBO(extend({ framebuffer: options.framebuffer.faces[i] }, options), clearImpl) } } else { setFBO(options, clearImpl) } } else { clearImpl(null, options) } } function frame (cb) { rafCallbacks.push(cb) function cancel () { // FIXME: should we check something other than equals cb here? // what if a user calls frame twice with the same callback... // var i = find(rafCallbacks, cb) function pendingCancel () { var index = find(rafCallbacks, pendingCancel) rafCallbacks[index] = rafCallbacks[rafCallbacks.length - 1] rafCallbacks.length -= 1 if (rafCallbacks.length <= 0) { stopRAF() } } rafCallbacks[i] = pendingCancel } startRAF() return { cancel: cancel } } // poll viewport function pollViewport () { var viewport = nextState.viewport var scissorBox = nextState.scissor_box viewport[0] = viewport[1] = scissorBox[0] = scissorBox[1] = 0 contextState.viewportWidth = contextState.framebufferWidth = contextState.drawingBufferWidth = viewport[2] = scissorBox[2] = gl.drawingBufferWidth contextState.viewportHeight = contextState.framebufferHeight = contextState.drawingBufferHeight = viewport[3] = scissorBox[3] = gl.drawingBufferHeight } function poll () { contextState.tick += 1 contextState.time = now() pollViewport() core.procs.poll() } function refresh () { textureState.refresh() pollViewport() core.procs.refresh() if (timer) { timer.update() } } function now () { return (clock() - START_TIME) / 1000.0 } refresh() function addListener (event, callback) { var callbacks switch (event) { case 'frame': return frame(callback) case 'lost': callbacks = lossCallbacks break case 'restore': callbacks = restoreCallbacks break case 'destroy': callbacks = destroyCallbacks break default: } callbacks.push(callback) return { cancel: function () { for (var i = 0; i < callbacks.length; ++i) { if (callbacks[i] === callback) { callbacks[i] = callbacks[callbacks.length - 1] callbacks.pop() return } } } } } var regl = extend(compileProcedure, { // Clear current FBO clear: clear, // Short cuts for dynamic variables prop: dynamic.define.bind(null, DYN_PROP), context: dynamic.define.bind(null, DYN_CONTEXT), this: dynamic.define.bind(null, DYN_STATE), // executes an empty draw command draw: compileProcedure({}), // Resources buffer: function (options) { return bufferState.create(options, GL_ARRAY_BUFFER, false, false) }, elements: function (options) { return elementState.create(options, false) }, texture: textureState.create2D, cube: textureState.createCube, renderbuffer: renderbufferState.create, framebuffer: framebufferState.create, framebufferCube: framebufferState.createCube, vao: attributeState.createVAO, // Expose context attributes attributes: glAttributes, // Frame rendering frame: frame, on: addListener, // System limits limits: limits, hasExtension: function (name) { return limits.extensions.indexOf(name.toLowerCase()) >= 0 }, // Read pixels read: readPixels, // Destroy regl and all associated resources destroy: destroy, // Direct GL state manipulation _gl: gl, _refresh: refresh, poll: function () { poll() if (timer) { timer.update() } }, // Current time now: now, // regl Statistics Information stats: stats$$1 }) config.onDone(null, regl) return regl } return wrapREGL; })));