/* QuickCheck tests for WebGL: 1. Write a valid arg generator for each function 1.1. Write valid arg predicates to use with random generator: if value passes generator, accept it as valid. 1.2. Often needs initializing and cleanup: setup - generate - cleanup gl.createBuffer - test(bindBufferGenerator) - gl.deleteBuffer 2. Write an invalid arg generator 2.1. Take valid args, modify an arg until the args no longer pass checkArgValidity. 2.2. Repeat for all args. 3. Test functions using the generators 3.1. Args generated with the valid arg generator should pass assertOk(f(args)) 3.2. Args generated with the invalid arg generator should pass assertFail(f(args)) */ var GLcanvas = document.createElement('canvas'); var canvas2D = document.createElement('canvas'); GLcanvas.width = GLcanvas.height = 256; GL = GLcanvas.getContext(GL_CONTEXT_ID); Array.from = function(o) { var a = []; for (var i=0; i= 0; if (bufData instanceof ArrayBuffer) return true; return WebGLArrayTypes.some(function(t) { return bufData instanceof t; }); }; isVertexAttribute = function(idx) { if (typeof idx != 'number') return false; return idx >= 0 && idx < MaxVertexAttribs; }; isValidName = function(name) { if (typeof name != 'string') return false; for (var i=0; i 0.5; }; randomStencil = function() { return randomInt(MaxStencilValue); }; randomLineWidth = function() { var lo = LineWidthRange[0], hi = LineWidthRange[1]; return randomFloatFromRange(lo, hi); }; randomImage = function(w,h) { var img; var r = Math.random(); if (r < 0.5) { img = document.createElement('canvas'); img.width = w; img.height = h; img.getContext('2d').fillRect(0,0,w,h); } else if (r < 0.5) { img = document.createElement('video'); img.width = w; img.height = h; } else if (r < 0.75) { img = document.createElement('img'); img.width = w; img.height = h; } else { img = canvas2D.getContext('2d').createImageData(w,h); } return img }; // ArgGenerators contains argument generators for WebGL functions. // The argument generators are used for running random tests against the WebGL // functions. // // ArgGenerators is an object consisting of functionName : argGen -properties. // // functionName is a WebGL context function name and the argGen is an argument // generator object that encapsulates the requirements to run // randomly generated tests on the WebGL function. // // An argGen object has the following methods: // - setup -- set up state for testing the GL function, returns values // that need cleanup in teardown. Run once before entering a // test loop. // - teardown -- do cleanup on setup's return values after testing is complete // - generate -- generate a valid set of random arguments for the GL function // - returnValueCleanup -- do cleanup on value returned by the tested GL function // - cleanup -- do cleanup on generated arguments from generate // - checkArgValidity -- check if passed args are valid. Has a call signature // that matches generate's return value. Returns true // if args are valid, false if not. // // Example test loop that demonstrates how the function args and return // values flow together: // // var setupArgs = argGen.setup(); // for (var i=0; i 0.5 ? null : GL.createFramebuffer()]; }, checkArgValidity : function(target, fbo) { return target == GL.FRAMEBUFFER && (fbo == null || GL.isFramebuffer(fbo)); }, cleanup : function(target, fbo) { GL.bindFramebuffer(target, null); if (fbo) GL.deleteFramebuffer(fbo); } }, bindRenderbuffer : { generate : function() { return [GL.RENDERBUFFER, Math.random() > 0.5 ? null : GL.createRenderbuffer()]; }, checkArgValidity : function(target, rbo) { return target == GL.RENDERBUFFER && (rbo == null || GL.isRenderbuffer(rbo)); }, cleanup : function(target, rbo) { GL.bindRenderbuffer(target, null); if (rbo) GL.deleteRenderbuffer(rbo); } }, bindTexture : { generate : function() { return [bindTextureTarget.random(), Math.random() > 0.5 ? null : GL.createTexture()]; }, checkArgValidity : function(target, o) { return bindTextureTarget.has(target) && (o == null || GL.isTexture(o)); }, cleanup : function(target, o) { GL.bindTexture(target, null); if (o) GL.deleteTexture(o); } }, blendColor : { generate : function() { return randomColor(); }, teardown : function() { GL.blendColor(0,0,0,0); } }, blendEquation : { generate : function() { return [blendEquationMode.random()]; }, checkArgValidity : function(o) { return blendEquationMode.has(o); }, teardown : function() { GL.blendEquation(GL.FUNC_ADD); } }, blendEquationSeparate : { generate : function() { return [blendEquationMode.random(), blendEquationMode.random()]; }, checkArgValidity : function(o,p) { return blendEquationMode.has(o) && blendEquationMode.has(p); }, teardown : function() { GL.blendEquationSeparate(GL.FUNC_ADD, GL.FUNC_ADD); } }, blendFunc : { generate : function() { return [blendFuncSfactor.random(), blendFuncDfactor.random()]; }, checkArgValidity : function(s,d) { return blendFuncSfactor.has(s) && blendFuncDfactor.has(d); }, teardown : function() { GL.blendFunc(GL.ONE, GL.ZERO); } }, blendFuncSeparate : { generate : function() { return [blendFuncSfactor.random(), blendFuncDfactor.random(), blendFuncSfactor.random(), blendFuncDfactor.random()]; }, checkArgValidity : function(s,d,as,ad) { return blendFuncSfactor.has(s) && blendFuncDfactor.has(d) && blendFuncSfactor.has(as) && blendFuncDfactor.has(ad) ; }, teardown : function() { GL.blendFuncSeparate(GL.ONE, GL.ZERO, GL.ONE, GL.ZERO); } }, bufferData : { setup : function() { var buf = GL.createBuffer(); var ebuf = GL.createBuffer(); GL.bindBuffer(GL.ARRAY_BUFFER, buf); GL.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, ebuf); return [buf, ebuf]; }, generate : function(buf, ebuf) { return [bufferTarget.random(), randomBufferData(), bufferMode.random()]; }, checkArgValidity : function(target, bufData, mode) { return bufferTarget.has(target) && isBufferData(bufData) && bufferMode.has(mode); }, teardown : function(buf, ebuf) { GL.deleteBuffer(buf); GL.deleteBuffer(ebuf); }, }, bufferSubData : { setup : function() { var buf = GL.createBuffer(); var ebuf = GL.createBuffer(); GL.bindBuffer(GL.ARRAY_BUFFER, buf); GL.bufferData(GL.ARRAY_BUFFER, 256, GL.STATIC_DRAW); GL.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, ebuf); GL.bufferData(GL.ELEMENT_ARRAY_BUFFER, 256, GL.STATIC_DRAW); return [buf, ebuf]; }, generate : function(buf, ebuf) { var d = randomBufferSubData(256); return [bufferTarget.random(), d.offset, d.data]; }, checkArgValidity : function(target, offset, data) { return bufferTarget.has(target) && offset >= 0 && data.byteLength >= 0 && offset + data.byteLength <= 256; }, teardown : function(buf, ebuf) { GL.deleteBuffer(buf); GL.deleteBuffer(ebuf); }, }, // C checkFramebufferStatus : { generate : function() { return [Math.random() > 0.5 ? null : GL.createFramebuffer()]; }, checkArgValidity : function(fbo) { return fbo == null || GL.isFramebuffer(fbo); }, cleanup : function(fbo){ if (fbo != null) try{ GL.deleteFramebuffer(fbo); } catch(e) {} } }, clear : { generate : function() { return [clearMask.random()]; }, checkArgValidity : function(mask) { return clearMask.has(mask); } }, clearColor : { generate : function() { return randomColor(); }, teardown : function() { GL.clearColor(0,0,0,0); } }, clearDepth : { generate : function() { return [Math.random()]; }, teardown : function() { GL.clearDepth(1); } }, clearStencil : { generate : function() { return [randomStencil()]; }, teardown : function() { GL.clearStencil(0); } }, colorMask : { generate : function() { return [randomBool(), randomBool(), randomBool(), randomBool()]; }, teardown : function() { GL.colorMask(true, true, true, true); } }, compileShader : {}, // FIXME copyTexImage2D : {}, // FIXME copyTexSubImage2D : {}, // FIXME createBuffer : { generate : function() { return []; }, returnValueCleanup : function(o) { GL.deleteBuffer(o); } }, createFramebuffer : { generate : function() { return []; }, returnValueCleanup : function(o) { GL.deleteFramebuffer(o); } }, createProgram : { generate : function() { return []; }, returnValueCleanup : function(o) { GL.deleteProgram(o); } }, createRenderbuffer : { generate : function() { return []; }, returnValueCleanup : function(o) { GL.deleteRenderbuffer(o); } }, createShader : { generate : function() { return [shaderType.random()]; }, checkArgValidity : function(t) { return shaderType.has(t); }, returnValueCleanup : function(o) { GL.deleteShader(o); } }, createTexture : { generate : function() { return []; }, returnValueCleanup : function(o) { GL.deleteTexture(o); } }, cullFace : { generate : function() { return [cullFace.random()]; }, checkArgValidity : function(f) { return cullFace.has(f); }, teardown : function() { GL.cullFace(GL.BACK); } }, // D deleteBuffer : { generate : function() { return [GL.createBuffer()]; }, checkArgValidity : function(o) { return GL.isBuffer(o); }, cleanup : function(o) { try { GL.deleteBuffer(o); } catch(e) {} } }, deleteFramebuffer : { generate : function() { return [GL.createFramebuffer()]; }, checkArgValidity : function(o) { return GL.isFramebuffer(o); }, cleanup : function(o) { try { GL.deleteFramebuffer(o); } catch(e) {} } }, deleteProgram : { generate : function() { return [GL.createProgram()]; }, checkArgValidity : function(o) { return GL.isProgram(o); }, cleanup : function(o) { try { GL.deleteProgram(o); } catch(e) {} } }, deleteRenderbuffer : { generate : function() { return [GL.createRenderbuffer()]; }, checkArgValidity : function(o) { return GL.isRenderbuffer(o); }, cleanup : function(o) { try { GL.deleteRenderbuffer(o); } catch(e) {} } }, deleteShader : { generate : function() { return [GL.createShader(shaderType.random())]; }, checkArgValidity : function(o) { return GL.isShader(o); }, cleanup : function(o) { try { GL.deleteShader(o); } catch(e) {} } }, deleteTexture : { generate : function() { return [GL.createTexture()]; }, checkArgValidity : function(o) { return GL.isTexture(o); }, cleanup : function(o) { try { GL.deleteTexture(o); } catch(e) {} } }, depthFunc : { generate : function() { return [depthFuncFunc.random()]; }, checkArgValidity : function(f) { return depthFuncFunc.has(f); }, teardown : function() { GL.depthFunc(GL.LESS); } }, depthMask : { generate : function() { return [randomBool()]; }, teardown : function() { GL.depthFunc(GL.TRUE); } }, depthRange : { generate : function() { return [Math.random(), Math.random()]; }, teardown : function() { GL.depthRange(0, 1); } }, detachShader : { generate : function() { var p = GL.createProgram(); var sh = GL.createShader(shaderType.random()); GL.attachShader(p, sh); return [p, sh]; }, checkArgValidity : function(p, sh) { return GL.isProgram(p) && GL.isShader(sh) && GL.getAttachedShaders(p).has(sh); }, cleanup : function(p, sh) { try {GL.deleteProgram(p);} catch(e) {} try {GL.deleteShader(sh);} catch(e) {} } }, disable : { generate : function() { return [enableCap.random()]; }, checkArgValidity : function(c) { return enableCap.has(c); }, cleanup : function(c) { if (c == GL.DITHER) GL.enable(c); } }, disableVertexAttribArray : { generate : function() { return [randomVertexAttribute()]; }, checkArgValidity : function(v) { return isVertexAttribute(v); } }, drawArrays : {}, // FIXME drawElements : {}, // FIXME // E enable : { generate : function() { return [enableCap.random()]; }, checkArgValidity : function(c) { return enableCap.has(c); }, cleanup : function(c) { if (c != GL.DITHER) GL.disable(c); } }, enableVertexAttribArray : { generate : function() { return [randomVertexAttribute()]; }, checkArgValidity : function(v) { return isVertexAttribute(castToInt(v)); }, cleanup : function(v) { GL.disableVertexAttribArray(v); } }, // F finish : { generate : function() { return []; } }, flush : { generate : function() { return []; } }, framebufferRenderbuffer : {}, // FIXME framebufferTexture2D : {}, // FIXME frontFace : { generate : function() { return [frontFaceMode.random()]; }, checkArgValidity : function(c) { return frontFaceMode.has(c); }, cleanup : function(c) { GL.frontFace(GL.CCW); } }, // G generateMipmap : { setup : function() { var tex = GL.createTexture(); var tex2 = GL.createTexture(); GL.bindTexture(GL.TEXTURE_2D, tex); GL.bindTexture(GL.TEXTURE_CUBE_MAP, tex2); var pix = new Uint8Array(16*16*4); GL.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, 16, 16, 0, GL.RGBA, GL.UNSIGNED_BYTE, pix); GL.texImage2D(GL.TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL.RGBA, 16, 16, 0, GL.RGBA, GL.UNSIGNED_BYTE, pix); GL.texImage2D(GL.TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL.RGBA, 16, 16, 0, GL.RGBA, GL.UNSIGNED_BYTE, pix); GL.texImage2D(GL.TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL.RGBA, 16, 16, 0, GL.RGBA, GL.UNSIGNED_BYTE, pix); GL.texImage2D(GL.TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL.RGBA, 16, 16, 0, GL.RGBA, GL.UNSIGNED_BYTE, pix); GL.texImage2D(GL.TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL.RGBA, 16, 16, 0, GL.RGBA, GL.UNSIGNED_BYTE, pix); GL.texImage2D(GL.TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL.RGBA, 16, 16, 0, GL.RGBA, GL.UNSIGNED_BYTE, pix); }, generate : function() { return [bindTextureTarget.random()]; }, checkArgValidity : function(t) { return bindTextureTarget.has(t); }, teardown : function(tex, tex2) { GL.bindTexture(GL.TEXTURE_2D, null); GL.bindTexture(GL.TEXTURE_CUBE_MAP, null); GL.deleteTexture(tex); GL.deleteTexture(tex2); } }, getActiveAttrib : { /* FIXME the queried attrib needs to be an active one generate : function() { var program = GL.createProgram(); return [program, randomVertexAttribute()]; }, checkArgValidity : function(program, index) { return GL.isProgram(program) && isVertexAttribute(index); }, cleanup : function(program, index) { GL.deleteProgram(program); } */ }, getActiveUniform : {}, // FIXME getAttachedShaders : { setup : function() { var program = GL.createProgram(); var s1 = GL.createShader(GL.VERTEX_SHADER); var s2 = GL.createShader(GL.FRAGMENT_SHADER); GL.attachShader(program, s1); GL.attachShader(program, s2); return [program, s1, s2]; }, generate : function(program, s1, s2) { return [program] }, checkArgValidity : function(program) { return GL.isProgram(program); }, teardown : function(program, s1, s2) { GL.deleteProgram(program); GL.deleteShader(s1); GL.deleteShader(s2); } }, getAttribLocation : { generate : function() { var program = GL.createProgram(); var name = randomName(); GL.bindAttribLocation(program, randomVertexAttribute(), name); return [program, name]; }, checkArgValidity : function(program, name) { return GL.isProgram(program) && isValidName(name); }, cleanup : function(program, name) { try { GL.deleteProgram(program); } catch(e) {} } }, getParameter : { generate : function() { return [getParameterPname.random()]; }, checkArgValidity : function(p) { return getParameterPname.has(p); } }, getBufferParameter : {}, // FIXME getError : { generate : function() { return []; } }, getFramebufferAttachmentParameter : {}, // FIXME getProgramParameter : {}, // FIXME getProgramInfoLog : {}, // FIXME getRenderbufferParameter : {}, // FIXME getShaderParameter : {}, // FIXME getShaderInfoLog : {}, // FIXME getShaderSource : {}, // FIXME getString : {}, // FIXME getTexParameter : {}, // FIXME getUniform : {}, // FIXME getUniformLocation : {}, // FIXME getVertexAttrib : {}, // FIXME getVertexAttribOffset : {}, // FIXME // H hint : { generate : function() { return [GL.GENERATE_MIPMAP_HINT, mipmapHint.random()]; }, checkValidArgs : function(h, m) { return h == GL.GENERATE_MIPMAP_HINT && mipmapHint.has(m); }, teardown : function(){ GL.hint(GL.GENERATE_MIPMAP_HINT, GL.DONT_CARE); } }, // I isBuffer : { generate : function() { return [GL.createBuffer()]; }, cleanup : function(o) { try { GL.deleteBuffer(o); } catch(e) {} } }, isEnabled : { generate : function() { return [enableCap.random()]; }, checkArgValidity : function(c) { return enableCap.has(c); } }, isFramebuffer : { generate : function() { return [GL.createFramebuffer()]; }, cleanup : function(o) { try { GL.deleteFramebuffer(o); } catch(e) {} } }, isProgram : { generate : function() { return [GL.createProgram()]; }, cleanup : function(o) { try { GL.deleteProgram(o); } catch(e) {} } }, isRenderbuffer : { generate : function() { return [GL.createRenderbuffer()]; }, cleanup : function(o) { try { GL.deleteRenderbuffer(o); } catch(e) {} } }, isShader : { generate : function() { return [GL.createShader(shaderType.random())]; }, cleanup : function(o) { try { GL.deleteShader(o); } catch(e) {} } }, isTexture : { generate : function() { return [GL.createTexture()]; }, cleanup : function(o) { try { GL.deleteTexture(o); } catch(e) {} } }, // L lineWidth : { generate : function() { return [randomLineWidth()]; }, teardown : function() { GL.lineWidth(1); } }, linkProgram : {}, // FIXME // P pixelStorei : { generate : function() { return [pixelStoreiPname.random(), pixelStoreiParam.random()]; }, checkArgValidity : function(pname, param) { return pixelStoreiPname.has(pname) && pixelStoreiParam.has(param); }, teardown : function() { GL.pixelStorei(GL.PACK_ALIGNMENT, 4); GL.pixelStorei(GL.UNPACK_ALIGNMENT, 4); } }, polygonOffset : { generate : function() { return [randomFloat(), randomFloat()]; }, teardown : function() { GL.polygonOffset(0,0); } }, // R readPixels : {}, // FIXME renderbufferStorage : {}, // FIXME // S sampleCoverage : { generate : function() { return [randomFloatFromRange(0,1), randomBool()] }, teardown : function() { GL.sampleCoverage(1, false); } }, scissor : { generate : function() { return [randomInt(3000)-1500, randomInt(3000)-1500, randomIntFromRange(0,3000), randomIntFromRange(0,3000)]; }, checkArgValidity : function(x,y,w,h) { return castToInt(w) >= 0 && castToInt(h) >= 0; }, teardown : function() { GL.scissor(0,0,GL.canvas.width, GL.canvas.height); } }, shaderSource : {}, // FIXME stencilFunc : { generate : function(){ return [stencilFuncFunc.random(), randomInt(MaxStencilValue), randomInt(0xffffffff)]; }, checkArgValidity : function(func, ref, mask) { return stencilFuncFunc.has(func) && castToInt(ref) >= 0 && castToInt(ref) < MaxStencilValue; }, teardown : function() { GL.stencilFunc(GL.ALWAYS, 0, 0xffffffff); } }, stencilFuncSeparate : { generate : function(){ return [cullFace.random(), stencilFuncFunc.random(), randomInt(MaxStencilValue), randomInt(0xffffffff)]; }, checkArgValidity : function(face, func, ref, mask) { return cullFace.has(face) && stencilFuncFunc.has(func) && castToInt(ref) >= 0 && castToInt(ref) < MaxStencilValue; }, teardown : function() { GL.stencilFunc(GL.ALWAYS, 0, 0xffffffff); } }, stencilMask : { generate : function() { return [randomInt(0xffffffff)]; }, teardown : function() { GL.stencilMask(0xffffffff); } }, stencilMaskSeparate : { generate : function() { return [cullFace.random(), randomInt(0xffffffff)]; }, checkArgValidity : function(face, mask) { return cullFace.has(face); }, teardown : function() { GL.stencilMask(0xffffffff); } }, stencilOp : { generate : function() { return [stencilOp.random(), stencilOp.random(), stencilOp.random()]; }, checkArgValidity : function(sfail, dpfail, dppass) { return stencilOp.has(sfail) && stencilOp.has(dpfail) && stencilOp.has(dppass); }, teardown : function() { GL.stencilOp(GL.KEEP, GL.KEEP, GL.KEEP); } }, stencilOpSeparate : { generate : function() { return [cullFace.random(), stencilOp.random(), stencilOp.random(), stencilOp.random()]; }, checkArgValidity : function(face, sfail, dpfail, dppass) { return cullFace.has(face) && stencilOp.has(sfail) && stencilOp.has(dpfail) && stencilOp.has(dppass); }, teardown : function() { GL.stencilOp(GL.KEEP, GL.KEEP, GL.KEEP); } }, // T texImage2D : { noAlreadyTriedCheck : true, // Object.toSource is very slow here setup : function() { var tex = GL.createTexture(); var tex2 = GL.createTexture(); GL.bindTexture(GL.TEXTURE_2D, tex); GL.bindTexture(GL.TEXTURE_CUBE_MAP, tex2); return [tex, tex2]; }, generate : function() { if (Math.random() < 0.5) { var img = randomImage(16,16); var a = [ texImageTarget.random(), 0, img ]; if (Math.random() < 0.5) a.push(randomBool()); if (Math.random() < 0.5) a.push(randomBool()); return a; } else { var pix = null; if (Math.random > 0.5) { pix = new Uint8Array(16*16*4); } return [ texImageTarget.random(), 0, texImageInternalFormat.random(), 16, 16, 0, texImageFormat.random(), GL.UNSIGNED_BYTE, pix ]; } }, checkArgValidity : function(target, level, internalformat, width, height, border, format, type, data) { if (!texImageTarget.has(target) || castToInt(level) < 0) return false; if (arguments.length <= 5) { var image = internalformat; var flipY = width; var asPremultipliedAlpha = height; if (image instanceof HTMLImageElement || image instanceof HTMLVideoElement || image instanceof HTMLCanvasElement || (image.width && image.length && image.data)) return true; return false; } var w = castToInt(width), h = castToInt(height), b = castToInt(border); return texImageInternalFormat.has(internalformat) && w >= 0 && h >= 0 && b == 0 && (data == null || data.byteLength == w*h*4) && texImageFormat.has(format) && texImageType.has(type); }, teardown : function(tex, tex2) { GL.bindTexture(GL.TEXTURE_2D, null); GL.bindTexture(GL.TEXTURE_CUBE_MAP, null); GL.deleteTexture(tex); GL.deleteTexture(tex2); } }, texParameterf : { generate : function() { var pname = texParameterPname.random(); var param = texParameterParam[pname].random(); return [bindTextureTarget.random(), pname, param]; }, checkArgValidity : function(target, pname, param) { if (!bindTextureTarget.has(target)) return false; if (!texParameterPname.has(pname)) return false; return texParameterParam[pname].has(param); } }, texParameteri : { generate : function() { var pname = texParameterPname.random(); var param = texParameterParam[pname].random(); return [bindTextureTarget.random(), pname, param]; }, checkArgValidity : function(target, pname, param) { if (!bindTextureTarget.has(target)) return false; if (!texParameterPname.has(pname)) return false; return texParameterParam[pname].has(param); } }, texSubImage2D : {}, // FIXME // U uniform1f : {}, // FIXME uniform1fv : {}, // FIXME uniform1i : {}, // FIXME uniform1iv : {}, // FIXME uniform2f : {}, // FIXME uniform2fv : {}, // FIXME uniform2i : {}, // FIXME uniform2iv : {}, // FIXME uniform3f : {}, // FIXME uniform3fv : {}, // FIXME uniform3i : {}, // FIXME uniform3iv : {}, // FIXME uniform4f : {}, // FIXME uniform4fv : {}, // FIXME uniform4i : {}, // FIXME uniform4iv : {}, // FIXME uniformMatrix2fv : {}, // FIXME uniformMatrix3fv : {}, // FIXME uniformMatrix4fv : {}, // FIXME useProgram : {}, // FIXME // V validateProgram : {}, // FIXME vertexAttrib1f : {}, // FIXME vertexAttrib1fv : {}, // FIXME vertexAttrib2f : {}, // FIXME vertexAttrib2fv : {}, // FIXME vertexAttrib3f : {}, // FIXME vertexAttrib3fv : {}, // FIXME vertexAttrib4f : {}, // FIXME vertexAttrib4fv : {}, // FIXME vertexAttribPointer : {}, // FIXME viewport : { generate : function() { return [randomInt(3000)-1500, randomInt(3000)-1500, randomIntFromRange(0,3000), randomIntFromRange(0,3000)]; }, checkArgValidity : function(x,y,w,h) { return castToInt(w) >= 0 && castToInt(h) >= 0; }, teardown : function() { GL.viewport(0,0,GL.canvas.width, GL.canvas.height); } } }; mutateArgs = function(args) { var mutateCount = randomIntFromRange(1, args.length); var newArgs = Array.from(args); for (var i=0; i