Comparing version 0.0.0 to 0.1.0
// Array and element buffer creation | ||
var check = require('./check') | ||
var isTypedArray = require('./is-typed-array') | ||
var bufferTypes = require('./constants/buffer.json') | ||
var usageTypes = require('./constants/usage.json') | ||
@@ -9,7 +8,18 @@ var arrayTypes = require('./constants/arraytypes.json') | ||
var GL_UNSIGNED_BYTE = 5121 | ||
var GL_ARRAY_BUFFER = 34962 | ||
var GL_ELEMENT_ARRAY_BUFFER = 34963 | ||
var GL_STATIC_DRAW = 35044 | ||
var GL_FLOAT = 5126 | ||
module.exports = function wrapBufferState (gl, extensions) { | ||
function flatten (data, dimension) { | ||
var result = new Float32Array(data.length * dimension) | ||
var ptr = 0 | ||
for (var i = 0; i < data.length; ++i) { | ||
var v = data[i] | ||
for (var j = 0; j < dimension; ++j) { | ||
result[ptr++] = v[j] | ||
} | ||
} | ||
return result | ||
} | ||
module.exports = function wrapBufferState (gl) { | ||
var bufferCount = 0 | ||
@@ -24,2 +34,3 @@ var bufferSet = {} | ||
this.byteLength = 0 | ||
this.dimension = 1 | ||
this.data = null | ||
@@ -31,5 +42,3 @@ this.dtype = GL_UNSIGNED_BYTE | ||
bind: function () { | ||
var buffer = this.buffer | ||
check(!!buffer, 'cannot bind deleted buffer') | ||
gl.bindBuffer(this.type, buffer) | ||
gl.bindBuffer(this.type, this.buffer) | ||
}, | ||
@@ -60,2 +69,3 @@ | ||
var dimension = (options.dimension | 0) || 1 | ||
if ('data' in options) { | ||
@@ -68,16 +78,16 @@ var data = options.data | ||
if (Array.isArray(data)) { | ||
if (this.type === GL_ELEMENT_ARRAY_BUFFER) { | ||
if (extensions['oes_element_index_uint']) { | ||
data = new Uint32Array(data) | ||
} else { | ||
data = new Uint16Array(data) | ||
} | ||
if (data.length > 0 && Array.isArray(data[0])) { | ||
dimension = data[0].length | ||
data = flatten(data, dimension) | ||
this.dtype = GL_FLOAT | ||
} else { | ||
data = new Float32Array(data) | ||
this.dtype = GL_FLOAT | ||
} | ||
} else { | ||
check.isTypedArray(data, 'invalid data type buffer data') | ||
this.dtype = arrayTypes[Object.prototype.toString.call(data)] | ||
} | ||
this.dimension = dimension | ||
this.byteLength = data.byteLength | ||
this.dtype = arrayTypes[Object.prototype.toString.call(data)] | ||
} | ||
@@ -106,3 +116,3 @@ this.data = data | ||
check(this.buffer, 'buffer must not be deleted already') | ||
gl.destroyBuffer(this.buffer) | ||
gl.deleteBuffer(this.buffer) | ||
this.buffer = null | ||
@@ -113,21 +123,16 @@ delete bufferSet[this.id] | ||
function createBuffer (options) { | ||
function createBuffer (options, type) { | ||
options = options || {} | ||
var handle = gl.createBuffer() | ||
var type = GL_ARRAY_BUFFER | ||
if ('type' in options) { | ||
check.parameter(type, bufferTypes, 'buffer type') | ||
type = bufferTypes[options.type] | ||
} | ||
var buffer = new REGLBuffer(handle, type) | ||
buffer.update(options) | ||
bufferSet[buffer.id] = buffer | ||
buffer.update(options) | ||
function updateBuffer (options) { | ||
buffer.update(options || {}) | ||
return buffer | ||
return updateBuffer | ||
} | ||
updateBuffer._reglType = 'buffer' | ||
updateBuffer._buffer = buffer | ||
@@ -155,3 +160,3 @@ updateBuffer.destroy = function () { buffer.destroy() } | ||
getBuffer: function (wrapper) { | ||
if (wrapper._buffer instanceof REGLBuffer) { | ||
if (wrapper && wrapper._buffer instanceof REGLBuffer) { | ||
return wrapper._buffer | ||
@@ -158,0 +163,0 @@ } |
// Error checking and parameter validation | ||
var isTypedArray = require('./is-typed-array') | ||
function extend (Parent, Child, name) { | ||
var proto = Child.prototype = new Parent() | ||
proto.name = name | ||
proto.constructor = Child | ||
} | ||
function REGLError (message) { | ||
this.message = 'REGL Error:\n' + message | ||
this.stack = (new Error()).stack | ||
} | ||
extend(REGLError, Error, 'REGLError') | ||
function raise (message) { | ||
console.error(message) | ||
throw new REGLError(message) | ||
throw new Error(message) | ||
} | ||
@@ -68,3 +56,2 @@ | ||
module.exports = Object.assign(check, { | ||
error: REGLError, | ||
raise: raise, | ||
@@ -71,0 +58,0 @@ parameter: checkParameter, |
@@ -15,3 +15,3 @@ function slice (x) { | ||
function link (value) { | ||
var name = '_g' + (varCounter++) | ||
var name = 'g' + (varCounter++) | ||
linkedNames.push(name) | ||
@@ -29,4 +29,24 @@ linkedValues.push(value) | ||
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 Object.assign(push, { | ||
toString: code.join.bind(code, '') | ||
def: def, | ||
toString: function () { | ||
return [ | ||
(vars.length > 0 ? 'var ' + vars + ';' : ''), | ||
code.join('') | ||
].join('') | ||
} | ||
}) | ||
@@ -40,3 +60,3 @@ } | ||
function arg () { | ||
var name = '_a' + (varCounter++) | ||
var name = 'a' + (varCounter++) | ||
args.push(name) | ||
@@ -46,30 +66,11 @@ return name | ||
var code = [] | ||
function push () { | ||
code.push.apply(code, slice(arguments)) | ||
} | ||
var body = block() | ||
var bodyToString = body.toString | ||
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 | ||
} | ||
var result = procedures[name] = Object.assign(push, { | ||
var result = procedures[name] = Object.assign(body, { | ||
arg: arg, | ||
def: def, | ||
toString: function () { | ||
return [ | ||
'function(', args.join(), '){', | ||
(vars.length > 0 ? 'var ' + vars.join() + ';' : ''), | ||
code.join(''), | ||
bodyToString(), | ||
'}' | ||
@@ -85,3 +86,3 @@ ].join('') | ||
function compile () { | ||
var code = ['return {'] | ||
var code = ['"use strict";return {'] | ||
Object.keys(procedures).forEach(function (name) { | ||
@@ -88,0 +89,0 @@ code.push('"', name, '":', procedures[name].toString(), ',') |
var VARIABLE_COUNTER = 0 | ||
function DynamicVariable (type, data) { | ||
function DynamicVariable (isFunc, data) { | ||
this.id = (VARIABLE_COUNTER++) | ||
this.type = type | ||
this.func = isFunc | ||
this.data = data | ||
@@ -11,8 +11,8 @@ } | ||
switch (typeof data) { | ||
case 'boolean': | ||
case 'number': | ||
return new DynamicVariable('arg', data) | ||
case 'string': | ||
return new DynamicVariable('prop', data) | ||
return new DynamicVariable(false, data) | ||
case 'function': | ||
return new DynamicVariable('func', data) | ||
return new DynamicVariable(true, data) | ||
default: | ||
@@ -24,3 +24,4 @@ return defineDynamic | ||
function isDynamic (x) { | ||
return x === defineDynamic || x instanceof DynamicVariable | ||
return (typeof x === 'function' && !x._reglType) || | ||
x instanceof DynamicVariable | ||
} | ||
@@ -31,4 +32,7 @@ | ||
return x | ||
} else if (typeof x === 'function' && | ||
x !== defineDynamic) { | ||
return new DynamicVariable(true, x) | ||
} | ||
return new DynamicVariable('prop', path) | ||
return new DynamicVariable(false, path) | ||
} | ||
@@ -35,0 +39,0 @@ |
@@ -1,52 +0,29 @@ | ||
// Shader state management | ||
var check = require('./check') | ||
var createEnvironment = require('./codegen') | ||
var DEFAULT_FRAG_SHADER = 'void main(){gl_FragColor=vec4(0,0,0,0);}' | ||
var DEFAULT_VERT_SHADER = 'void main(){gl_Position=vec4(0,0,0,0);}' | ||
var GL_FRAGMENT_SHADER = 35632 | ||
var GL_VERTEX_SHADER = 35633 | ||
var GL_FLOAT = 5126 | ||
var GL_FLOAT_VEC2 = 35664 | ||
var GL_FLOAT_VEC3 = 35665 | ||
var GL_FLOAT_VEC4 = 35666 | ||
var GL_INT = 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 | ||
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 ActiveInfo (name, location, info) { | ||
this.name = name | ||
this.location = location | ||
this.info = info | ||
} | ||
module.exports = function wrapShaderState (gl, extensions) { | ||
var NUM_ATTRIBUTES = gl.getParameter(gl.MAX_VERTEX_ATTRIBS) | ||
var INSTANCING = extensions.extensions.angle_instanced_arrays | ||
module.exports = function wrapShaderState ( | ||
gl, | ||
extensions, | ||
attributeState, | ||
uniformState, | ||
compileShaderDraw) { | ||
// =================================================== | ||
// shader compilation | ||
// glsl compilation and linking | ||
// =================================================== | ||
var shaders = {} | ||
var fragShaders = [DEFAULT_FRAG_SHADER] | ||
var vertShaders = [DEFAULT_VERT_SHADER] | ||
function getShader (type, source) { | ||
@@ -79,3 +56,3 @@ var cache = shaders[type] | ||
Object.keys(shaders[type]).forEach(function (shader) { | ||
gl.destroyShader(shader) | ||
gl.deleteShader(shaders[type][shader]) | ||
}) | ||
@@ -93,14 +70,2 @@ }) | ||
function UniformInfo (name, location, info) { | ||
this.name = name | ||
this.location = location | ||
this.info = info | ||
} | ||
function AttributeInfo (name, location, info) { | ||
this.name = name | ||
this.location = location | ||
this.info = info | ||
} | ||
function REGLProgram (fragSrc, vertSrc) { | ||
@@ -112,3 +77,4 @@ this.fragSrc = fragSrc | ||
this.attributes = [] | ||
this.poll = function () {} | ||
this.draw = function () {} | ||
this.batchCache = {} | ||
} | ||
@@ -146,14 +112,14 @@ | ||
var name = info.name.replace('[0]', '[' + j + ']') | ||
uniforms.push(new UniformInfo( | ||
uniforms.push(new ActiveInfo( | ||
name, | ||
gl.getUniformLocation(program, name), | ||
info)) | ||
defUniform(name) | ||
uniformState.def(name) | ||
} | ||
} else { | ||
uniforms.push(new UniformInfo( | ||
uniforms.push(new ActiveInfo( | ||
info.name, | ||
gl.getUniformLocation(program, info.name), | ||
info)) | ||
defUniform(info.name) | ||
uniformState.def(info.name) | ||
} | ||
@@ -171,7 +137,7 @@ } | ||
if (info) { | ||
attributes.push(new AttributeInfo( | ||
attributes.push(new ActiveInfo( | ||
info.name, | ||
gl.getAttribLocation(program, info.name), | ||
info)) | ||
defAttribute(info.name) | ||
attributeState.def(info.name) | ||
} | ||
@@ -181,5 +147,6 @@ } | ||
// ------------------------------- | ||
// compile poll() | ||
// clear cached rendering methods | ||
// ------------------------------- | ||
this.poll = compileShaderPoll(this) | ||
this.draw = compileShaderDraw(this) | ||
this.batchCache = {} | ||
}, | ||
@@ -222,268 +189,6 @@ | ||
// =================================================== | ||
// uniform state | ||
// program state | ||
// =================================================== | ||
var uniformState = {} | ||
function defUniform (name) { | ||
if (name in uniformState) { | ||
return | ||
} | ||
uniformState[name] = [] | ||
} | ||
// =================================================== | ||
// attribute state | ||
// =================================================== | ||
var attributeState = {} | ||
function AttributeRecord () { | ||
this.pointer = false | ||
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 | ||
this.offset = 0 | ||
this.stride = 0 | ||
this.divisor = 0 | ||
} | ||
Object.assign(AttributeRecord.prototype, { | ||
equals: function (other, size) { | ||
if (this.pointer) { | ||
return other.pointer && | ||
this.x === other.x && | ||
this.y === other.y && | ||
this.z === other.z && | ||
this.w === other.w | ||
} else { | ||
return !other.pointer && | ||
this.buffer === other.buffer && | ||
this.size === size && | ||
this.normalized === other.normalized && | ||
this.type === other.type && | ||
this.offset === other.offset && | ||
this.stride === other.stride && | ||
this.divisor === other.divisor | ||
} | ||
}, | ||
set: function (other, size) { | ||
var pointer = this.pointer = other.pointer | ||
if (pointer) { | ||
this.buffer = other.buffer | ||
this.size = size | ||
this.normalized = other.normalized | ||
this.type = other.type | ||
this.offset = other.offset | ||
this.stride = other.stride | ||
this.divisor = other.divisor | ||
} else { | ||
this.x = other.x | ||
this.y = other.y | ||
this.z = other.z | ||
this.w = other.w | ||
} | ||
} | ||
}) | ||
function AttributeStack () { | ||
var records = new Array(16) | ||
for (var i = 0; i < 16; ++i) { | ||
records[i] = new AttributeRecord() | ||
} | ||
this.records = records | ||
this.top = 0 | ||
} | ||
function pushAttributeStack (stack) { | ||
var records = stack.records | ||
var top = stack.top | ||
while (records.length - 1 <= top) { | ||
records.push(new AttributeRecord()) | ||
} | ||
return records[++stack.top] | ||
} | ||
Object.assign(AttributeStack.prototype, { | ||
pushVec: function (x, y, z, w) { | ||
var head = pushAttributeStack(this) | ||
head.pointer = false | ||
head.x = x | ||
head.y = y | ||
head.z = z | ||
head.w = w | ||
}, | ||
pushPtr: function ( | ||
buffer, | ||
size, | ||
offset, | ||
stride, | ||
divisor, | ||
normalized, | ||
type) { | ||
var head = pushAttributeStack(this) | ||
head.pointer = true | ||
head.buffer = buffer | ||
head.size = size | ||
head.offset = offset | ||
head.stride = stride | ||
head.divisor = divisor | ||
head.normalized = normalized | ||
head.type = type | ||
}, | ||
pop: function () { | ||
this.top -= 1 | ||
} | ||
}) | ||
function defAttribute (name) { | ||
if (name in attributeState) { | ||
return | ||
} | ||
attributeState[name] = new AttributeStack() | ||
} | ||
var attributeBindings = new Array(NUM_ATTRIBUTES) | ||
for (var i = 0; i < NUM_ATTRIBUTES; ++i) { | ||
attributeBindings[i] = new AttributeRecord() | ||
} | ||
function bindAttribute (index, current, stack, size) { | ||
var next = stack.records[stack.top] | ||
size = next.size || size | ||
if (current.equals(next, size)) { | ||
return | ||
} | ||
if (!next.pointer) { | ||
if (current.pointer) { | ||
gl.disableVertexAttribArray(index) | ||
} | ||
gl.vertexAttrib4f(index, next.x, next.y, next.z, next.w) | ||
} else { | ||
if (!current.pointer) { | ||
gl.enableVertexAttribArray(index) | ||
} | ||
if (current.buffer !== next.buffer) { | ||
next.buffer.bind() | ||
} | ||
gl.vertexAttribPointer( | ||
index, | ||
size, | ||
next.type, | ||
next.normalized, | ||
next.stride, | ||
next.offset) | ||
if (INSTANCING) { | ||
INSTANCING.vertexAttribDivisorANGLE(index, next.divisor) | ||
} | ||
} | ||
current.set(next, size) | ||
} | ||
// =================================================== | ||
// state diffing/polling | ||
// =================================================== | ||
function compileShaderPoll (program) { | ||
var env = createEnvironment() | ||
var link = env.link | ||
var poll = env.proc('poll') | ||
var GL = link(gl) | ||
var PROGRAM = link(program.program) | ||
var BIND_ATTRIBUTE = link(bindAttribute) | ||
// bind the program | ||
poll(GL, '.useProgram(', PROGRAM, ');') | ||
// set up attribute state | ||
program.attributes.forEach(function (attribute) { | ||
poll(BIND_ATTRIBUTE, '(', | ||
attribute.location, ',', | ||
link(attributeBindings[attribute.location]), ',', | ||
link(attributeState[attribute.name]), ',', | ||
typeLength(attribute.info.type), ');') | ||
}) | ||
// set up uniforms | ||
program.uniforms.forEach(function (uniform) { | ||
var LOCATION = link(uniform.location) | ||
var STACK = link(uniformState[uniform.name]) | ||
var TOP = STACK + '[' + STACK + '.length-1]' | ||
var infix | ||
var separator = ',' | ||
switch (uniform.info.type) { | ||
case GL_FLOAT: | ||
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: | ||
case GL_INT: | ||
infix = '1i' | ||
break | ||
case GL_BOOL_VEC2: | ||
case GL_INT_VEC2: | ||
infix = '2i' | ||
break | ||
case GL_BOOL_VEC3: | ||
case GL_INT_VEC3: | ||
infix = '3i' | ||
break | ||
case GL_BOOL_VEC4: | ||
case GL_INT_VEC4: | ||
infix = '4i' | ||
break | ||
case GL_FLOAT_MAT2: | ||
infix = 'Matrix2f' | ||
separator = ',true,' | ||
break | ||
case GL_FLOAT_MAT3: | ||
infix = 'Matrix3f' | ||
separator = ',true,' | ||
break | ||
case GL_FLOAT_MAT4: | ||
infix = 'Matrix4f' | ||
separator = ',true,' | ||
break | ||
default: | ||
check.raise('unsupported uniform type') | ||
} | ||
poll(GL, '.uniform', infix, 'v(', LOCATION, separator, TOP, ');') | ||
}) | ||
return env.compile().poll | ||
} | ||
// =================================================== | ||
// context management | ||
// =================================================== | ||
var programState = [null] | ||
function poll () { | ||
var activeShader = programState[programState.length - 1] | ||
if (activeShader) { | ||
activeShader.poll() | ||
} else { | ||
gl.useProgram(null) | ||
} | ||
} | ||
// =================================================== | ||
@@ -507,15 +212,8 @@ // context management | ||
create: getProgram, | ||
clear: clear, | ||
refresh: refresh, | ||
poll: poll, | ||
programs: programState, | ||
uniforms: uniformState, | ||
defUniform: defUniform, | ||
attributes: attributeState, | ||
defAttribute: defAttribute | ||
fragShaders: fragShaders, | ||
vertShaders: vertShaders | ||
} | ||
} |
@@ -39,16 +39,11 @@ // A stack for managing the state of a scalar/vector parameter | ||
push: function () { | ||
var ptr = stack.length - n | ||
for (var i = 0; i < n; ++i) { | ||
var value = arguments[i] | ||
dirty = dirty || (stack[ptr + i] === value) | ||
stack.push(value) | ||
stack.push(arguments[i]) | ||
} | ||
dirty = true | ||
}, | ||
pop: function () { | ||
var ptr = stack.length - 2 * n | ||
for (var i = 0; i < n; ++i) { | ||
var top = stack.pop() | ||
dirty = dirty || top === stack[ptr + i] | ||
} | ||
stack.length -= n | ||
dirty = true | ||
}, | ||
@@ -55,0 +50,0 @@ |
495
lib/state.js
var createStack = require('./stack') | ||
var check = require('./check') | ||
var createEnvironment = require('./codegen') | ||
var primTypes = require('./constants/primitives.json') | ||
var glTypes = require('./constants/dtypes.json') | ||
@@ -26,20 +23,6 @@ // WebGL constants | ||
var GL_KEEP = 7680 | ||
var GL_TRIANGLES = 4 | ||
var GL_FLOAT = 5126 | ||
var DEFAULT_FRAG_SHADER = 'void main(){gl_FragColor=vec4(0,0,0,0);}' | ||
var DEFAULT_VERT_SHADER = 'void main(){gl_Position=vec4(0,0,0,0);}' | ||
module.exports = function wrapContextState ( | ||
gl, | ||
extensions, | ||
shaderState, | ||
bufferState, | ||
textureState, | ||
fboState) { | ||
var extInstancing = extensions.extensions.angle_instanced_arrays | ||
var hasInstancing = !!extInstancing | ||
function capStack (cap) { | ||
return createStack([false], function (flag) { | ||
module.exports = function wrapContextState (gl, shaderState) { | ||
function capStack (cap, dflt) { | ||
var result = createStack([!!dflt], function (flag) { | ||
if (flag) { | ||
@@ -51,28 +34,25 @@ gl.enable(cap) | ||
}) | ||
result.flag = cap | ||
return result | ||
} | ||
// Draw state | ||
var primitiveState = [ GL_TRIANGLES ] | ||
var countState = [ 0 ] | ||
var offsetState = [ 0 ] | ||
var instancesState = [ 0 ] | ||
var viewportState = { | ||
width: 0, | ||
height: 0 | ||
} | ||
// Caps, flags and other random WebGL context state | ||
var contextState = { | ||
// Caps | ||
cull: capStack(GL_CULL_FACE), | ||
blend: capStack(GL_BLEND), | ||
dither: capStack(GL_DITHER), | ||
stencilTest: capStack(GL_STENCIL_TEST), | ||
depthTest: capStack(GL_DEPTH_TEST), | ||
scissorTest: capStack(GL_SCISSOR_TEST), | ||
polygonOffsetFill: capStack(GL_POLYGON_OFFSET_FILL), | ||
sampleAlpha: capStack(GL_SAMPLE_ALPHA_TO_COVERAGE), | ||
sampleCoverage: capStack(GL_SAMPLE_COVERAGE), | ||
// Dithering | ||
'dither': capStack(GL_DITHER), | ||
// Blending | ||
blendEquation: createStack([GL_FUNC_ADD, GL_FUNC_ADD], function (rgb, a) { | ||
'blend.enable': capStack(GL_BLEND), | ||
'blend.color': createStack([0, 0, 0, 0], function (r, g, b, a) { | ||
gl.blendColor(r, g, b, a) | ||
}), | ||
'blend.equation': createStack([GL_FUNC_ADD, GL_FUNC_ADD], function (rgb, a) { | ||
gl.blendEquationSeparate(rgb, a) | ||
}), | ||
blendFunc: createStack([ | ||
'blend.func': createStack([ | ||
GL_ONE, GL_ZERO, GL_ONE, GL_ZERO | ||
@@ -84,14 +64,21 @@ ], function (srcRGB, dstRGB, srcAlpha, dstAlpha) { | ||
// Depth | ||
depthFunc: createStack([GL_LESS], function (func) { | ||
'depth.enable': capStack(GL_DEPTH_TEST, true), | ||
'depth.func': createStack([GL_LESS], function (func) { | ||
gl.depthFunc(func) | ||
}), | ||
depthRange: createStack([0, 1], function (near, far) { | ||
'depth.range': createStack([0, 1], function (near, far) { | ||
gl.depthRange(near, far) | ||
}), | ||
'depth.mask': createStack([true], function (m) { | ||
gl.depthMask(m) | ||
}), | ||
// Face culling | ||
cullFace: createStack([GL_BACK], function (mode) { | ||
'cull.enable': capStack(GL_CULL_FACE), | ||
'cull.face': createStack([GL_BACK], function (mode) { | ||
gl.cullFace(mode) | ||
}), | ||
frontFace: createStack([GL_CCW], function (mode) { | ||
// Front face orientation | ||
'frontFace': createStack([GL_CCW], function (mode) { | ||
gl.frontFace(mode) | ||
@@ -101,15 +88,8 @@ }), | ||
// Write masks | ||
colorMask: createStack([true, true, true, true], function (r, g, b, a) { | ||
'colorMask': createStack([true, true, true, true], function (r, g, b, a) { | ||
gl.colorMask(r, g, b, a) | ||
}), | ||
depthMask: createStack([true], function (m) { | ||
gl.depthMask(m) | ||
}), | ||
stencilMask: createStack([-1, -1], function (front, back) { | ||
gl.stencilMask(GL_FRONT, front) | ||
gl.stencilMask(GL_BACK, back) | ||
}), | ||
// Line width | ||
lineWidth: createStack([1], function (w) { | ||
'lineWidth': createStack([1], function (w) { | ||
gl.lineWidth(w) | ||
@@ -119,3 +99,4 @@ }), | ||
// Polygon offset | ||
polygonOffset: createStack([0, 0], function (factor, units) { | ||
'polygonOffset.enable': capStack(GL_POLYGON_OFFSET_FILL), | ||
'polygonOffset.offset': createStack([0, 0], function (factor, units) { | ||
gl.polygonOffset(factor, units) | ||
@@ -125,3 +106,5 @@ }), | ||
// Sample coverage | ||
sampleCoverageParams: createStack([1, false], function (value, invert) { | ||
'sample.alpha': capStack(GL_SAMPLE_ALPHA_TO_COVERAGE), | ||
'sample.enable': capStack(GL_SAMPLE_COVERAGE), | ||
'sample.coverage': createStack([1, false], function (value, invert) { | ||
gl.sampleCoverage(value, invert) | ||
@@ -131,373 +114,73 @@ }), | ||
// Stencil | ||
stencilFunc: createStack([ | ||
GL_ALWAYS, 0, -1, | ||
'stencil.enable': capStack(GL_STENCIL_TEST), | ||
'stencil.mask': createStack([-1], function (mask) { | ||
gl.stencilMask(mask) | ||
}), | ||
'stencil.func': createStack([ | ||
GL_ALWAYS, 0, -1 | ||
], function (frontFunc, frontRef, frontMask, | ||
backFunc, backRef, backMask) { | ||
gl.stencilFuncSeparate(GL_FRONT, frontFunc, frontRef, frontMask) | ||
gl.stencilFuncSeparate(GL_BACK, backFunc, backRef, backMask) | ||
], function (func, ref, mask) { | ||
gl.stencilFunc(func, ref, mask) | ||
}), | ||
stencilOp: createStack([ | ||
GL_KEEP, GL_KEEP, GL_KEEP, | ||
'stencil.opFront': createStack([ | ||
GL_KEEP, GL_KEEP, GL_KEEP | ||
], function (frontFail, frontDPFail, frontPass, | ||
backFail, backDPFail, backPass) { | ||
gl.stencilOpSeparate(GL_FRONT, frontFail, frontDPFail, frontPass) | ||
gl.stencilOpSeparate(GL_BACK, backFail, backDPFail, backPass) | ||
], function (fail, zfail, pass) { | ||
gl.stencilOpSeparate(GL_FRONT, fail, zfail, pass) | ||
}), | ||
'stencil.opBack': createStack([ | ||
GL_KEEP, GL_KEEP, GL_KEEP | ||
], function (fail, zfail, pass) { | ||
gl.stencilOpSeparate(GL_BACK, fail, zfail, pass) | ||
}), | ||
// Scissor | ||
scissor: createStack([0, 0, -1, -1], function (x, y, w, h) { | ||
gl.scissor( | ||
x, y, | ||
w < 0 ? gl.drawingBufferWidth : w, | ||
h < 0 ? gl.drawingBufferHeight : h) | ||
'scissor.enable': capStack(GL_SCISSOR_TEST), | ||
'scissor.box': createStack([0, 0, -1, -1], function (x, y, w, h) { | ||
var w_ = w | ||
if (w < 0) { | ||
w_ = gl.drawingBufferWidth - x | ||
} | ||
var h_ = h | ||
if (h < 0) { | ||
h_ = gl.drawingBufferHeight - y | ||
} | ||
gl.scissor(x, y, w_, h_) | ||
}), | ||
// Viewport | ||
viewport: createStack([0, 0, -1, -1], function (x, y, w, h) { | ||
gl.viewport( | ||
x, y, | ||
w < 0 ? gl.drawingBufferWidth : w, | ||
h < 0 ? gl.drawingBufferHeight : h) | ||
}) | ||
// TODO: textures | ||
// TODO: fbos | ||
// TODO: extensions | ||
} | ||
var contextProps = Object.keys(contextState) | ||
function refreshState () { | ||
contextProps.forEach(function (state) { | ||
contextState[state].setDirty() | ||
}) | ||
} | ||
// ========================================================== | ||
// Update state bindings | ||
// ========================================================== | ||
function poll () { | ||
shaderState.poll() | ||
contextProps.forEach(function (state) { | ||
contextState[state].poll() | ||
}) | ||
} | ||
// ========================================================== | ||
// Partially evaluate a draw command | ||
// ========================================================== | ||
function partialEvaluateCommand ( | ||
staticOptions, staticUniforms, staticAttributes, | ||
dynamicOptions, dynamicUniforms, dynamicAttributes, | ||
hasDynamic) { | ||
// Create code generation environment | ||
var env = createEnvironment() | ||
var link = env.link | ||
var block = env.block | ||
var proc = env.proc | ||
// Helper functions | ||
function stackTop (x) { | ||
return x + '[' + x + '.length-1]' | ||
} | ||
// ------------------------------- | ||
// Common state variables | ||
// ------------------------------- | ||
var GL = link(gl) | ||
var POLL = link(poll) | ||
var PROGRAM_STATE = link(shaderState.programs) | ||
var DRAW_STATE = { | ||
count: link(countState), | ||
offset: link(offsetState), | ||
instances: link(instancesState), | ||
primitive: link(primitiveState) | ||
} | ||
var CONTEXT_STATE = {} | ||
function linkContext (x) { | ||
var result = CONTEXT_STATE[x] | ||
if (result) { | ||
return result | ||
'viewport': createStack([0, 0, -1, -1], function (x, y, w, h) { | ||
var w_ = w | ||
if (w < 0) { | ||
w_ = gl.drawingBufferWidth - x | ||
} | ||
result = CONTEXT_STATE[x] = link(contextState[x]) | ||
return result | ||
} | ||
// ========================================================== | ||
// STATIC STATE | ||
// ========================================================== | ||
// Code blocks for the static sections | ||
var entry = block() | ||
var exit = block() | ||
// ------------------------------- | ||
// update default context state variables | ||
// ------------------------------- | ||
function handleStaticOption (param, value) { | ||
var STATE_STACK = linkContext(param) | ||
entry(STATE_STACK, '.push(', value, ');') | ||
exit(STATE_STACK, '.pop();') | ||
} | ||
var hasShader = false | ||
Object.keys(staticOptions).forEach(function (param) { | ||
switch (param) { | ||
case 'frag': | ||
case 'vert': | ||
hasShader = true | ||
break | ||
// Update draw state | ||
case 'count': | ||
case 'offset': | ||
case 'instances': | ||
var value = staticOptions[param] | ||
check.nni(value, param) | ||
entry(DRAW_STATE[param], '.push(', value, ');') | ||
exit(DRAW_STATE[param], '.pop();') | ||
break | ||
// Update primitive type | ||
case 'primitive': | ||
check.parameter(staticOptions[param], primTypes, | ||
'not a valid drawing primitive') | ||
var primType = primTypes[staticOptions[param]] | ||
entry(DRAW_STATE.primitive, '.push(', primType, ');') | ||
exit(DRAW_STATE.primitive, '.pop();') | ||
break | ||
// Caps | ||
case 'cull': | ||
case 'blend': | ||
case 'dither': | ||
case 'stencilTest': | ||
case 'depthTest': | ||
case 'scissorTest': | ||
case 'polygonOffsetFill': | ||
case 'sampleAlpha': | ||
case 'sampleCoverage': | ||
case 'stencilMask': | ||
case 'depthMask': | ||
var flag = staticOptions[param] | ||
check.type(flag, 'boolean', param) | ||
handleStaticOption(param, staticOptions[param]) | ||
break | ||
// TODO Handle the rest of the state values here | ||
default: | ||
// TODO Should this just be a warning instead? | ||
check.raise('unsupported parameter ' + param) | ||
break | ||
var h_ = h | ||
if (h < 0) { | ||
h_ = gl.drawingBufferHeight - y | ||
} | ||
gl.viewport(x, y, w_, h_) | ||
viewportState.width = w_ | ||
viewportState.height = h_ | ||
}) | ||
} | ||
// ------------------------------- | ||
// update shader program | ||
// ------------------------------- | ||
var program | ||
if (hasShader) { | ||
var fragSrc = staticOptions.frag || DEFAULT_FRAG_SHADER | ||
var vertSrc = staticOptions.vert || DEFAULT_VERT_SHADER | ||
program = shaderState.create(vertSrc, fragSrc) | ||
entry(PROGRAM_STATE, '.push(', link(program), ');') | ||
exit(PROGRAM_STATE, '.pop();') | ||
} | ||
var env = createEnvironment() | ||
var poll = env.proc('poll') | ||
var refresh = env.proc('refresh') | ||
Object.keys(contextState).forEach(function (prop) { | ||
var STACK = env.link(contextState[prop]) | ||
poll(STACK, '.poll();') | ||
refresh(STACK, '.setDirty();') | ||
}) | ||
var procs = env.compile() | ||
// ------------------------------- | ||
// update static uniforms | ||
// ------------------------------- | ||
Object.keys(staticUniforms).forEach(function (uniform) { | ||
shaderState.defUniform(uniform) | ||
var STACK = link(shaderState.uniforms[uniform]) | ||
var VALUE | ||
var value = staticUniforms[uniform] | ||
if (Array.isArray(value)) { | ||
VALUE = link(value.slice()) | ||
} else { | ||
VALUE = link([value]) | ||
} | ||
entry(STACK, '.push(', VALUE, ');') | ||
exit(STACK, '.pop();') | ||
}) | ||
return { | ||
contextState: contextState, | ||
viewport: viewportState, | ||
poll: procs.poll, | ||
refresh: procs.refresh, | ||
// ------------------------------- | ||
// update default attributes | ||
// ------------------------------- | ||
Object.keys(staticAttributes).forEach(function (attribute) { | ||
shaderState.defAttribute(attribute) | ||
var ATTRIBUTE = link(shaderState.attributes[attribute]) | ||
var data = staticAttributes[attribute] | ||
if (typeof data === 'number') { | ||
entry(ATTRIBUTE, '.pushVec(', +data, ',0,0,0);') | ||
} else { | ||
check(!!data, 'invalid attribute: ' + attribute) | ||
if (Array.isArray(data)) { | ||
entry( | ||
ATTRIBUTE, '.pushVec(', | ||
[data[0] || 0, data[1] || 0, data[2] || 0, data[3] || 0], ');') | ||
} else { | ||
var buffer = bufferState.getBuffer(data) | ||
var size = 0 | ||
var stride = 0 | ||
var offset = 0 | ||
var divisor = 0 | ||
var normalized = false | ||
var type = GL_FLOAT | ||
if (!buffer) { | ||
check.type(data, 'object', 'invalid attribute "' + attribute + '"') | ||
buffer = bufferState.getBuffer(data.buffer) | ||
size = data.size || 0 | ||
stride = data.stride || 0 | ||
offset = data.offset || 0 | ||
divisor = data.divisor || 0 | ||
normalized = data.normalized || false | ||
check(!!buffer, 'invalid attribute ' + attribute + '.buffer') | ||
// Check for user defined type overloading | ||
type = buffer.dtype | ||
if ('type' in data) { | ||
check.parameter(data.type, glTypes, 'attribute type') | ||
type = glTypes[data.type] | ||
} | ||
} else { | ||
type = buffer.dtype | ||
} | ||
check(!!buffer, 'invalid attribute ' + attribute + '.buffer') | ||
check.nni(stride, attribute + '.stride') | ||
check.nni(offset, attribute + '.offset') | ||
check.nni(divisor, attribute + '.divisor') | ||
check.type(normalized, 'boolean', attribute + '.normalized') | ||
check.oneOf(size, [0, 1, 2, 3, 4], attribute + '.size') | ||
entry( | ||
ATTRIBUTE, '.pushPtr(', [ | ||
link(buffer), size, offset, stride, | ||
divisor, normalized, type | ||
].join(), ');') | ||
} | ||
} | ||
exit(ATTRIBUTE, '.pop();') | ||
}) | ||
// ========================================================== | ||
// DYNAMIC STATE | ||
// ========================================================== | ||
// Generated code blocks for dynamic state flags | ||
var dynamicEntry = env.block() | ||
var dynamicExit = env.block() | ||
// ------------------------------- | ||
// dynamic context state variables | ||
// ------------------------------- | ||
Object.keys(dynamicOptions).forEach(function (param) { | ||
switch (param) { | ||
// Caps | ||
case 'cull': | ||
case 'blend': | ||
case 'dither': | ||
case 'stencilTest': | ||
case 'depthTest': | ||
case 'scissorTest': | ||
case 'polygonOffsetFill': | ||
case 'sampleAlpha': | ||
case 'sampleCoverage': | ||
case 'stencilMask': | ||
case 'depthMask': | ||
break | ||
default: | ||
break | ||
} | ||
}) | ||
// ------------------------------- | ||
// dynamic uniforms | ||
// ------------------------------- | ||
// ------------------------------- | ||
// dynamic attributes | ||
// ------------------------------- | ||
// ========================================================== | ||
// SCOPE PROCEDURE | ||
// ========================================================== | ||
var scope = proc('scope') | ||
var BODY = scope.arg() | ||
scope( | ||
entry, | ||
hasDynamic ? dynamicEntry : '', | ||
BODY, '();', | ||
hasDynamic ? dynamicExit : '', | ||
exit) | ||
// ========================================================== | ||
// DRAW PROCEDURE | ||
// ========================================================== | ||
var draw = proc('draw') | ||
draw( | ||
entry, | ||
hasDynamic ? dynamicEntry : '', | ||
POLL, '();') | ||
// Generate draw command | ||
var CUR_PRIMITIVE = stackTop(DRAW_STATE.primitive) | ||
var CUR_COUNT = stackTop(DRAW_STATE.count) | ||
var CUR_OFFSET = stackTop(DRAW_STATE.offset) | ||
if (hasInstancing) { | ||
var CUR_INSTANCES = draw.def(stackTop(DRAW_STATE.instances)) | ||
draw( | ||
'if(', CUR_INSTANCES, '>0){', | ||
// then | ||
GL, '.drawArraysInstancedANGLE(', | ||
CUR_PRIMITIVE, ',', | ||
CUR_OFFSET, ',', | ||
CUR_COUNT, ',', | ||
CUR_INSTANCES, ');}else{', | ||
// else | ||
GL, '.drawArrays(', | ||
CUR_PRIMITIVE, ',', | ||
CUR_OFFSET, ',', | ||
CUR_COUNT, ');}') | ||
} else { | ||
draw( | ||
GL, '.drawArrays(', | ||
CUR_PRIMITIVE, ',', | ||
CUR_OFFSET, ',', | ||
CUR_COUNT, ');') | ||
notifyViewportChanged: function () { | ||
contextState.viewport.setDirty() | ||
contextState['scissor.box'].setDirty() | ||
} | ||
draw( | ||
hasDynamic ? dynamicExit : '', | ||
exit) | ||
// ========================================================== | ||
// BATCH DRAW | ||
// ========================================================== | ||
if (hasDynamic) { | ||
// TODO | ||
} | ||
// ------------------------------- | ||
// eval and bind | ||
// ------------------------------- | ||
return env.compile() | ||
} | ||
return { | ||
create: partialEvaluateCommand, | ||
refresh: refreshState | ||
} | ||
} |
@@ -1,2 +0,60 @@ | ||
module.exports = function createTextureSet (gl) { | ||
var check = require('./check') | ||
var GL_TEXTURE_2D = 0x0DE1 | ||
var GL_DEPTH_COMPONENT = 0x1902 | ||
var GL_ALPHA = 0x1906 | ||
var GL_RGB = 0x1907 | ||
var GL_RGBA = 0x1908 | ||
var GL_LUMINANCE = 0x1909 | ||
var GL_LUMINANCE_ALPHA = 0x190A | ||
var GL_UNSIGNED_BYTE = 0x1401 | ||
var GL_UNSIGNED_SHORT = 0x1403 | ||
var GL_FLOAT = 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_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 wrapModes = { | ||
'repeat': GL_REPEAT, | ||
'clamp': GL_CLAMP_TO_EDGE, | ||
'mirror': GL_MIRRORED_REPEAT | ||
} | ||
var magFilters = { | ||
'nearest': GL_NEAREST, | ||
'linear': GL_LINEAR | ||
} | ||
var minFilters = Object.assign({ | ||
'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, | ||
'mipmap': GL_LINEAR_MIPMAP_LINEAR | ||
}, magFilters) | ||
module.exports = function createTextureSet (gl, extensionState) { | ||
var extensions = extensionState.extensions | ||
var textureCount = 0 | ||
@@ -7,2 +65,31 @@ var textureSet = {} | ||
this.id = textureCount++ | ||
// Texture target | ||
this.target = GL_TEXTURE_2D | ||
// Texture handle | ||
this.texture = null | ||
// Texture format | ||
this.format = GL_RGBA | ||
this.type = GL_UNSIGNED_BYTE | ||
// Data | ||
this.mipLevels = [] | ||
// Shape | ||
this.width = 0 | ||
this.height = 0 | ||
// Parameters | ||
this.minFilter = GL_NEAREST | ||
this.magFilter = GL_NEAREST | ||
this.wrapS = GL_REPEAT | ||
this.wrapT = GL_REPEAT | ||
this.mipSamples = 0 | ||
// Storage flags | ||
this.flipY = false | ||
this.premultiplyAlpha = false | ||
this.colorSpace = GL_BROWSER_DEFAULT_WEBGL | ||
} | ||
@@ -14,9 +101,54 @@ | ||
update: function (option) { | ||
update: function (args) { | ||
var options = args || {} | ||
// Possible initialization pathways: | ||
if (Array.isArray(args) || | ||
isTypedArray(args) || | ||
isHTMLElement(args)) { | ||
options = { | ||
data: args | ||
} | ||
} | ||
var data = options.data || null | ||
var width = options.width || 0 | ||
var height = options.height || 0 | ||
var format = options.format || 'rgba' | ||
this.minFilter = GL_NEAREST | ||
if ('min' in options) { | ||
check.param(options.min, minFilters) | ||
this.minFilter = minFilters[options.min] | ||
} | ||
this.magFilter = GL_NEAREST | ||
if ('mag' in options) { | ||
check.param(options.mag, magFilters) | ||
this.magFilter = magFilters(options.mag) | ||
} | ||
if (Array.isArray(data)) { | ||
} else if (isTypedArray(data)) { | ||
} else if (isHTMLElement(data)) { | ||
} | ||
// Set tex image | ||
}, | ||
refresh: function () { | ||
gl.textureParameteri(GL_TEXTURE_MIN_FILTER, this.minFilter) | ||
gl.textureParameteri(GL_TEXTURE_MAG_FILTER, this.magFilter) | ||
gl.textureParameteri(GL_TEXTURE_WRAP_T, this.wrapT) | ||
gl.textureParameteri(GL_TEXTURE_WRAP_S, this.wrapS) | ||
}, | ||
destroy: function () { | ||
check(this.texture, 'must not double free texture') | ||
gl.deleteTexture(this.texture) | ||
this.texture = null | ||
delete textureSet[this.id] | ||
} | ||
@@ -26,3 +158,18 @@ }) | ||
function createTexture (options) { | ||
return null | ||
var texture = new REGLTexture() | ||
texture.texture = gl.createTexture() | ||
texture.update(options) | ||
textureSet[texture.id] = texture | ||
function updateTexture (options) { | ||
texture.update(options) | ||
return updateTexture | ||
} | ||
updateTexture._texture = texture | ||
updateTexture.destroy = function () { | ||
texture.destroy() | ||
} | ||
return updateTexture | ||
} | ||
@@ -45,3 +192,3 @@ | ||
refresh: refreshTextures, | ||
destroy: destroyTextures, | ||
clear: destroyTextures, | ||
getTexture: function (wrapper) { | ||
@@ -48,0 +195,0 @@ return null |
{ | ||
"name": "regl", | ||
"version": "0.0.0", | ||
"description": "Dataflow programming for WebGL", | ||
"version": "0.1.0", | ||
"description": "WebGL", | ||
"main": "regl.js", | ||
@@ -9,11 +9,29 @@ "directories": { | ||
}, | ||
"dependencies": {}, | ||
"devDependencies": { | ||
"browserify": "^13.0.0", | ||
"budo": "^8.1.0", | ||
"coverify": "^1.4.1", | ||
"derequire": "^2.0.3", | ||
"faucet": "0.0.1", | ||
"gl": "^3.0.3", | ||
"indexhtmlify": "^1.2.1", | ||
"runscript": "^1.1.0", | ||
"smokestack": "^3.4.1", | ||
"snazzy": "^3.0.0", | ||
"standard": "^6.0.7", | ||
"tape": "^4.4.0" | ||
"tap-browser-color": "^0.1.2", | ||
"tape": "^4.4.0", | ||
"uglify-js": "^2.6.2" | ||
}, | ||
"dependencies": { | ||
"gl-format-compiler-error": "^1.0.2" | ||
}, | ||
"scripts": { | ||
"test": "standard && tape test/*.js" | ||
"test": "standard | snazzy && tape test/*.js | faucet", | ||
"test-browser": "budo test/util/browser.js --open", | ||
"coverage": "browserify test/util/browser.js -t coverify | smokestack | coverify", | ||
"bench": "budo bench/index.js --open", | ||
"build": "npm run build-script && npm run build-min && npm run build-bench && npm run build-gallery", | ||
"build-script": "browserify regl.js -s regl | derequire > dist/regl.js", | ||
"build-min": "uglifyjs < dist/regl.js > dist/regl.min.js", | ||
"build-bench": "browserify bench/index.js | indexhtmlify > www/bench.html", | ||
"build-gallery": "node bin/build-gallery.js" | ||
}, | ||
@@ -30,3 +48,3 @@ "repository": { | ||
"graphics", | ||
"cg", | ||
"computer graphics", | ||
"opengl", | ||
@@ -44,3 +62,6 @@ "glsl", | ||
}, | ||
"homepage": "https://github.com/mikolalysenko/regl#readme" | ||
"standard": { | ||
"ignore": "dist/*" | ||
}, | ||
"homepage": "https://mikolalysenko.github.io/regl" | ||
} |
# New abstractions for WebGL | ||
The high level goal of stack.gl is to deconstruct a 3D engine into reusable, interchangeable, composable modules. More precisely, we mean that a module is, | ||
The high level goal of stack.gl is to deconstruct a 3D engine into modules. Ideally these modules should be | ||
* *Reusable* if it can be extracted from its original environment and used again | ||
* *Interchangeable* if it can be replaced with a module having an equivalent interface | ||
* *Composable* if it is composed itself of simpler, smaller modules | ||
* *Reusable*: if it can be extracted from its original environment and used again | ||
* *Interchangeable*: if it can be replaced with a module having an equivalent interface | ||
* *Composable*: if it is composed itself of simpler, smaller modules | ||
@@ -81,3 +81,3 @@ stack.gl is a loosely coupled collection of modules that communicate using standard interfaces. At a high level, there are three basic parts of stack.gl, | ||
## Rendering as change detection | ||
## Functional rendering | ||
Thinking in terms of data flow reframes the problem of rendering as detecting changes in the properties of the `render()` function. | ||
@@ -84,0 +84,0 @@ |
174
README.md
# regl | ||
[![Circle CI](https://circleci.com/gh/mikolalysenko/regl.svg?style=svg)](https://circleci.com/gh/mikolalysenko/regl) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/) | ||
This repo is an attempt at building some new functional abstractions for working with WebGL. It is still pretty experimental right now, so expect things to change a lot in the near future. If you want to know more about why I am writing this thing, take a look at the [rationale](RATIONALE.md). | ||
This repo is an attempt at building new functional abstractions for working with WebGL. It is still **experimental**, so expect things to change a lot in the near future! If you want to know more about why I am writing this thing and why it looks the way it does, take a look at the [rationale](RATIONALE.md). | ||
## Some sketches | ||
### Why use regl | ||
In regl, you write functions which transform data into sequences of WebGL draw calls. These functions are then partially evaluated at run time into optimized JavaScript code. Here is a sketch of how this might look: | ||
`regl` offers the following advantages over raw WebGL code: | ||
* **Just one function** | ||
* **Less state** Draw commands in regl are self contained, so you don't have to worry about some other weird subroutine breaking your rendering code | ||
* **No `bind()`** In regl, shaders, buffers, textures and fbos are specified declaratively, so there is no need to ever `bind()` them or unbind them. | ||
* **Fewer silent failure** If you pass incorrect parameters to some WebGL method, the default behavior is to set an error code and continue on. Because `regl` commands have more structure, we can do more validation up front without the run time performance cost. | ||
* **Sane defaults** Many WebGL APIs have redundant or outright broken parameters (for example `border` in `gl.texImage2D` or `transpose` in `gl.uniformMatrix4fv`). `regl` wraps these APIs in such a way that you will never have to see this mess. | ||
## Simple example | ||
In `regl`, there are two fundamental abstractions, **resources** and **commands**: | ||
* A **resource** is a handle to a GPU resident object, like a texture, FBO or buffer. | ||
* A **command** is a complete representation of the WebGL state required to perform some draw call. | ||
To define a command you specify a mixture of static and dynamic data for the object. Once this is done, `regl` takes this description and then compiles it into optimized JavaScript code. For example, here is a simple `regl` program to draw a colored triangle: | ||
```JavaScript | ||
// NOTE: This doesn't work yet, it is just for illustration | ||
// Calling the regl module with no arguments creates a full screen canvas and | ||
// WebGL context, and then uses this context to initialize a new REGL instance | ||
const regl = require('regl')() | ||
// This creates a new partially evaluated draw call. We flag the dynamic | ||
// parts of the draw call using the special `regl.dynamic` variable | ||
const draw = regl({ | ||
// Calling regl() creates a new partially evaluated draw command | ||
const drawTriangle = regl({ | ||
// Shaders in regl are just strings. You can use glslify or whatever you want | ||
// to define them. No need to manually create shader objects. | ||
frag: ` | ||
@@ -31,15 +49,23 @@ precision mediump float; | ||
// Here we define the vertex attributes for the above shader | ||
attributes: { | ||
position: regl.buffer(new Float32Array([-2, -2, 4, -2, 4, 4])) | ||
// regl.buffer creates a new array buffer object | ||
position: regl.buffer([ | ||
[-2, -2], // no need to flatten nested arrays, regl automatically | ||
[4, -2], // unrolls them into a typedarray (default Float32) | ||
[4, 4] | ||
])) | ||
// regl automatically infers sane defaults for the vertex attribute pointers | ||
}, | ||
uniforms: { | ||
// This makes the color uniform dynamic | ||
color: regl.dynamic | ||
// This defines the color of the triangle to be a dynamic variable | ||
color: regl.prop('color') | ||
}, | ||
// This tells regl the number of vertices to draw in this command | ||
count: 3 | ||
}).draw | ||
}) | ||
function render() { | ||
regl.frame(() => { | ||
// clear contents of the drawing buffer | ||
@@ -51,65 +77,111 @@ regl.clear({ | ||
// draw a triangle | ||
// draw a triangle using the command defined above | ||
drawTriangle({ | ||
uniforms: { | ||
color: [ | ||
Math.cos(Date.now() * 0.001), | ||
Math.sin(Date.now() * 0.0008), | ||
Math.cos(Date.now() * 0.003), | ||
1 | ||
] | ||
} | ||
color: [ | ||
Math.cos(Date.now() * 0.001), | ||
Math.sin(Date.now() * 0.0008), | ||
Math.cos(Date.now() * 0.003), | ||
1 | ||
] | ||
}) | ||
// schedule next render event | ||
requestAnimationFrame(render) | ||
} | ||
render() | ||
}) | ||
``` | ||
# API (WORK IN PROGRESS) | ||
## More examples | ||
## Constructors | ||
[Check out the demo gallery](https://mikolalysenko.github.io/regl/www/gallery.html) | ||
#### `var regl = require('regl')([options])` | ||
## Setup | ||
#### `var regl = require('regl')(element, [options])` | ||
regl has no dependencies, so setting it up is pretty easy | ||
#### `var regl = require('regl')(canvas, [options])` | ||
#### Live editing | ||
To try out regl right away, you can use [RequireBin](http://requirebin.com/) or [codepen](http://codepen.io/). The following links should help get you started: | ||
#### `var regl = require('regl')(gl, [options])` | ||
* requirebin | ||
* codepen | ||
## Resources | ||
#### npm | ||
The easiest way to use `regl` in a project is via [npm](http://npmjs.com). Once you have node set up, you can install and use `regl` in your project using the following command: | ||
### Resource constructors | ||
```sh | ||
npm i -S regl | ||
``` | ||
#### `regl.buffer(options)` | ||
For more info on how to use npm, [check out the official docs](https://docs.npmjs.com/). | ||
#### `regl.texture(options)` | ||
#### Standalone script tag | ||
You can also use `regl` as a standalone script if you are really stubborn. The most recent versions can be found under the [releases tab](releases). To do this, add the following script tag to your HTML: | ||
#### `regl.fbo(options)` | ||
```html | ||
<script src="[some cdn tbd]/regl.min.js"></script> | ||
``` | ||
### Resource methods | ||
## Comparisons | ||
#### `resource(options)` | ||
Updates a resource | ||
TODO implement spinning textured cube in each of the following frameworks | ||
#### `resource.destroy()` | ||
Destroy resource | ||
* vs WebGL | ||
* vs gl-* modules from stack.gl | ||
* vs TWGL | ||
* vs THREE.js | ||
## Rendering | ||
## Benchmarks | ||
You can run benchmarks locally using `npm run bench` or check them out here: | ||
#### `regl(options)` | ||
* [Interactive benchmarks](https://mikolalysenko.github.io/regl/www/bench.html) | ||
## Clean up | ||
## [API](API.md) | ||
* [Initialization](API.md#initialization) | ||
* [As a fullscreen canvas](API.md#as-a-fullscreen-canvas) | ||
* [From a container div](API.md#from-a-container-div) | ||
* [From a canvas](API.md#from-a-canvas) | ||
* [From a WebGL context](API.md#from-a-webgl-context) | ||
* [Commands](API.md#commands) | ||
+ [Dynamic properties](API.md#dynamic-properties) | ||
+ [Executing commands](API.md#executing-commands) | ||
- [One-shot rendering](API.md#one-shot-rendering) | ||
- [Batch rendering](API.md#batch-rendering) | ||
- [Scoped parameters](API.md#scoped-parameters) | ||
+ [Parameters](API.md#parameters) | ||
- [Shaders](API.md#shaders) | ||
- [Uniforms](API.md#uniforms) | ||
- [Attributes](API.md#attributes) | ||
- [Drawing](API.md#drawing) | ||
- [Framebuffer](API.md#framebuffer) | ||
- [Depth buffer](API.md#depth-buffer) | ||
- [Blending](API.md#blending) | ||
- [Stencil](API.md#stencil) | ||
- [Polygon offset](API.md#polygon-offset) | ||
- [Culling](API.md#culling) | ||
- [Front face](API.md#front-face) | ||
- [Dithering](API.md#dithering) | ||
- [Line width](API.md#line-width) | ||
- [Color mask](API.md#color-mask) | ||
- [Sample coverage](API.md#sample-coverage) | ||
- [Scissor](API.md#scissor) | ||
- [Viewport](API.md#viewport) | ||
* [Resources](API.md#resources) | ||
+ [Basic usage](API.md#basic-usage) | ||
- [Updating a resource](API.md#updating-a-resource) | ||
- [Destroying a resource](API.md#destroying-a-resource) | ||
+ [Types](API.md#types) | ||
- [`regl.buffer(options)`](API.md#-reglbuffer-options--) | ||
- [`regl.elements(options)`](API.md#-reglelements-options--) | ||
* [Other features](API.md#other-properties) | ||
+ [Clear the draw buffer](API.md#clear-the-draw-buffer) | ||
+ [Reading pixels](API.md#reading-pixels) | ||
+ [Per-frame callbacks](API.md#per-frame-callbacks) | ||
+ [Frame stats](API.md#frame-stats) | ||
+ [WebGL capabilities](API.md#webgl-capabilities) | ||
+ [Clean up](API.md#clean-up) | ||
+ [Context loss](API.md#context-loss) | ||
#### `regl.destroy()` | ||
## Contributing | ||
## Errors | ||
[For info on how to build and test headless, see the contributing guide here](DEVELOPING.md) | ||
#### `var REGLError = require('regl/error')` | ||
## License | ||
(c) 2016 MIT License | ||
Supported by the Freeman Lab and the Howard Hughes Medical Institute | ||
Supported by the [Freeman Lab](https://www.janelia.org/lab/freeman-lab) and the Howard Hughes Medical Institute |
225
regl.js
var check = require('./lib/check') | ||
var getContext = require('./lib/context') | ||
var wrapExtensions = require('./lib/extension') | ||
var wrapShaders = require('./lib/shader') | ||
var wrapBuffers = require('./lib/buffer') | ||
var wrapElements = require('./lib/elements') | ||
var wrapTextures = require('./lib/texture') | ||
var wrapFBOs = require('./lib/fbo') | ||
var wrapUniforms = require('./lib/uniform') | ||
var wrapAttributes = require('./lib/attribute') | ||
var wrapShaders = require('./lib/shader') | ||
var wrapDraw = require('./lib/draw') | ||
var wrapContext = require('./lib/state') | ||
var createCompiler = require('./lib/compile') | ||
var wrapRead = require('./lib/read') | ||
var dynamic = require('./lib/dynamic') | ||
var raf = require('./lib/raf') | ||
var clock = require('./lib/clock') | ||
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' | ||
@@ -19,21 +33,85 @@ var CONTEXT_RESTORED_EVENT = 'webglcontextrestored' | ||
var GL_COLOR_BUFFER_BIT = 16384 | ||
var GL_DEPTH_BUFFER_BIT = 256 | ||
var GL_STENCIL_BUFFER_BIT = 1024 | ||
var extensionState = wrapExtensions(gl, options.requiredExtensions || []) | ||
var bufferState = wrapBuffers(gl, extensionState) | ||
var bufferState = wrapBuffers(gl) | ||
var elementState = wrapElements(gl, extensionState, bufferState) | ||
var textureState = wrapTextures(gl, extensionState) | ||
var fboState = wrapFBOs(gl, extensionState, textureState) | ||
var shaderState = wrapShaders(gl, extensionState) | ||
var contextState = wrapContext( | ||
var uniformState = wrapUniforms() | ||
var attributeState = wrapAttributes(gl, extensionState, bufferState) | ||
var shaderState = wrapShaders( | ||
gl, | ||
extensionState, | ||
shaderState, | ||
attributeState, | ||
uniformState, | ||
function (program) { | ||
return compiler.draw(program) | ||
}) | ||
var drawState = wrapDraw(gl, extensionState, bufferState) | ||
var glState = wrapContext(gl, shaderState) | ||
var frameState = { | ||
count: 0, | ||
start: clock(), | ||
dt: 0, | ||
t: clock(), | ||
renderTime: 0, | ||
width: gl.drawingBufferWidth, | ||
height: gl.drawingBufferHeight | ||
} | ||
var readPixels = wrapRead(gl, glState) | ||
var compiler = createCompiler( | ||
gl, | ||
extensionState, | ||
bufferState, | ||
elementState, | ||
textureState, | ||
fboState) | ||
fboState, | ||
glState, | ||
uniformState, | ||
attributeState, | ||
shaderState, | ||
drawState, | ||
frameState) | ||
var canvas = gl.canvas | ||
// raf stuff | ||
var rafCallbacks = [] | ||
var activeRAF = 0 | ||
function handleRAF () { | ||
activeRAF = raf.next(handleRAF) | ||
frameState.count += 1 | ||
if (frameState.width !== gl.drawingBufferWidth || | ||
frameState.height !== gl.drawingBufferHeight) { | ||
frameState.width = gl.drawingBufferWidth | ||
frameState.height = gl.drawingBufferHeight | ||
glState.notifyViewportChanged() | ||
} | ||
var now = clock() | ||
frameState.dt = now - frameState.t | ||
frameState.t = now | ||
for (var i = 0; i < rafCallbacks.length; ++i) { | ||
var cb = rafCallbacks[i] | ||
cb(frameState.count, frameState.t, frameState.dt) | ||
} | ||
frameState.renderTime = clock() - now | ||
} | ||
function startRAF () { | ||
if (!activeRAF && rafCallbacks.length > 0) { | ||
handleRAF() | ||
} | ||
} | ||
function stopRAF () { | ||
if (activeRAF) { | ||
raf.cancel(handleRAF) | ||
activeRAF = 0 | ||
} | ||
} | ||
function handleContextLoss (event) { | ||
stopRAF() | ||
event.preventDefault() | ||
@@ -52,6 +130,7 @@ if (options.onContextLost) { | ||
shaderState.refresh() | ||
contextState.refresh() | ||
glState.refresh() | ||
if (options.onContextRestored) { | ||
options.onContextRestored() | ||
} | ||
handleRAF() | ||
} | ||
@@ -66,2 +145,4 @@ | ||
function destroy () { | ||
stopRAF() | ||
if (canvas) { | ||
@@ -72,3 +153,2 @@ canvas.removeEventListener(CONTEXT_LOST_EVENT, handleContextLoss) | ||
contextState.clear() | ||
shaderState.clear() | ||
@@ -92,4 +172,32 @@ fboState.clear() | ||
function compileProcedure (options) { | ||
check(!!options, 'invalid args to regl({...})') | ||
check.type(options, 'object', 'invalid args to regl({...})') | ||
var hasDynamic = false | ||
function flattenNestedOptions (options) { | ||
var result = Object.assign({}, options) | ||
delete result.uniforms | ||
delete result.attributes | ||
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') | ||
return result | ||
} | ||
// First we separate the options into static and dynamic components | ||
@@ -116,23 +224,35 @@ function separateDynamic (object) { | ||
var attributes = separateDynamic(options.attributes || {}) | ||
var parts = separateDynamic(options) | ||
var staticOptions = parts.static | ||
delete staticOptions.uniforms | ||
delete staticOptions.attributes | ||
var opts = separateDynamic(flattenNestedOptions(options)) | ||
return contextState.create( | ||
staticOptions, | ||
uniforms.static, | ||
attributes.static, | ||
parts.dynamic, | ||
uniforms.dynamic, | ||
attributes.dynamic, | ||
var compiled = compiler.command( | ||
opts.static, uniforms.static, attributes.static, | ||
opts.dynamic, uniforms.dynamic, attributes.dynamic, | ||
hasDynamic) | ||
} | ||
// The main regl entry point | ||
function regl (options) { | ||
var compiled = compileProcedure(options) | ||
var result = compiled.scope | ||
result.draw = compiled.draw | ||
return result | ||
var draw = compiled.draw | ||
var batch = compiled.batch | ||
var scope = compiled.scope | ||
var EMPTY_ARRAY = [] | ||
function reserve (count) { | ||
while (EMPTY_ARRAY.length < count) { | ||
EMPTY_ARRAY.push(null) | ||
} | ||
return EMPTY_ARRAY | ||
} | ||
function REGLCommand (args, body) { | ||
if (typeof args === 'number') { | ||
return batch(args | 0, reserve(args | 0)) | ||
} else if (Array.isArray(args)) { | ||
return batch(args.length, args) | ||
} else if (typeof args === 'function') { | ||
return scope(null, args) | ||
} else if (typeof body === 'function') { | ||
return scope(args, body) | ||
} | ||
return draw(args) | ||
} | ||
return REGLCommand | ||
} | ||
@@ -144,2 +264,5 @@ | ||
// Update context state | ||
glState.poll() | ||
var c = options.color | ||
@@ -165,14 +288,48 @@ if (c) { | ||
return Object.assign(regl, { | ||
// Registers another requestAnimationFrame callback | ||
function frame (cb) { | ||
rafCallbacks.push(cb) | ||
function cancel () { | ||
var index = rafCallbacks.find(function (item) { | ||
return item === cb | ||
}) | ||
if (index < 0) { | ||
return | ||
} | ||
rafCallbacks.splice(index, 1) | ||
if (rafCallbacks.length <= 0) { | ||
stopRAF() | ||
} | ||
} | ||
startRAF() | ||
return { | ||
cancel: cancel | ||
} | ||
} | ||
return Object.assign(compileProcedure, { | ||
// Clear current FBO | ||
clear: clear, | ||
// Place holder for dynamic keys | ||
dynamic: dynamic.define, | ||
// Dynamic variable binding | ||
prop: dynamic.define, | ||
// Object constructors | ||
buffer: create(bufferState), | ||
elements: create(elementState), | ||
buffer: function (options) { | ||
return bufferState.create(options, GL_ARRAY_BUFFER) | ||
}, | ||
texture: create(textureState), | ||
fbo: create(fboState), | ||
// Frame rendering | ||
frame: frame, | ||
stats: frameState, | ||
// Read pixels | ||
read: readPixels, | ||
// Destroy regl and all associated resources | ||
@@ -179,0 +336,0 @@ destroy: destroy |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses eval() which is a dangerous function. This prevents the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
2008218
0
51
6642
186
14
3
5
3
- Removedgl-format-compiler-error@^1.0.2
- Removedadd-line-numbers@1.0.1(transitive)
- Removedatob-lite@1.0.0(transitive)
- Removedcore-util-is@1.0.3(transitive)
- Removedgl-constants@1.0.0(transitive)
- Removedgl-format-compiler-error@1.0.3(transitive)
- Removedglsl-shader-name@1.0.0(transitive)
- Removedglsl-tokenizer@2.1.5(transitive)
- Removedinherits@2.0.4(transitive)
- Removedisarray@0.0.1(transitive)
- Removedpad-left@1.0.2(transitive)
- Removedreadable-stream@1.0.34(transitive)
- Removedrepeat-string@1.6.1(transitive)
- Removedsprintf-js@1.1.3(transitive)
- Removedstring_decoder@0.10.31(transitive)
- Removedthrough2@0.6.5(transitive)
- Removedxtend@4.0.2(transitive)