├── .gitignore ├── .npmignore ├── index.js ├── package.json ├── readme.md ├── research.js └── test ├── index.js └── shaders ├── alpha.frag ├── blue.frag ├── test.vert └── uniforms.frag /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | test 7 | test.js 8 | demo/ 9 | .npmignore 10 | LICENSE.md -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Nogl gl-shader-output implementation 3 | * 4 | * @module nogl-shader-output 5 | */ 6 | var GLSL = require('glsl-transpiler'); 7 | var extend = require('xtend/mutable'); 8 | 9 | module.exports = create; 10 | 11 | 12 | /** 13 | * Shader constructor 14 | */ 15 | function create (shader, options) { 16 | options = extend({ 17 | width: 1, 18 | height: 1 19 | }, options); 20 | 21 | 22 | //obtain vert and frag shaders 23 | var vShader, fShader; 24 | 25 | if (typeof shader === 'string') { 26 | vShader = [ 27 | 'attribute vec2 position;', 28 | 'void main() {', 29 | ' gl_Position = vec4(position, 1.0, 1.0);', 30 | '}' 31 | ].join('\n'); 32 | 33 | fShader = shader; 34 | } 35 | else if (shader.vertShader != null) { 36 | vShader = shader._vref.src; 37 | } 38 | 39 | if (shader.fragShader != null) { 40 | fShader = shader._fref.src; 41 | }; 42 | 43 | 44 | var width = options.width, height = options.height; 45 | var threads = 1; 46 | var size = width * height / threads; 47 | 48 | 49 | 50 | //create compiler each time anew, as old compiler keeps secrets of old code 51 | var compile = new GLSL({ 52 | attribute: function (name) { 53 | //supposed to be fn arg, so just ignore safely 54 | return ''; 55 | } 56 | }); 57 | 58 | //get varying attributes from vertex shader 59 | //luckily we can run vertex shader for 4 point coords to get simple rect interpolation afterwards 60 | var vSource = compile(vShader); 61 | var fSource = compile(fShader); 62 | 63 | 64 | //save parsed uniforms and varyings 65 | var varyings = compile.compiler.varyings; 66 | var uniforms = compile.compiler.uniforms; 67 | 68 | //create varying range calculator 69 | if (Object.keys(varyings).length) { 70 | var processVerteces = new Function (` 71 | var gl_Position, gl_PointSize, position; 72 | 73 | ${vSource} 74 | 75 | var __pos = [[-1, -1], [-1, 3], [3, -1]]; 76 | 77 | return function (__u) { 78 | ${Object.keys(uniforms).map(function (name) { 79 | return `${name} = __u.${name}`; 80 | }).join(';\n')} 81 | 82 | var __varying = {${Object.keys(varyings).map(function (name) { 83 | return `${name}: []` 84 | }).join(',\n')}}; 85 | 86 | __pos.forEach(function (__position, __i) { 87 | gl_Position = [0, 0, 0, 0]; 88 | gl_PointSize = 1; 89 | position = __position; 90 | 91 | main(); 92 | 93 | ${Object.keys(varyings).map(function (name) { 94 | return `__varying.${name}[__i] = ${name};` 95 | }).join('\n')} 96 | }); 97 | 98 | return __varying; 99 | } 100 | `)(); 101 | } else { 102 | var processVerteces = function () { return {}; }; 103 | } 104 | 105 | 106 | //create number of threads 107 | var workers = []; 108 | for (var i = 0; i < threads; i++) { 109 | var worker = createWorker(i); 110 | workers.push(worker); 111 | } 112 | 113 | 114 | function createWorker (i) { 115 | //specify range for a shader 116 | var start = Math.floor(size * i), 117 | end = Math.floor(size * (i+1)); 118 | 119 | //process function is created in closure 120 | //so to avoid recreation of shader’s stuff on each call 121 | var createFragmentShader = new Function(` 122 | var gl_FragColor, gl_FragCoord; 123 | 124 | ${fSource} 125 | 126 | ${interpolate.toString()} 127 | 128 | return function __process (__u, __v) { 129 | ${Object.keys(uniforms).map(function (name) { 130 | return `${name} = __u.${name}`; 131 | }).join(';\n')} 132 | 133 | gl_FragColor = [0, 0, 0, 0]; 134 | gl_FragCoord = [0, 0, 1.0, 1.0]; 135 | 136 | //FIXME: if there is error this will hang indefinitely 137 | var __result = new Float32Array(${(end - start) * 4}), 138 | __x, __y, __i, __j; 139 | 140 | for (var __c = ${start * 4}, __offset = 0; __c < ${end * 4}; __c+=4, __offset += 4) { 141 | __j = (__c / ${width * 4})|0; 142 | __i = __c % ${width * 4} / 4; 143 | __y = (__j + 0.5) / ${height}; 144 | __x = (__i + 0.5) / ${width}; 145 | 146 | ${Object.keys(varyings).map(function (name) { 147 | return `${name} = interpolate(__v.${name}, __y, __x)`; 148 | }).join(';\n')} 149 | 150 | gl_FragCoord[0] = __i + 0.5; 151 | gl_FragCoord[1] = __j + 0.5; 152 | 153 | main(); 154 | 155 | __result[__offset ] = gl_FragColor[0]; 156 | __result[__offset + 1] = gl_FragColor[1]; 157 | __result[__offset + 2] = gl_FragColor[2]; 158 | __result[__offset + 3] = gl_FragColor[3]; 159 | } 160 | 161 | return __result; 162 | } 163 | `); 164 | 165 | var worker = createFragmentShader(); 166 | 167 | return worker; 168 | } 169 | 170 | 171 | //interpolate value on the range bilinearly 172 | function interpolate (v, x, y) { 173 | if (v[0].length) { 174 | var result = Array(v[0].length); 175 | for (var i = 0; i < result.length; i++) { 176 | result[i] = interpolate([v[0][i], v[1][i], v[2][i]], x, y); 177 | } 178 | return result; 179 | } 180 | 181 | //calc weights 182 | var a = v[0]; 183 | var b = (v[0] + v[1])*0.5; 184 | var d = (v[0] + v[2])*0.5; 185 | var c = (v[1] + v[2])*0.5; 186 | 187 | var val = a*(1-x)*(1-y) + c*x*y + b*(1-y)*x + d*(1-x)*y || 0; 188 | 189 | return val; 190 | } 191 | 192 | 193 | //process passed uniforms 194 | function draw (uniforms) { 195 | uniforms = uniforms || {}; 196 | 197 | var varyings = processVerteces(uniforms); 198 | var result = workers[0](uniforms, varyings); 199 | 200 | return result; 201 | } 202 | 203 | 204 | return draw; 205 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nogl-shader-output", 3 | "version": "1.4.2", 4 | "description": "Process fragment shader on a rectangular canvas, webgl-less", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test", 8 | "lib": "lib" 9 | }, 10 | "scripts": { 11 | "test": "node test/index.js", 12 | "test:browser": "budo test/index.js -- -t glslify-sync/transform" 13 | }, 14 | "files": [ 15 | "index.js" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/dfcreative/nogl-shader-output.git" 20 | }, 21 | "keywords": [ 22 | "test", 23 | "shader", 24 | "stackgl", 25 | "glsl-transpiler", 26 | "stack.gl", 27 | "unit", 28 | "testing", 29 | "glsl", 30 | "gpu", 31 | "fragment", 32 | "glslify", 33 | "nogl", 34 | "webgl" 35 | ], 36 | "author": "Deema Yvanow ", 37 | "license": "MIT", 38 | "bugs": { 39 | "url": "https://github.com/dfcreative/nogl-shader-output/issues" 40 | }, 41 | "homepage": "https://github.com/dfcreative/nogl-shader-output#readme", 42 | "dependencies": { 43 | "glsl-transpiler": "^1.3.0", 44 | "xtend": "^4.0.1" 45 | }, 46 | "devDependencies": { 47 | "almost-equal": "^1.1.0", 48 | "gl-shader": "^4.2.0", 49 | "gl-shader-output": "^2.0.1", 50 | "glslify": "^5.0.2", 51 | "glslify-sync": "^1.0.0", 52 | "is-browser": "^2.0.1", 53 | "ndarray": "^1.0.18", 54 | "ndarray-imshow": "^1.0.1", 55 | "nogl": "^1.0.0", 56 | "save-pixels": "^2.3.0", 57 | "tst": "^1.3.1", 58 | "webgl-context": "^2.2.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Process rectangular shaders without webgl and obtain the result. Can be used for shaders unit testing, audio processing etc. A nogl analog of [gl-shader-output](https://github.com/jam3/gl-shader-output) for node. 2 | 3 | 4 | [![npm install nogl-shader-output](https://nodei.co/npm/nogl-shader-output.png?mini=true)](https://npmjs.org/package/nogl-shader-output/) 5 | 6 | 7 | ```js 8 | var ShaderOutput = require('nogl-shader-output') 9 | 10 | //get a draw function for our test 11 | var draw = ShaderOutput(` 12 | precision mediump float; 13 | uniform float green; 14 | void main() { 15 | gl_FragColor = vec4(0.0, green, 0.0, 1.0); 16 | } 17 | `, { 18 | width: 1, 19 | height: 1 20 | }); 21 | 22 | //returns the frag color as [R, G, B, A] 23 | var color = draw() 24 | 25 | //we could also set uniforms before rendering 26 | var color2 = draw({ green: 0.5 }) 27 | 28 | //due to precision loss, you may want to use a fuzzy equality check 29 | var epsilon = 1e-5; 30 | var almostEqual = require('array-almost-equal') 31 | almostEqual(color2, [0.0, 0.5, 0.0, 1.0], epsilon) 32 | ``` 33 | 34 | ## API 35 | 36 | #### `draw = ShaderOutput(shader, options?)` 37 | 38 | Takes a gl-shader instance or fragment shader source and an options, returns a `draw` function. Possible options: 39 | 40 | - `width` the width of a drawing buffer, by default - 1 41 | - `height` the height of a drawing buffer, by default - 1 42 | 43 | The draw function has the following signature: 44 | 45 | ```js 46 | var fragColor = draw(uniforms?) 47 | ``` 48 | 49 | Where `uniforms` is an optional map of uniform names to values (such as `[x, y]` array for vec2), applied before rendering. 50 | 51 | The return value is the gl_FragColor RGBA of the canvas, in floats, such as `[0.5, 1.0, 0.25, 1.0]`. 52 | 53 | **Hint:** you can define varyings by passing gl-shader instance with custom vertex shader. To create gl-shader in node, you can use [nogl](https://github.com/dfcreative/nogl): 54 | 55 | ```js 56 | var gl = require('nogl')(); 57 | var shader = require('gl-shader')(gl, vertexSrc, fragmentSrc); 58 | var draw = require('nogl-shader-output')(shader); 59 | ``` 60 | 61 | 62 | ## Related 63 | 64 | * [gl-shader-output](http://npmjs.org/package/gl-shader-output) — a webgl version of fragment shader processor. 65 | * [audio-shader](https://github.com/audio-lab/audio-shader) — an example case of application of nogl-shader-output for processing audio. 66 | * [nogl](https://npmjs.org/package/nogl) — WebGL shim for node. -------------------------------------------------------------------------------- /research.js: -------------------------------------------------------------------------------- 1 | var test = require('tst'); 2 | 3 | 4 | test('The best way to provide environment', function () { 5 | /* 6 | setup: 7 | what is better - passing fns in environment 8 | or evaling fn each call, like shaders does 9 | 10 | results: 11 | - evaling fns each time in each shader call is slower as it seems shader creates inner function contexts each fn call, and the complexity of fn code affects the run of a function. 12 | - the best result is wrapping the most optimized short code by a cycle, the rest is unimportant. That implies that we should provide all the context variables before the cycle call and remove any technical lib access or prop reading code out of the cycle. 13 | - that means we cannot run eval function outside each call, we should provide the number of steps for it to run. eval(code, times) 14 | */; 15 | 16 | 17 | var max = 10e5; 18 | 19 | 20 | //✘ 21 | test('Inner definition', function () { 22 | function main () { 23 | fn1(fn2(1.2)); 24 | 25 | function fn1(a){ 26 | return a*a*a/2 + 1; 27 | } 28 | function fn2(a){ 29 | return a*a*a*a*a* 2; 30 | } 31 | } 32 | 33 | for (var i = 0; i < max; i++) { 34 | main(); 35 | } 36 | }); 37 | 38 | //~ 39 | test('Stdlib', function () { 40 | function main (_) { 41 | _.fn1(_.fn2(1.2)); 42 | } 43 | 44 | var _ = { 45 | fn1: function fn1 (a){ 46 | return a*a*a/2 + 1; 47 | }, 48 | fn2: function fn2 (a){ 49 | return a*a*a*a*a* 2; 50 | } 51 | } 52 | 53 | for (var i = 0; i < max; i++) { 54 | main(_) 55 | } 56 | }); 57 | 58 | //~ 59 | test('main.call', function () { 60 | function main () { 61 | this.fn1(this.fn2(1.2)); 62 | } 63 | 64 | var _ = { 65 | fn1: function fn1 (a){ 66 | return a*a*a/2 + 1; 67 | }, 68 | fn2: function fn2 (a){ 69 | return a*a*a*a*a* 2; 70 | } 71 | } 72 | 73 | for (var i = 0; i < max; i++) { 74 | main.call(_) 75 | } 76 | }); 77 | 78 | //~ 79 | test('Lib call', function () { 80 | var _ = { 81 | fn1: function fn1 (a){ 82 | return a*a*a/2 + 1; 83 | }, 84 | fn2: function fn2 (a){ 85 | return a*a*a*a*a* 2; 86 | }, 87 | main: function main () { 88 | this.fn1(this.fn2(1.2)); 89 | } 90 | } 91 | 92 | 93 | for (var i = 0; i < max; i++) { 94 | _.main(); 95 | } 96 | }); 97 | 98 | //✔ - this is possible only in case where the cycle is on the same scope as main 99 | test('Outer scope', function () { 100 | function main () { 101 | fn1(fn2(1.2)); 102 | } 103 | function fn1 (a){ 104 | return a*a*a/2 + 1; 105 | } 106 | function fn2 (a){ 107 | return a*a*a*a*a* 2; 108 | } 109 | 110 | for (var i = 0; i < max; i++) { 111 | main() 112 | } 113 | }); 114 | 115 | //✔ - inner cycle requires fn body being wrapped directly to it 116 | test('Inner cycle', function () { 117 | function main () { 118 | for (var i = 0; i < max; i++) { 119 | fn1(fn2(1.2)); 120 | } 121 | } 122 | function fn1 (a){ 123 | return a*a*a/2 + 1; 124 | } 125 | function fn2 (a){ 126 | return a*a*a*a*a* 2; 127 | } 128 | 129 | main(); 130 | }); 131 | 132 | //✔ - the best function call - with no external access 133 | //though troublesome to pass the long list of args 134 | test('Arguments', function () { 135 | function main (fn1, fn2) { 136 | fn1(fn2(1.2)); 137 | } 138 | function fn1 (a){ 139 | return a*a*a/2 + 1; 140 | } 141 | function fn2 (a){ 142 | return a*a*a*a*a* 2; 143 | } 144 | function fn3 (a){ 145 | return a*a*a/2 + 1; 146 | } 147 | function fn4 (a){ 148 | return a*a*a*a*a* 2; 149 | } 150 | function fn5 (a){ 151 | return a*a*a/2 + 1; 152 | } 153 | function fn6 (a){ 154 | return a*a*a*a*a* 2; 155 | } 156 | 157 | for (var i = 0; i < max; i++) { 158 | main(fn1, fn2, fn3, fn4, fn5, fn6); 159 | } 160 | }); 161 | 162 | //✔ 163 | test('Wrapped all included', function () { 164 | var f = new Function('max', 165 | ` 166 | function main (fn1, fn2) { 167 | fn1(fn2(1.2)); 168 | } 169 | function fn1 (a){ 170 | return a*a*a/2 + 1; 171 | } 172 | function fn2 (a){ 173 | return a*a*a*a*a* 2; 174 | } 175 | 176 | for (var i = 0; i < max; i++) { 177 | main(fn1, fn2) 178 | } 179 | ` 180 | ); 181 | f(max); 182 | }); 183 | 184 | //✔ 185 | test('Wrapped called outside', function () { 186 | var f = new Function('fn1', 'fn2', 187 | ` 188 | fn1(fn2(1.2)); 189 | 190 | ` 191 | ); 192 | 193 | var _ = { 194 | fn1: function fn1 (a){ 195 | return a*a*a/2 + 1; 196 | }, 197 | fn2: function fn2 (a){ 198 | return a*a*a*a*a* 2; 199 | } 200 | } 201 | 202 | for (var i = 0; i < max; i++) { 203 | f(_.fn1, _.fn2) 204 | } 205 | }); 206 | 207 | //✔ 208 | test('Wrapped called with lib', function () { 209 | var f = new Function('_', 'max', 210 | ` 211 | var fn1 = _.fn1, fn2 = _.fn2; 212 | 213 | for (var i = 0; i < max; i++) { 214 | fn1(fn2(1.2)); 215 | } 216 | 217 | ` 218 | ); 219 | 220 | var _ = { 221 | fn1: function fn1 (a){ 222 | return a*a*a/2 + 1; 223 | }, 224 | fn2: function fn2 (a){ 225 | return a*a*a*a*a* 2; 226 | } 227 | } 228 | 229 | f(_, max); 230 | }); 231 | }); 232 | 233 | 234 | 235 | test('Varying attributes', function () { 236 | /* 237 | What is the most efficient way to provide varying/attributes - a values changing for each function call? The fn callback seems to be awesome, but is it really fast? Or webgl-way is better? 238 | */; 239 | 240 | test.skip('Callback', function () { 241 | /* 242 | ✘ callbacks are not a good idea, as each cycle it will call additional fn, which is redundant thing 243 | */; 244 | }); 245 | 246 | test('Buffer access', function () { 247 | /* 248 | ✔ obviously pure math is better than fn call each cycle step. 249 | it should interpolate between side values. What is the good format for it? 250 | */; 251 | 252 | test('Array range', function () { 253 | /* 254 | Provide varying as array of left and right range values. 255 | Seems easy and pretty. And performant. 256 | */; 257 | 258 | eval(shader, { 259 | varying: { 260 | a: [[1, 2], [100, 200]] 261 | } 262 | }); 263 | }); 264 | 265 | }); 266 | }); 267 | 268 | 269 | test('✔ How do we interpolate varying?', function () { 270 | /** 271 | In basic webgl rendering varying values are interpolated between 3 verteces, which are walked by a rendered triangle -1..1 (clipped); so basically fragment shaders are interpolated by 3 verteces, linearly. 272 | */ 273 | 274 | test.skip('✘ Pass 3 points', function () { 275 | eval(shaderCode, { 276 | attributes: [p1, p2, p3], 277 | varying: [v1, v2, v3], 278 | 279 | }); 280 | 281 | /** 282 | * - In this case we need viewport size. 283 | * ? Also a question how to grab export - gl_FragColors set. 284 | */ 285 | }); 286 | 287 | test.skip('✘ Eval gl-shader', function () { 288 | var shader = Shader(vertex, fragment); 289 | 290 | eval(shader, { 291 | attributes: [a, b, c] 292 | }); 293 | 294 | /** 295 | * + we don’t need to pass varying - it is calculates automatically 296 | * - we still need viewport size 297 | * + we process verteces 298 | * - we attach to gl-shader 299 | * + we could’ve recognized simple strings at the same time 300 | * - we ignore shader’s own data bindings 301 | * + we could’ve eval shader only, without passing separate data, 302 | * or passing it right to the shader. 303 | * - shader binds to a gl context, we don’t have one. 304 | * - shader also ignores framebuffer (output) 305 | * - shader also requires using webgl buffer to bind data 306 | * it can’t use custom arrays 307 | * + anyways it’s nice idea to manage webbuffer 308 | * + shader reflects pointers to access to buffers for us. 309 | * - we can use that manually 310 | * + gl-shader-output compatible 311 | * ✘ too complicated all in all, too many questions 312 | */ 313 | }); 314 | 315 | test('✔ Eval quad shader', function () { 316 | /** 317 | * Like gl-shader-output, but eval textural shader instead. 318 | * 319 | * + passing size 1:1 becomes a gl-shader-output. 320 | * + avoids the problem of keeping sizes - it is one of the properties 321 | * + avoids the problem of inputs/outputs - they are quad buffers/ndarrays 322 | * + avoids problem of verteces - it is one huge renderer 323 | * + we don’t need to pass functions separately, shader eval will read them once and provide along with environment, bultins etc. 324 | * + it is perfect for 2d effects and for audio processing. 325 | */ 326 | var draw = ShaderEval({ 327 | shader: shader, 328 | gl: gl, 329 | //...webgl-context options 330 | }); 331 | 332 | var result = draw({ 333 | a: texture, 334 | b: vecArray, 335 | c: float 336 | }); 337 | 338 | test('✘ Should we stick to gl-shader-output?', function () { 339 | /* 340 | + it makes easy and compatible API 341 | - it is redundant: we create unnecessary shader and unused options 342 | - ideally we provide only API we ever need and nothing else, as everything else confuses things 343 | + but what is useful, not theoretically pure? 344 | - ✔ we can just recognize the shader, if passed one. That is going to be easy. 345 | - using gl-shader-output we also stick to gl-shader. That makes things difficultier with no reason. 346 | */ 347 | }); 348 | 349 | test('How do we pass attributes to a vertex shader?', function () { 350 | /* 351 | ? what are the use-cases for that? Something other than coords? 352 | - color, material, ... 353 | + all that can be passed through uniforms data, as we paint a single shader. 354 | */ 355 | }); 356 | 357 | //Resulting API: 358 | 359 | var draw = Eval({ 360 | width: 200, 361 | height: 120, 362 | gl: gl?, 363 | // ...context opts 364 | }); 365 | 366 | draw({ 367 | u1: [], 368 | u2: [] 369 | }); 370 | }); 371 | }); 372 | 373 | test('What should be the name of the package', function () { 374 | test.skip('gl-eval', function () { 375 | var ShaderEval = require('gl-eval'); 376 | 377 | /* 378 | - This is not a real gl-eval 379 | - The class name does not make any sense 380 | */ 381 | }); 382 | 383 | test('gl-shader-eval', function () { 384 | var ShaderEval = require('shader-eval')({ 385 | 386 | }); 387 | 388 | /* 389 | 390 | */ 391 | }); 392 | 393 | test('gl-draw', function () { 394 | var Draw = require('shader-draw') 395 | 396 | /* 397 | - It shouldn’t be called gl-shader-*, because shader supposes using real gl-shader. 398 | + Though we can use that shader really. 399 | */ 400 | }); 401 | 402 | test('gl-run', function () { 403 | 404 | }); 405 | }); -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var test = require('tst') 2 | var glslify = require('glslify-sync'); 3 | var almost = require('almost-equal'); 4 | var assert = require('assert'); 5 | var Shader = require('gl-shader'); 6 | var createGlContext = require('webgl-context'); 7 | var createNoglContext = require('nogl'); 8 | var createGl = require('gl-shader-output') 9 | var createNogl = require('../') 10 | var isBrowser = require('is-browser'); 11 | var ndarray = require('ndarray'); 12 | var savePixels = require('save-pixels'); 13 | 14 | 15 | /** 16 | * Add almost method 17 | */ 18 | assert.almost = function (x, y) { 19 | if (x && x.length != null && y && y.length != null) return x.every(function (xi, i) { 20 | try { 21 | assert.almost(xi, y[i]); 22 | } catch (e) { 23 | assert.fail(x, y, `${(x+'').slice(0,50)}...\n≈\n${(y+'').slice(0,50)}...\n\nspecifically x[${i}] == ${xi} ≈ ${y[i]}`, '≈') 24 | return false; 25 | } 26 | return true; 27 | }); 28 | 29 | var EPSILON = 10e-5; 30 | if (!almost(x, y, EPSILON)) assert.fail(x, y, 31 | `${x} ≈ ${y}`, '≈'); 32 | return true; 33 | }; 34 | 35 | 36 | test('should process single point', function() { 37 | var vShader = glslify('./shaders/test.vert') 38 | var fShader = glslify('./shaders/blue.frag') 39 | 40 | var max = 10e2; 41 | 42 | var draw = createNogl(fShader); 43 | assert.deepEqual(draw(), [0, 0, 1, 1]); 44 | }); 45 | 46 | 47 | test('gl vs nogl performance', function() { 48 | 49 | var vShader = glslify('./shaders/test.vert'); 50 | var fShader = glslify('./shaders/blue.frag'); 51 | 52 | var max = 7; 53 | 54 | var drawNogl = createNogl(fShader, { 55 | width: 1024, 56 | height: 1024 57 | }); 58 | 59 | //nogl is times faster to set up 60 | //but 2-3 times slower for processing big images 61 | test('nogl', function () { 62 | for (var i = 0; i < max; i++) { 63 | drawNogl(); 64 | } 65 | }); 66 | 67 | if (!isBrowser) return; 68 | var drawGl = createGl(fShader, { 69 | width: 1024, 70 | height: 1024 71 | }); 72 | test('webgl', function () { 73 | for (var i = 0; i < max; i++) { 74 | drawGl(); 75 | } 76 | }); 77 | }); 78 | 79 | 80 | test('should process more-than-one dimension input', function() { 81 | if (isBrowser) { 82 | var shader = Shader(createGlContext(), 83 | glslify('./shaders/test.vert'), 84 | glslify('./shaders/blue.frag') 85 | ); 86 | } else { 87 | var shader = glslify('./shaders/blue.frag'); 88 | } 89 | 90 | var draw = createNogl(shader, { 91 | width: 2, 92 | height: 2 93 | }); 94 | assert.deepEqual(draw(), [0,0,1,1, 0,0,1,1, 0,0,1,1, 0,0,1,1]); 95 | assert.deepEqual(draw(), [0,0,1,1, 0,0,1,1, 0,0,1,1, 0,0,1,1]); 96 | }); 97 | 98 | test('should be able to handle alpha', function() { 99 | if (isBrowser) { 100 | var shader = Shader(createGlContext(), 101 | glslify('./shaders/test.vert'), 102 | glslify('./shaders/alpha.frag') 103 | ); 104 | } else { 105 | var shader = glslify('./shaders/alpha.frag'); 106 | } 107 | 108 | var draw = createNogl(shader); 109 | assert.deepEqual(draw(), [0, 0, 1, 0]) 110 | }); 111 | 112 | 113 | test('should accept uniforms', function() { 114 | if (isBrowser) { 115 | var shader = Shader(createGlContext(), 116 | glslify('./shaders/test.vert'), 117 | glslify('./shaders/uniforms.frag') 118 | ); 119 | } else { 120 | var shader = glslify('./shaders/uniforms.frag'); 121 | } 122 | 123 | var input = [0, 0.25, 0.5, 1.0] 124 | var reversed = input.slice().reverse(); 125 | 126 | var draw = createNogl(shader) 127 | 128 | assert.almost(draw({ u_value: input, multiplier: 1.0 }), reversed, 0.01) 129 | assert.almost(draw({ u_value: input, multiplier: 3.0 }), [ 3, 1.5, 0.75, 0 ], 0.01) 130 | }); 131 | 132 | 133 | test('gl_FragCoord', function () { 134 | if (!isBrowser) return; 135 | var src = ` 136 | precision highp float; 137 | 138 | void main () { 139 | gl_FragColor = vec4(gl_FragCoord) / 40.0; 140 | } 141 | ` 142 | 143 | var drawGl = createGl(src, { 144 | width: 40, 145 | height: 40 146 | }) 147 | 148 | var drawNogl = createNogl(src, { 149 | width: 40, 150 | height: 40 151 | }) 152 | 153 | assert.almost(drawGl(), drawNogl()); 154 | }); 155 | 156 | test('Varyings', function () { 157 | var vSrc = ` 158 | attribute vec2 position; 159 | varying float offset; 160 | varying vec2 pos; 161 | 162 | void main() { 163 | offset = position.x; 164 | gl_Position = vec4(position, 1, 1); 165 | pos = position; 166 | } 167 | `; 168 | 169 | var fSrc = ` 170 | precision highp float; 171 | 172 | varying vec2 pos; 173 | varying float offset; 174 | uniform float mult; 175 | 176 | void main () { 177 | gl_FragColor = vec4(pos, offset * mult, 1.0); 178 | } 179 | `; 180 | 181 | 182 | var noglShader = Shader(createNoglContext(), vSrc, fSrc); 183 | 184 | if (!isBrowser) { 185 | var drawNogl = createNogl(noglShader, { 186 | width: 2, 187 | height: 2 188 | }); 189 | 190 | assert.almost(drawNogl({mult: 1}), [-0.5, -0.5, -0.5, 1, 0.5, -0.5, 0.5, 1, -0.5, 0.5, -0.5, 1, 0.5, 0.5, 0.5, 1]); 191 | return; 192 | } 193 | 194 | test('nogl', function () { 195 | var drawNogl = createNogl(noglShader, { 196 | width: 170, 197 | height: 170 198 | }); 199 | var arr = drawNogl({ 200 | mult: 0.9 201 | }); 202 | document.body.appendChild(savePixels(ndarray(arr.map(function (x,i) { 203 | if (i%4 === 0) return x*100; 204 | return x*255; 205 | }), [170, 170, 4]), 'canvas')); 206 | }); 207 | 208 | 209 | var glShader = Shader(createGlContext(), vSrc, fSrc); 210 | test('gl', function () { 211 | var drawGl = createGl(glShader, { 212 | width: 170, 213 | height: 170 214 | }); 215 | var arr = drawGl({ 216 | mult: 0.9 217 | }); 218 | 219 | document.body.appendChild(savePixels(ndarray(arr.map(function (x,i) { 220 | if (i%4 === 0) return x*100; 221 | return x*255; 222 | }), [170, 170, 4]), 'canvas')); 223 | }); 224 | 225 | 226 | var drawNogl = createNogl(noglShader, {width: 2, height: 2}); 227 | var drawGl = createGl(glShader, {width: 2, height: 2}); 228 | 229 | assert.almost(drawGl({ 230 | mult: 0.9 231 | }), drawNogl( { 232 | mult: 0.9 233 | })); 234 | }); 235 | 236 | 237 | test('Vertex uniforms', function () { 238 | if (!isBrowser) return; 239 | 240 | var vSrc = ` 241 | attribute vec2 position; 242 | varying vec2 offset; 243 | uniform float shift; 244 | 245 | void main() { 246 | gl_Position = vec4(position, 1, 1); 247 | offset = position + shift; 248 | } 249 | `; 250 | 251 | var fSrc = ` 252 | precision highp float; 253 | 254 | varying vec2 offset; 255 | uniform float scale; 256 | 257 | void main () { 258 | gl_FragColor = vec4(offset * scale, 1.0, 1.0); 259 | } 260 | `; 261 | 262 | var noglShader = Shader(createNoglContext(), vSrc, fSrc); 263 | test('nogl', function () { 264 | var drawNogl = createNogl(noglShader, { 265 | width: 300, 266 | height: 300 267 | }); 268 | var arr = drawNogl({ 269 | scale: 2, 270 | shift: 1 271 | }); 272 | document.body.appendChild(savePixels(ndarray(arr.map(function (x,i) { 273 | if (i%4 === 0) return x*100; 274 | return x*255; 275 | }), [300, 300, 4]), 'canvas')); 276 | }); 277 | 278 | 279 | var glShader = Shader(createGlContext(), vSrc, fSrc); 280 | test('gl', function () { 281 | var drawGl = createGl(glShader, { 282 | width: 300, 283 | height: 300 284 | }); 285 | var arr = drawGl({ 286 | scale: 2, 287 | shift: 1 288 | }); 289 | 290 | document.body.appendChild(savePixels(ndarray(arr.map(function (x,i) { 291 | if (i%4 === 0) return x*100; 292 | return x*255; 293 | }), [300, 300, 4]), 'canvas')); 294 | }); 295 | 296 | 297 | var drawNogl = createNogl(noglShader, { 298 | width: 20, 299 | height: 20 300 | }); 301 | var drawGl = createGl(glShader, { 302 | width: 20, 303 | height: 20 304 | }); 305 | 306 | assert.almost(drawGl({ 307 | scale: 0.9, 308 | shift: 0.5 309 | }), drawNogl( { 310 | scale: 0.9, 311 | shift: 0.5 312 | })); 313 | }); 314 | 315 | 316 | test.skip('Textures', function () { 317 | 318 | 319 | shader = Shader(gl, '\ 320 | precision mediump float;\ 321 | attribute vec2 position;\ 322 | varying vec2 uv;\ 323 | void main (void) {\ 324 | gl_Position = vec4(position, 0, 1);\ 325 | uv = vec2(position.x * 0.5 + 0.5, - position.y * 0.5 + 0.5);\ 326 | }\ 327 | ', '\ 328 | precision mediump float;\ 329 | uniform sampler2D image;\ 330 | varying vec2 uv;\ 331 | void main (void) {\ 332 | gl_FragColor = texture2D(image, uv);\ 333 | }\ 334 | '); 335 | 336 | shader.bind(); 337 | }); 338 | 339 | 340 | test('Nogl performance of heavy shaders', function () { 341 | var vSrc = ` 342 | attribute vec2 position; 343 | varying vec2 offset; 344 | uniform float shift; 345 | 346 | void main() { 347 | gl_Position = vec4(position, 1, 1); 348 | offset = position + shift; 349 | } 350 | `; 351 | 352 | var fSrc = ` 353 | #define MOD2 vec2(.16632,.17369) 354 | 355 | precision highp float; 356 | 357 | varying vec2 offset; 358 | uniform float scale; 359 | 360 | float hash11(float p) 361 | { 362 | vec2 p2 = fract(vec2(p) * MOD2); 363 | p2 += dot(p2.yx, p2.xy+19.19); 364 | return fract(p2.x * p2.y); 365 | } 366 | 367 | float noise (float x) 368 | { 369 | float p = floor(x); 370 | float f = fract(x); 371 | f = f*f*(3.0-2.0*f); 372 | return mix( hash11(p), hash11(p + 1.0), f)-.5; 373 | } 374 | 375 | void main () { 376 | gl_FragColor = vec4(offset * scale, noise(offset.x), 1.0); 377 | } 378 | `; 379 | 380 | max = 5; 381 | 382 | var noglShader = Shader(createNoglContext(), vSrc, fSrc); 383 | var drawNogl = createNogl(noglShader, { 384 | width: 300, 385 | height: 300, 386 | threads: 1 387 | }); 388 | 389 | test('nogl', function () { 390 | for (var i = 0; i < max; i++) { 391 | drawNogl({ 392 | scale: 2, 393 | shift: 1 394 | }); 395 | } 396 | var arr = drawNogl({ 397 | scale: 2, 398 | shift: 1 399 | }); 400 | if (!isBrowser) return; 401 | document.body.appendChild(savePixels(ndarray(arr.map(function (x,i) { 402 | if (i%4 === 0) return x*100; 403 | return x*255; 404 | }), [300, 300, 4]), 'canvas')); 405 | }); 406 | 407 | if (!isBrowser) return; 408 | 409 | 410 | var glShader = Shader(createGlContext(), vSrc, fSrc); 411 | test('gl', function () { 412 | var drawGl = createGl(glShader, { 413 | width: 300, 414 | height: 300 415 | }); 416 | for (var i = 0; i < max; i++) { 417 | drawGl({ 418 | scale: 2, 419 | shift: 1 420 | }); 421 | } 422 | var arr = drawGl({ 423 | scale: 2, 424 | shift: 1 425 | }); 426 | 427 | document.body.appendChild(savePixels(ndarray(arr.map(function (x,i) { 428 | if (i%4 === 0) return x*100; 429 | return x*255; 430 | }), [300, 300, 4]), 'canvas')); 431 | }); 432 | }); -------------------------------------------------------------------------------- /test/shaders/alpha.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | void main() { 4 | gl_FragColor = vec4(0.0, 0.0, 1.0, 0.0); 5 | } -------------------------------------------------------------------------------- /test/shaders/blue.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | void main() { 4 | gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0); 5 | } -------------------------------------------------------------------------------- /test/shaders/test.vert: -------------------------------------------------------------------------------- 1 | attribute vec2 position; 2 | 3 | void main() { 4 | gl_Position = vec4(position, 1.0, 1.0); 5 | } -------------------------------------------------------------------------------- /test/shaders/uniforms.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | uniform vec4 u_value; 4 | uniform float multiplier; 5 | 6 | void main() { 7 | gl_FragColor = vec4(u_value.wzyx) * multiplier; 8 | } --------------------------------------------------------------------------------