diff --git a/lib/core.js b/lib/core.js index f2adf44d..7cc41113 100644 --- a/lib/core.js +++ b/lib/core.js @@ -2575,12 +2575,25 @@ module.exports = function reglCore ( 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' @@ -2634,74 +2647,99 @@ module.exports = function reglCore ( } else { switch (type) { case GL_FLOAT: - check.commandType(value, 'number', 'uniform ' + name, env.commandStr) + if (size === 1) { + check.commandType(value, 'number', 'uniform ' + name, env.commandStr) + } else { + check.command( + isArrayLike(value) && (value.length === size), + 'uniform ' + name, env.commandStr) + } infix = '1f' break case GL_FLOAT_VEC2: check.command( - isArrayLike(value) && value.length === 2, + isArrayLike(value) && (value.length && value.length % 2 === 0 && value.length <= size * 2), 'uniform ' + name, env.commandStr) infix = '2f' break case GL_FLOAT_VEC3: check.command( - isArrayLike(value) && value.length === 3, + isArrayLike(value) && (value.length && value.length % 3 === 0 && value.length <= size * 3), 'uniform ' + name, env.commandStr) infix = '3f' break case GL_FLOAT_VEC4: check.command( - isArrayLike(value) && value.length === 4, + isArrayLike(value) && (value.length && value.length % 4 === 0 && value.length <= size * 4), 'uniform ' + name, env.commandStr) infix = '4f' break case GL_BOOL: - check.commandType(value, 'boolean', 'uniform ' + name, env.commandStr) + if (size === 1) { + check.commandType(value, 'boolean', 'uniform ' + name, env.commandStr) + } else { + check.command( + isArrayLike(value) && (value.length === size), + 'uniform ' + name, env.commandStr) + } infix = '1i' break case GL_INT: - check.commandType(value, 'number', 'uniform ' + name, env.commandStr) + if (size === 1) { + check.commandType(value, 'number', 'uniform ' + name, env.commandStr) + } else { + check.command( + isArrayLike(value) && (value.length === size), + 'uniform ' + name, env.commandStr) + } infix = '1i' break case GL_BOOL_VEC2: check.command( - isArrayLike(value) && value.length === 2, + isArrayLike(value) && (value.length && value.length % 2 === 0 && value.length <= size * 2), 'uniform ' + name, env.commandStr) infix = '2i' break case GL_INT_VEC2: check.command( - isArrayLike(value) && value.length === 2, + isArrayLike(value) && (value.length && value.length % 2 === 0 && value.length <= size * 2), 'uniform ' + name, env.commandStr) infix = '2i' break case GL_BOOL_VEC3: check.command( - isArrayLike(value) && value.length === 3, + isArrayLike(value) && (value.length && value.length % 3 === 0 && value.length <= size * 3), 'uniform ' + name, env.commandStr) infix = '3i' break case GL_INT_VEC3: check.command( - isArrayLike(value) && value.length === 3, + isArrayLike(value) && (value.length && value.length % 3 === 0 && value.length <= size * 3), 'uniform ' + name, env.commandStr) infix = '3i' break case GL_BOOL_VEC4: check.command( - isArrayLike(value) && value.length === 4, + isArrayLike(value) && (value.length && value.length % 4 === 0 && value.length <= size * 4), 'uniform ' + name, env.commandStr) infix = '4i' break case GL_INT_VEC4: check.command( - isArrayLike(value) && value.length === 4, + isArrayLike(value) && (value.length && value.length % 4 === 0 && value.length <= size * 4), 'uniform ' + name, env.commandStr) 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, ',', - isArrayLike(value) ? Array.prototype.slice.call(value) : value, + value, ');') } continue @@ -2736,20 +2774,24 @@ module.exports = function reglCore ( 'bad data or missing for uniform "' + name + '". ' + message) } - function checkType (type) { - check(!Array.isArray(VALUE), 'must not specify an array type for uniform') + function checkType (type, size) { + if (size === 1) { + check(!Array.isArray(VALUE), 'must not specify an array type for uniform') + } emitCheck( - 'typeof ' + VALUE + '==="' + type + '"', + 'Array.isArray(' + VALUE + ') && typeof ' + VALUE + '[0]===" ' + type + '"' + + ' || typeof ' + VALUE + '==="' + type + '"', 'invalid type, expected ' + type) } - function checkVector (n, type) { + function checkVector (n, type, size) { if (Array.isArray(VALUE)) { - check(VALUE.length === n, 'must have length ' + n) + check(VALUE.length && VALUE.length % n === 0 && VALUE.length <= n * size, 'must have length of ' + (size === 1 ? '' : 'n * ') + n) } else { emitCheck( - shared.isArrayLike + '(' + VALUE + ')&&' + VALUE + '.length===' + n, - 'invalid vector, should have length ' + n, env.commandStr) + shared.isArrayLike + '(' + VALUE + ')&&' + VALUE + '.length && ' + VALUE + '.length % ' + n + ' === 0' + + ' && ' + VALUE + '.length<=' + n * size, + 'invalid vector, should have length of ' + (size === 1 ? '' : 'n * ') + n, env.commandStr) } } @@ -2764,49 +2806,49 @@ module.exports = function reglCore ( switch (type) { case GL_INT: - checkType('number') + checkType('number', size) break case GL_INT_VEC2: - checkVector(2, 'number') + checkVector(2, 'number', size) break case GL_INT_VEC3: - checkVector(3, 'number') + checkVector(3, 'number', size) break case GL_INT_VEC4: - checkVector(4, 'number') + checkVector(4, 'number', size) break case GL_FLOAT: - checkType('number') + checkType('number', size) break case GL_FLOAT_VEC2: - checkVector(2, 'number') + checkVector(2, 'number', size) break case GL_FLOAT_VEC3: - checkVector(3, 'number') + checkVector(3, 'number', size) break case GL_FLOAT_VEC4: - checkVector(4, 'number') + checkVector(4, 'number', size) break case GL_BOOL: - checkType('boolean') + checkType('boolean', size) break case GL_BOOL_VEC2: - checkVector(2, 'boolean') + checkVector(2, 'boolean', size) break case GL_BOOL_VEC3: - checkVector(3, 'boolean') + checkVector(3, 'boolean', size) break case GL_BOOL_VEC4: - checkVector(4, 'boolean') + checkVector(4, 'boolean', size) break case GL_FLOAT_MAT2: - checkVector(4, 'number') + checkVector(4, 'number', size) break case GL_FLOAT_MAT3: - checkVector(9, 'number') + checkVector(9, 'number', size) break case GL_FLOAT_MAT4: - checkVector(16, 'number') + checkVector(16, 'number', size) break case GL_SAMPLER_2D: checkTexture(GL_TEXTURE_2D) @@ -2881,6 +2923,11 @@ module.exports = function reglCore ( 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) diff --git a/lib/shader.js b/lib/shader.js index 81c0c409..49efab83 100644 --- a/lib/shader.js +++ b/lib/shader.js @@ -120,13 +120,16 @@ module.exports = function wrapShaderState (gl, stringStore, stats, config) { gl.getUniformLocation(program, name), info)) } - } else { - insertActiveInfo(uniforms, new ActiveInfo( - info.name, - stringStore.id(info.name), - gl.getUniformLocation(program, info.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)) } } diff --git a/test/browser.js b/test/browser.js index 372315b5..e372d5a9 100644 --- a/test/browser.js +++ b/test/browser.js @@ -47,3 +47,4 @@ function updateDOM () { } require('./index') +require('./uniform-array') diff --git a/test/uniform-array.js b/test/uniform-array.js new file mode 100644 index 00000000..4d0dc208 --- /dev/null +++ b/test/uniform-array.js @@ -0,0 +1,198 @@ +var createContext = require('./util/create-context') +var createREGL = require('../regl') +var tape = require('tape') +var extend = require('../lib/util/extend') + +function toVec4 (type, name) { + switch (type) { + case 'vec4': + case 'ivec4': + case 'bvec4': + return 'vec4(' + name + ')' + case 'vec3': + case 'ivec3': + case 'bvec3': + return 'vec4(' + name + ',1.0)' + case 'vec2': + case 'ivec2': + case 'bvec2': + return 'vec4(' + name + ',0.0,1.0)' + case 'float': + case 'int': + case 'bool': + return 'vec4(' + name + ',0,0,1)' + } +} + +function toFrag (type) { + return [ + 'precision mediump float;', + 'uniform ' + type + ' foo[3];', + + 'void main () {', + ' gl_FragColor = ' + toVec4(type, 'foo[0]') + ';', + // ' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);', + '}' + ].join('\n') +} + +tape('uniform array', function (t) { + var gl = createContext(16, 16) + var regl = createREGL( + { + gl: gl + }) + + var command = { + depth: { enable: false }, + vert: [ + 'precision highp float;', + 'attribute vec2 position;', + 'void main () {', + ' gl_Position = vec4(position, 0, 1);', + '}' + ].join('\n'), + + attributes: { + position: [ + -4, 0, + 4, -4, + 4, 4 + ] + }, + primitive: 'triangles', + count: 3 + } + + regl(extend({ + frag: toFrag('vec4'), + uniforms: { + 'foo[0]': [1, 0, 0, 1], + 'foo[1]': [0, 1, 0, 1], + 'foo[2]': [0, 0, 1, 1], + 'foo[3]': [0, 0, 1, 1] + } + }, command))() + + checkPixel('vec4 type') + + regl.clear({ + color: [0, 0, 0, 1], + depth: 1 + }) + + // vec4 foo + regl(extend({ + frag: toFrag('vec4'), + uniforms: { + 'foo': [1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1] + } + }, command))() + + checkPixel('vec4 type') + + // vec4 dynamic + regl(extend({ + frag: toFrag('vec4'), + uniforms: function (context, props) { + return props.foo + } + }, command))({ + foo: [1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1] + }) + + checkPixel('dynamic type') + + // vec3 foo + regl(extend({ + frag: toFrag('vec3'), + uniforms: { + 'foo': [1, 0, 0, 0, 1, 0, 0, 0, 1] + } + }, command))() + + checkPixel('vec3 type') + + // vec2 foo + regl(extend({ + frag: toFrag('vec2'), + uniforms: { + 'foo': [1, 0, 0, 1, 0, 0] + } + }, command))() + + checkPixel('vec2 type') + + // float foo + regl(extend({ + frag: toFrag('float'), + uniforms: { + 'foo': [1, 0, 0] + } + }, command))() + + checkPixel('float type') + + // ivec4 foo + regl(extend({ + frag: toFrag('vec4'), + uniforms: { + foo: [1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1] + } + }, command))() + + checkPixel('ivec4 type') + + // ivec3 foo + regl(extend({ + frag: toFrag('ivec3'), + uniforms: { + 'foo': [1, 0, 0, 0, 1, 0, 0, 0, 1] + } + }, command))() + + checkPixel('ivec3 type') + + // ivec2 foo + regl(extend({ + frag: toFrag('ivec2'), + uniforms: { + 'foo': [1, 0, 0, 1, 0, 0] + } + }, command))() + + checkPixel('ivec2 type') + + // int foo + regl(extend({ + frag: toFrag('int'), + uniforms: { + 'foo': [1, 0, 0] + } + }, command))() + + checkPixel('int type') + + endTest() + + function checkPixel (info) { + var pixels = regl.read({ + x: 8, + y: 8, + width: 1, + height: 1, + data: new Uint8Array(4) + }) + t.equals(pixels[0], 255, info) + t.equals(pixels[1], 0, info) + t.equals(pixels[2], 0, info) + t.equals(pixels[3], 255, info) + } + + function endTest () { + regl.destroy() + t.equals(gl.getError(), 0, 'error ok') + createContext.destroy(gl) + t.end() + } +})