9168 lines
236 KiB
JavaScript
9168 lines
236 KiB
JavaScript
(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;
|
|
|
|
})));
|