package.regl.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of regl Show documentation
Show all versions of regl Show documentation
regl is a fast functional WebGL framework.
The newest version!
var check = require('./lib/util/check')
var extend = require('./lib/util/extend')
var dynamic = require('./lib/dynamic')
var raf = require('./lib/util/raf')
var clock = require('./lib/util/clock')
var createStringStore = require('./lib/strings')
var initWebGL = require('./lib/webgl')
var wrapExtensions = require('./lib/extension')
var wrapLimits = require('./lib/limits')
var wrapBuffers = require('./lib/buffer')
var wrapElements = require('./lib/elements')
var wrapTextures = require('./lib/texture')
var wrapRenderbuffers = require('./lib/renderbuffer')
var wrapFramebuffers = require('./lib/framebuffer')
var wrapAttributes = require('./lib/attribute')
var wrapShaders = require('./lib/shader')
var wrapRead = require('./lib/read')
var createCore = require('./lib/core')
var createStats = require('./lib/stats')
var createTimer = require('./lib/timer')
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
}
module.exports = function wrapREGL (args) {
var config = initWebGL(args)
if (!config) {
return null
}
var gl = config.gl
var glAttributes = gl.getContextAttributes()
var contextLost = gl.isContextLost()
var extensionState = wrapExtensions(gl, config)
if (!extensionState) {
return null
}
var stringStore = createStringStore()
var stats = createStats()
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 = wrapBuffers(
gl,
stats,
config,
destroyBuffer)
var elementState = wrapElements(gl, extensions, bufferState, stats)
var attributeState = wrapAttributes(
gl,
extensions,
limits,
stats,
bufferState,
elementState,
drawState)
function destroyBuffer (buffer) {
return attributeState.destroyBuffer(buffer)
}
var shaderState = wrapShaders(gl, stringStore, stats, config)
var textureState = wrapTextures(
gl,
extensions,
limits,
function () { core.procs.poll() },
contextState,
stats,
config)
var renderbufferState = wrapRenderbuffers(gl, extensions, limits, stats, config)
var framebufferState = wrapFramebuffers(
gl,
extensions,
limits,
textureState,
renderbufferState,
stats)
var core = createCore(
gl,
stringStore,
extensions,
limits,
bufferState,
elementState,
textureState,
framebufferState,
uniformState,
attributeState,
shaderState,
drawState,
contextState,
timer,
config)
var readPixels = wrapRead(
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) {
check(!!options, 'invalid args to regl({...})')
check.type(options, 'object', 'invalid args to regl({...})')
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 = {
gpuTime: 0.0,
cpuTime: 0.0,
count: 0
}
var compiled = core.compile(opts, attributes, uniforms, context, stats)
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) {
check.raise('context lost')
}
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,
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
}
check(!!clearFlags, 'called regl.clear with no buffer specified')
gl.clear(clearFlags)
}
function clear (options) {
check(
typeof options === 'object' && options,
'regl.clear() takes an object as input')
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) {
check.type(cb, 'function', 'regl.frame() callback must be a function')
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)
check(i >= 0, 'cannot cancel a frame twice')
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) {
check.type(callback, 'function', 'listener callback must be a function')
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:
check.raise('invalid event, must be one of frame,lost,restore,destroy')
}
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
})
config.onDone(null, regl)
return regl
}