Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel is required to process package imports. If you need a different preprocessor remove all packages first.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <head>
  <script>
  	load(this, function (exports) {
  function WebMonkeys(opt){
    var maxVertexIndex,
      vertexIndexBuffer,
      resultTextureSide,
      resultTexture,
      arrays,
      arrayByName,
      shaderByTask,
      gl,
      defaultLib,
      writer,
      renderer,
      userLib,
      framebuffer,
      rendererVertexBuffer;

    // () -> Monkeys
    function init(){
      opt = opt || [];
      maxVertexIndex = 0;
      resultTextureSide = 0;
      arrays = [];
      arrayByName = {};
      shaderByTask = {};

      var glOpt = {antialias: false, preserveDrawingBuffer: true};
      if (typeof window === "undefined"){
        gl = require("g"+"l")(1, 1, glOpt);
      } else {
        var canvas = document.createElement("canvas");
        gl = canvas.getContext("webgl", glOpt);
        gl.canvas = canvas;
        gl.canvas.width = 1;
        gl.canvas.height = 1;
        gl.canvas.style = [
          "border: 1px solid black;",
          "image-rendering: optimizeSpeed;",
          "image-rendering: -moz-crisp-edges;",
          "image-rendering: -webkit-optimize-contrast;",
          "image-rendering: -o-crisp-edges;",
          "image-rendering: pixelated;",
          "-ms-interpolation-mode: nearest-neighbor;"].join("");
      }

      defaultLib = [
        "vec2 indexToPos(vec2 size, float index){",
        "  return vec2(mod(index, size.x), floor(index/size.x));",
        "}",
        "float posToIndex(vec2 size, vec2 pos){",
        "  return pos.y*size.x + pos.x;",
        "}",
        "vec2 scaleRange(vec2 fromA, vec2 fromB, vec2 toA, vec2 toB, vec2 pos){",
        "  return toA+(pos-fromA)/(fromB-fromA)*(toB-toA);",
        "}",
        "vec4 packFloat(float x){",
        "  float s = 0.0;",
        "  float e = 0.0;",
        "  float m = x;",
        "  if (m<0.0) s=1.0, m=-m;",
        "  for (int i=0; i<24; ++i){",
        "    if (m>=2.0) m=m/2.0, e+=1.0;",
        "    if (m< 1.0) m=m/2.0, e-=1.0;",
        "    if (m>=1.0 && m<2.0) break;",
        "  };",
        "  return vec4(",
        "    floor(fract((m-1.0)*256.0*256.0)*256.0),",
        "    floor(fract((m-1.0)*256.0)*256.0),",
        "    floor(fract((m-1.0)*1.0)*256.0),",
        "    ((e+63.0) + (x>0.0?128.0:0.0)))/255.0;",
        "}",
        "float unpackFloat(vec4 v){",
        "  v *= 255.0;",
        "  float s = v.a >= 128.0 ? 1.0 : -1.0;",
        "  float e = v.a - (v.a >= 128.0 ? 128.0 : 0.0) - 63.0;",
        "  float m = 1.0 + v.x/256.0/256.0/256.0 + v.y/256.0/256.0 + v.z/256.0;",
        "  return s * pow(2.0, e) * m;",
        "}",
        "vec4 packVec4(vec4 v){",
        "  return v/255.0;",
        "}",
        "vec4 unpackVec4(vec4 v){",
        "  return v*255.0;",
        "}",
        "vec4 packIndexDepth(int a, int b){",
        "  float av = float(a);",
        "  float bv = float(b);",
        "  float x = mod(floor(av), 256.0);",
        "  float y = mod(floor(av/256.0), 256.0);",
        "  float z = mod(floor(av/256.0/256.0), 256.0);",
        "  float w = mod(floor(bv), 256.0);",
        "  return vec4(x,y,z,w)/255.0;",
        "}",
        "int unpackIndex(vec4 v){",
        "  return int(v.x*255.0 + v.y*255.0*256.0 + v.z*255.0*256.0*256.0);",
        "}",
        "int unpackDepth(vec4 v){",
        "  return int(v.w*255.0);",
        "}",
        ].join("\n");

      writer = buildShader(
        ["precision highp float;",
        "attribute float resultIndex;",
        "uniform sampler2D resultTexture;",
        "uniform float resultTextureSide;",
        "uniform float resultGridSide;",
        "uniform float resultSquareSide;",
        "uniform float targetTextureSide;",
        "varying vec4 value;",
        defaultLib,
        "void main(){",
        "  float resultSquareIndex = mod(resultIndex, resultSquareSide*resultSquareSide/2.0);", 
        "  vec2 resultSquareCoord = indexToPos(vec2(resultSquareSide/2.0,resultSquareSide), resultSquareIndex)*vec2(2.0,1.0);",
        "  vec2 resultGridCoord = indexToPos(vec2(resultGridSide), floor(resultIndex/(resultSquareSide*resultSquareSide/2.0)));",
        "  vec2 resultCoord = resultGridCoord * resultSquareSide + resultSquareCoord;",
        "  vec2 indexCoord = (resultCoord+vec2(0.5,0.5))/resultTextureSide;",
        "  vec2 valueCoord = (resultCoord+vec2(1.5,0.5))/resultTextureSide;",
        "  float index = float(unpackIndex(texture2D(resultTexture, indexCoord))-1);",
        "  float depth = float(unpackDepth(texture2D(resultTexture, indexCoord)));",
        "  value = texture2D(resultTexture, valueCoord);",
        "  vec2 rPos = (indexToPos(vec2(targetTextureSide),index)+vec2(0.5))/targetTextureSide*2.0-1.0;",
        "  gl_Position = vec4(depth > 0.5 ? rPos : vec2(-1.0,-1.0), (255.0-depth)/255.0, 1.0);",
        //"  gl_Position = vec4(rPos, -0.5, 1.0);",
        "  gl_PointSize = 1.0;",
        "}"].join("\n"),
        ["precision highp float;",
        "varying vec4 value;",
        "void main(){",
        "  gl_FragColor = value;",
        "}"].join("\n"));

      renderer = buildShader(
        ["precision highp float;",
        "attribute vec2 vertexPos;",
        "varying vec2 pos;",
        "void main(){",
        "  pos = vertexPos;",
        "  gl_Position = vec4(vertexPos, 0.0, 1.0);",
        "}"].join("\n"),
        ["precision mediump float;",
        "uniform sampler2D array;",
        "varying vec2 pos;",
        "void main(){",
        "  gl_FragColor = texture2D(array, pos*0.5+0.5);",
        "}"].join("\n"));

      gl.clearDepth(256.0);

      vertexIndexBuffer = gl.createBuffer();

      rendererVertexBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, rendererVertexBuffer);
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1,1,-1,-1,1,-1,1,1,-1,1,-1,-1]), gl.STATIC_DRAW);

      resultTexture = gl.createTexture();
      gl.activeTexture(gl.TEXTURE0);
      gl.bindTexture(gl.TEXTURE_2D, resultTexture);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

      framebuffer = gl.createFramebuffer();
      gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);

      return monkeysApi;
    };

    // *Monkeys => Number -> Monkeys
    //   Makes sure there are enough index vertices available
    //   for a `gl.drawArrays(gl.POINTS, 0, vertices)` call.
    function allocVertexIndices(indices){
      if (indices > maxVertexIndex){
        maxVertexIndex = Math.pow(fitTextureSide(indices), 2);
        var vertexIndexArray = new Float32Array(maxVertexIndex);
        for (var i=0; i<maxVertexIndex; ++i)
          vertexIndexArray[i] = i; 
        gl.bindBuffer(gl.ARRAY_BUFFER, vertexIndexBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, vertexIndexArray, gl.STATIC_DRAW);
        gl.bindBuffer(gl.ARRAY_BUFFER, null);
      };
    };

    // *Monkeys => Number -> ()
    //   Makes sure the results texture is big
    //   enough to fit every result of a task.
    function allocResultTexture(usedTextureSide){
      if (usedTextureSide > resultTextureSide){
        resultTextureSide = usedTextureSide;
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, resultTexture);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, resultTextureSide, resultTextureSide, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
      }
    };

    // *Monkeys => String, String -> WebGLProgram
    function buildShader(vertexSrc, fragmentSrc){
      function compile(type, shaderSource){
        var shader = gl.createShader(type);
        gl.shaderSource(shader, shaderSource);
        gl.compileShader(shader);
        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)){
          var errorMsg = "WebMonkeys had the following error from WebGL: " + gl.getShaderInfoLog(shader);
          if (errorMsg.indexOf("syntax error") !== -1)
            errorMsg += "This could be fixed by adding extra `;` before setters.";
          throw errorMsg;
        }
        return shader;
      }
      var vertexShader = compile(gl.VERTEX_SHADER, vertexSrc);
      var fragmentShader = compile(gl.FRAGMENT_SHADER, fragmentSrc);

      var shader = gl.createProgram();
      gl.attachShader(shader, vertexShader);
      gl.attachShader(shader, fragmentShader);
      gl.linkProgram(shader);
      if(!gl.getProgramParameter(shader, gl.LINK_STATUS))
        throw "Error linking shaders.";

      return shader;
    }

    // Number -> Number
    function fitTextureSide(elements){
      return Math.pow(2, Math.ceil(Math.log(Math.sqrt(elements))/Math.log(2)));
    };

    // Number -> Number
    function fract(x){ 
      return x - Math.floor(x);
    };

    // *Monkeys => String -> Maybe (Either (Array Number) *Uint32Array)
    function get(name){
      var array = arrayByName[name];
      if (!array) return null;
      var targetArray = array.uint32Array;
      var pixels = targetArray
        ? new Uint8Array(targetArray.buffer)  // re-uses existing buffer
        : new Uint8Array(array.textureSide*array.textureSide*4);
      gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, array.texture, 0);
      gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, null);
      gl.readPixels(0, 0, array.textureSide, array.textureSide, gl.RGBA, gl.UNSIGNED_BYTE, pixels);

      if (!targetArray){
        var result = [];
        for (var i=0, l=array.length; i<l; ++i){
          var s = pixels[i*4+3] >= 128 ? 1 : -1;
          var e = pixels[i*4+3] - (pixels[i*4+3] >= 128 ? 128 : 0) - 63;
          var m = 1 + pixels[i*4+0]/256/256/256 + pixels[i*4+1]/256/256 + pixels[i*4+2]/256;
          var n = s * Math.pow(2, e) * m;
          var z = 0.000000000000000001; // to avoid annoying floating point error for 0
          result.push(-z < n && n < z ? 0 : n);
        };
        return result;
      } else {
        return targetArray;
      }
    };

    // *Monkeys => String, *Uint32Array -> Monkeys
    // *Monkeys => String, Array Number -> Monkeys
    // *Monkeys => String, Number -> Monkeys
    function set(name, lengthOrArray){
      if (typeof lengthOrArray === "number"){
        var length = lengthOrArray;
        var textureSide = fitTextureSide(length);
        var array = null;
      } else {
        var length = lengthOrArray.length;
        var textureSide = fitTextureSide(length);
        if (lengthOrArray instanceof Array) { // upload JS Numbers as Floats
          var array = new Uint8Array(textureSide*textureSide*4);
          for (var i=0, l=lengthOrArray.length; i<l; ++i){ 
            var x = lengthOrArray[i];
            var s = x > 0 ? 1 : -1;
            var e = Math.floor(Math.log2(s*x));
            var m = s*x/Math.pow(2, e);
            array[i*4+0] = Math.floor(fract((m-1)*256*256)*256)||0;
            array[i*4+1] = Math.floor(fract((m-1)*256)*256)||0;
            array[i*4+2] = Math.floor(fract((m-1)*1)*256)||0;
            array[i*4+3] = ((e+63) + (x>0?128:0))||0;
          };
        } else { // upload 32-bit Uints as Vec4s
          if (textureSide * textureSide !== length)
            throw "WebMonkey error: when on raw buffer mode, the length of your\n"
                + "buffer must be (2^n)^2 for a positive integer n. That is, it\n"
                + "could be 1, 4, 16, 64, 256, 1024, 4096, 16384, 65536, 262144\n"
                + "and so on. Your '"+name+"' buffer has length "+length+".";
          var array = new Uint8Array(lengthOrArray.buffer);
        }
      }
      gl.activeTexture(gl.TEXTURE0);
      if (!arrayByName[name]){
        var texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, textureSide, textureSide, 0, gl.RGBA, gl.UNSIGNED_BYTE, array);
        var depthbuffer = gl.createRenderbuffer();
        gl.bindRenderbuffer(gl.RENDERBUFFER, depthbuffer);
        gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, textureSide, textureSide);
        arrayByName[name] = {
          name: name,
          uint32Array: lengthOrArray instanceof Uint32Array ? lengthOrArray : null,
          valueType: lengthOrArray instanceof Uint32Array ? "vec4" : "float",
          texture: texture,
          depthbuffer: depthbuffer,
          textureName: name+"_",
          textureSide: textureSide,
          length: length};
        arrays.push(arrayByName[name]);
      } else {
        var texture = arrayByName[name].texture;
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, textureSide, textureSide, 0, gl.RGBA, gl.UNSIGNED_BYTE, array);
      }
      return monkeysApi;
    };

    // *Monkeys => String, Number -> Monkeys
    //   Fills an array with a floating point number
    function fill(name, x){
      var array = arrayByName[name];
      // Since the float packing on the set function is
      // inlined for performance, it must be duplicated
      // here. FIXME: find a way to avoid this.
      var s = x > 0 ? 1 : -1;
      var e = Math.floor(Math.log2(s*x));
      var m = s*x/Math.pow(2, e);
      var a = Math.floor(fract((m-1)*256*256)*256)||0;
      var b = Math.floor(fract((m-1)*256)*256)||0;
      var c = Math.floor(fract((m-1)*1)*256)||0;
      var d = ((e+63) + (x>0?128:0))||0;
      return clear(name, ((d<<24)+(c<<16)+(b<<8)+a));
    };

    // *Monkeys => String, Uint32 -> Monkeys
    //   Fills an array with an Uint32
    function clear(name, value){
      var array = arrayByName[name];
      gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
      gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, array.texture, 0);
      gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, null);
      gl.clearColor(
        ((value&0x000000FF) >>>  0)/255,
        ((value&0x0000FF00) >>>  8)/255,
        ((value&0x00FF0000) >>> 16)/255,
        ((value&0xFF000000) >>> 24)/255);
      gl.clear(gl.COLOR_BUFFER_BIT)
      return monkeysApi;
    };

    // *Monkeys => String -> Monkeys
    function del(name){
      var existingArray;
      if (existingArray = arraysByName[name]){
        delete arraysByName[name];
        arrays = arrays.filter(function(arr){
          return arr !== existingArray;
        });
        gl.deleteTexture(existingArray.texture);
      };
      return monkeysApi;
    };

    // String -> Maybe {name: String, index: String, depth: String, value: String}
    //   Parses a setter statement such as `foo(i*8) := bar(i*8) + baz(i*8);` and
    //   returns `name`, `index`, `depth` and `value` strings:
    //   {name: "foo", index: "i*8", depth: "", value: "bar(i*8) + baz(i*8)"}
    function parseSetterStatement(statement){
      var name = "";
      var index = "";
      var depth = "";
      var value = "";
      var phase = 0;
      var brackets = 1;
      for (var i=0, l=statement.length; i < l; ++i){
        var chr = statement[i];
        switch (phase){
          case 0: 
            if (chr === "(")
              phase = 1;
            else if (chr !== " " && chr !== "\n")
              name += chr;
          break;
          case 1:
            if (chr === "(")
              ++brackets;
            else if (chr === ")")
              --brackets;
            if (brackets === 1 && chr === ",")
              phase = 2;
            else if (brackets === 0)
              phase = 3;
            else
              index += chr;
          break;
          case 2:
            if (chr === "(")
              ++brackets;
            else if (chr === ")")
              --brackets;
            if (brackets === 0)
              phase = 3;
            else
              depth += chr;
          break;
          case 3:
            if (chr === ":")
              phase = 4;
          break;
          case 4:
            if (chr === "=")
              phase = 5;
            else
              return null;
          break;
          case 5:
            if (chr !== " ")
              value += chr,
              phase = 6;
            break;
          case 6:
            if (chr === ";")
              phase = 7;
            else
              value += chr;
          break;
        };
      };
      return phase === 7 
        ? {name: name,
          index: index,
          depth: depth,
          value: value}
        : null;
    };

    // String -> {
    //   shader: GLShader,
    //   usedResults: Number,
    //   allocResults: Number,
    //   resultArrayName: String,
    //   usesDepth: Bool}
    function buildTask(task){
      if (shaderByTask[task]) 
        return shaderByTask[task];

      var usesDepth = false;
      var taskStatements = task.split(";");
      taskStatements.pop();
      var setters = [];
      var setter;
      while (setter = parseSetterStatement(taskStatements[taskStatements.length-1]+";")){
        setters.push(setter);
        taskStatements.pop();
        if (setter.depth !== "0")
          usesDepth = true;
      };
      if (setters.length === 0)
        throw "Error parsing Monkey task: tasks must end with a setter statement such as `foo[0] = 0;`.";
      var resultArrayName = setters[0].name;
      for (var i=1, l=setters.length; i<l; ++i)
        if (setters[i].name !== resultArrayName)
          throw "Error parsing Monkey task: you can't write to different arrays on the same task.";

      var taskWithoutSetters = taskStatements.join(";")+";";

      // `usedResults` is how many sets this work does.
      // `allocResults` is how many sets we actually allocated space for.
      // Explanation: a result is an (indice, value) pair which will be used on
      // the next pass to fill the target array. Those results are recorded
      // into square sections of a 2D texture. Each monkey has its own square.
      // In order for everything to fit, the square of a monkey will have empty
      // space. For example, if a task makes 3 sets, it requires 6 pixels on
      // the texture report its result (3 indices + 3 values). To fit 6 pixels,
      // we need a square of side 4; side 2 isn't enough because it only fits 4
      // pixels, side 3 isn't allowed because such a square wouldn't align
      // correctly on the texture. 
      // TODO: complete this explanation, move it to the top, make some drawings
      var usedResults = setters.length;
      var allocResults = Math.pow(fitTextureSide(usedResults*2),2)/2;

      var getters = "";
      for (var i=0, l=arrays.length; i<l; ++i)
        getters 
          += "uniform sampler2D "+arrays[i].textureName+";\n"
          +  arrays[i].valueType+" "+arrays[i].name+"(float idx){\n"
          +  "  return "+(arrays[i].valueType==="float"?"unpackFloat":"unpackVec4")+"(texture2D("+arrays[i].textureName+",indexToPos(vec2("+arrays[i].textureSide.toFixed(1)+"), idx)/"+arrays[i].textureSide.toFixed(2)+"));\n"
          +  "}\n"
          +  arrays[i].valueType+" "+arrays[i].name+"(int idx){\n"
          +  "  return "+arrays[i].name+"(float(idx));\n"
          +  "}\n";

      var setterFns = "";
      for (var i=0; i<allocResults; ++i){
        setterFns += "void set"+i+"(int i"+i+", int d"+i+", float v"+i+"){\n";
        setterFns += "  results["+(i*2+0)+"] = packIndexDepth(i"+i+"+1, d"+i+");\n"
        setterFns += "  results["+(i*2+1)+"] = packFloat(v"+i+");\n"
        setterFns += "}\n";
        setterFns += "void set"+i+"(int i"+i+", int d"+i+", vec4 v"+i+"){\n";
        setterFns += "  results["+(i*2+0)+"] = packIndexDepth(i"+i+"+1, d"+i+");\n"
        setterFns += "  results["+(i*2+1)+"] = packVec4(v"+i+");\n"
        setterFns += "}\n";
      };

      var writeToTexture = "";
      for (var i=0; i<allocResults*2; ++i)
        writeToTexture += "  if (idx == "+i+") gl_FragColor = results["+i+"];\n";

      var setter = "";
      for (var i=0; i < allocResults; ++i){
        setter += "  set"+i+"(";
        setter += i < usedResults
          ? setters[i].index+", "
            + (setters[i].depth||"1")+", "
            + setters[i].value
          : "0, 0, vec4(0.0)";
        setter += ");\n";
      };

      var vertexShader = [
        "precision highp float;",
        "uniform float resultTextureSide;",
        "uniform float resultGridSide;",
        "uniform float resultSquareSide;",
        "attribute float resultIndex;",
        "varying float resultIndexVar;",
        "varying vec4 results["+(allocResults*2)+"];",
        defaultLib,
        getters,
        setterFns,
        userLib,
        "vec4 scaleToScreen(vec2 pos){",
        "  vec2 screenCoord = scaleRange(vec2(0.0,0.0), vec2(resultGridSide), vec2(-1.0), vec2(-1.0+resultSquareSide*resultGridSide/resultTextureSide*2.0), pos);",
        "  return vec4(screenCoord + vec2(resultSquareSide)/resultTextureSide, 1.0, 1.0);",
        "}",
        "void main(){",
        "  int i = int(resultIndex);",
        "  float f = resultIndex;",
        taskWithoutSetters,
        setter,
        "  gl_PointSize = resultSquareSide;",
        "  gl_Position = scaleToScreen(indexToPos(vec2(resultGridSide), resultIndex));",
        "  resultIndexVar = resultIndex;",
        "}"].join("\n")

      var fragmentShader = [
        "precision highp float;",
        "varying float resultIndexVar;",
        "varying vec4 results["+(allocResults*2)+"];",
        "uniform float resultSquareSide;",
        defaultLib,
        "void main(){",
        "  vec2 coord = floor(gl_PointCoord * resultSquareSide);",
        "  int idx = int((resultSquareSide-1.0-coord.y) * resultSquareSide + coord.x);",
        writeToTexture,
        "}"].join("\n");
        
        var shader = buildShader(vertexShader, fragmentShader);

        return shaderByTask[task] = {
          usesDepth: usesDepth,
          usedResults: usedResults,
          allocResults: allocResults,
          shader: shader,
          resultArrayName: resultArrayName};
    };

    // *Monkeys => Number, String -> Monkeys
    function work(monkeyCount, taskSource){
      var task = buildTask(taskSource);
      var resultArray = arrayByName[task.resultArrayName];
      var resultSquareSide = fitTextureSide(task.allocResults*2);
      var resultGridSide = fitTextureSide(monkeyCount);
      var usedResultTextureSide = resultGridSide * resultSquareSide;
      var resultSquareSide = fitTextureSide(task.allocResults*2);
      var resultGridSide = fitTextureSide(monkeyCount);
      var usedResultTextureSide = resultGridSide * resultSquareSide;

      allocResultTexture(usedResultTextureSide);
      allocVertexIndices(Math.max(monkeyCount, monkeyCount*resultSquareSide*resultSquareSide/2));

      gl.useProgram(task.shader);
      gl.bindBuffer(gl.ARRAY_BUFFER, vertexIndexBuffer);
      gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
      gl.uniform1f(gl.getUniformLocation(task.shader,"resultGridSide"), resultGridSide);
      gl.uniform1f(gl.getUniformLocation(task.shader,"resultSquareSide"), resultSquareSide);
      gl.uniform1f(gl.getUniformLocation(task.shader,"resultTextureSide"), resultTextureSide);
      gl.vertexAttribPointer(gl.getAttribLocation(task.shader,"resultIndex"), 1, gl.FLOAT, false, 0, 0);
      gl.enableVertexAttribArray(gl.getAttribLocation(task.shader,"resultIndex"));
      gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, resultTexture, 0);
      gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, null);
      gl.viewport(0, 0, resultTextureSide, resultTextureSide);
      for (var i=0, l=arrays.length; i<l; ++i){
        gl.activeTexture(gl.TEXTURE0+i);
        gl.bindTexture(gl.TEXTURE_2D, arrays[i].texture);
        gl.uniform1i(gl.getUniformLocation(task.shader,arrays[i].textureName), i);
      }
      gl.drawArrays(gl.POINTS, 0, monkeyCount);

      if (task.usesDepth) gl.enable(gl.DEPTH_TEST);
      gl.useProgram(writer);
      gl.activeTexture(gl.TEXTURE0);
      gl.bindTexture(gl.TEXTURE_2D, resultTexture);
      gl.uniform1i(gl.getUniformLocation(writer,"resultTexture"), resultTexture);
      gl.uniform1f(gl.getUniformLocation(writer,"resultGridSide"), resultGridSide);
      gl.uniform1f(gl.getUniformLocation(writer,"resultSquareSide"), resultSquareSide);
      gl.uniform1f(gl.getUniformLocation(writer,"resultTextureSide"), resultTextureSide);
      gl.uniform1f(gl.getUniformLocation(writer,"targetTextureSide"), resultArray.textureSide);
      gl.vertexAttribPointer(gl.getAttribLocation(writer,"resultIndex"), 1, gl.FLOAT, false, 0, 0);
      gl.enableVertexAttribArray(gl.getAttribLocation(writer,"resultIndex"));
      gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, resultArray.texture, 0);
      gl.viewport(0, 0, resultArray.textureSide, resultArray.textureSide);
      if (task.usesDepth){
        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, resultArray.depthbuffer);
        gl.clear(gl.DEPTH_BUFFER_BIT)
      };
      gl.drawArrays(gl.POINTS, 0, monkeyCount*resultSquareSide*resultSquareSide/2);
      if (task.usesDepth) gl.disable(gl.DEPTH_TEST);
      return monkeysApi;
    };

    // Allows rendering arrays to a Canvas for visualization
    // *Monkeys => String, Number, Number -> Maybe Canvas
    function render(name, width, height){
      if (gl.canvas && arrayByName[name]){
        gl.canvas.width = width;
        gl.canvas.height = height;
        gl.useProgram(renderer);
        gl.viewport(0, 0, width, height);

        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, arrayByName[name].texture);

        gl.bindBuffer(gl.ARRAY_BUFFER, rendererVertexBuffer);
        var vertexPosAttr = gl.getAttribLocation(renderer, "vertexPos")
        gl.vertexAttribPointer(vertexPosAttr, 2, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(vertexPosAttr);
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);

        gl.drawArrays(gl.TRIANGLES, 0, 6);
        return gl.canvas;
      }
      return null;
    };

    // *Monkeys => String -> Monkeys
    function lib(source){
      userLib = source;
      return monkeysApi;
    };

    // Monkeys => String -> String
    function stringify(name){
      return JSON.stringify(get(name));
    };

    // Monkeys => String -> IO ()
    function log(name){
      console.log(stringify(name))
    };

    var monkeysApi = {
      set: set,
      get: get,
      del: del,
      lib: lib,
      work: work,
      clear: clear,
      fill: fill,
      render: render,
      stringify: stringify,
      log: log
    };

    return init();
  }

  if (typeof window === 'object')
    exports.WebMonkeys = WebMonkeys;

  if (typeof module !== "undefined")
    module.exports = WebMonkeys;
});

function load(root, factory) {
  'use strict';

  // amd
  if (typeof define === 'function' && define.amd)
    // register as an anonymous module
    define([], factory);

  // commonjs
  else if (typeof exports === 'object' && typeof exports.nodeName !== 'string')
    factory(exports);

  // browser globals
  else
    factory(root);

}

  </script>
  
</head>

<body>
  <center><h3>Web Monkey Test</h3></center>
  <div></div>

  <script>
  const monkey = WebMonkeys();

  monkey.set('res', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
  monkey.work(10, 'res(i) := res(i) * res(i) * 2.0;')

  var nums = monkey.get('res')
  document.querySelector("div").innerHTML = nums.join(",</br>")
  console.log(monkey.get('res'))
  </script>
</body>
              
            
!

CSS

              
                
              
            
!

JS

              
                
              
            
!
999px

Console