From 8621fbd0be38357a3090eae249e543eb204d906c Mon Sep 17 00:00:00 2001
From: fuzhenn <fuzhen@maptalks.org>
Date: Wed, 11 Nov 2020 21:00:24 +0800
Subject: [PATCH 1/3] simpler array uniform support, fix #258

---
 lib/core.js           |  64 +++++++++++---
 lib/shader.js         |  15 ++--
 test/browser.js       |   1 +
 test/uniform-array.js | 198 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 259 insertions(+), 19 deletions(-)
 create mode 100644 test/uniform-array.js

diff --git a/lib/core.js b/lib/core.js
index 337c3eba..de128737 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 % 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 % 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 % 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 % 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 % 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 % 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 % 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 % 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 % 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
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()
+  }
+})

From 680b1b526c2f52af4ba13615ff24689ebdd21ff9 Mon Sep 17 00:00:00 2001
From: fuzhenn <fuzhen@maptalks.org>
Date: Wed, 11 Nov 2020 22:38:37 +0800
Subject: [PATCH 2/3] fix uniform tests

---
 lib/core.js | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/lib/core.js b/lib/core.js
index de128737..efa38055 100644
--- a/lib/core.js
+++ b/lib/core.js
@@ -2658,19 +2658,19 @@ module.exports = function reglCore (
                 break
               case GL_FLOAT_VEC2:
                 check.command(
-                  isArrayLike(value) && (value.length % 2 === 0 && value.length <= size * 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 === 0 && value.length <= size * 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 === 0 && value.length <= size * 4),
+                  isArrayLike(value) && (value.length && value.length % 4 === 0 && value.length <= size * 4),
                   'uniform ' + name, env.commandStr)
                 infix = '4f'
                 break
@@ -2696,37 +2696,37 @@ module.exports = function reglCore (
                 break
               case GL_BOOL_VEC2:
                 check.command(
-                  isArrayLike(value) && (value.length % 2 === 0 && value.length <= size * 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 === 0 && value.length <= size * 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 === 0 && value.length <= size * 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 === 0 && value.length <= size * 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 === 0 && value.length <= size * 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 === 0 && value.length <= size * 4),
+                  isArrayLike(value) && (value.length && value.length % 4 === 0 && value.length <= size * 4),
                   'uniform ' + name, env.commandStr)
                 infix = '4i'
                 break

From 917582f421290ea4b9d97582971202a59a37393f Mon Sep 17 00:00:00 2001
From: fuzhenn <fuzhen@maptalks.org>
Date: Fri, 13 Nov 2020 17:01:06 +0800
Subject: [PATCH 3/3] fixes for dynamic declaration

---
 lib/core.js | 53 +++++++++++++++++++++++++++++++----------------------
 1 file changed, 31 insertions(+), 22 deletions(-)

diff --git a/lib/core.js b/lib/core.js
index efa38055..6b92debd 100644
--- a/lib/core.js
+++ b/lib/core.js
@@ -2774,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)
           }
         }
 
@@ -2802,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)
@@ -2919,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)