securityos/node_modules/regl/regl.js

623 lines
15 KiB
JavaScript
Raw Normal View History

2024-09-06 15:32:35 +00:00
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
}