UNPKG

node-webcl

Version:

A WebCL implementation for desktops with NodeJS

824 lines (695 loc) 25.7 kB
// Copyright (c) 2011-2012, Motorola Mobility, Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of the Motorola Mobility, Inc. nor the names of its // contributors may be used to endorse or promote products derived from this // software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. var nodejs = (typeof window === 'undefined'); if(nodejs) { webcl = require('../../../webcl'); clu = require('../../../lib/clUtils'); util = require('util'); fs = require('fs'); WebGL = require('node-webgl'); document = WebGL.document(); log = console.log; alert = console.log; ATB=document.AntTweakBar; Image = WebGL.Image; } else webcl = window.webcl; requestAnimationFrame = document.requestAnimationFrame; var use_gpu=true; main(); function CLGL() { var COMPUTE_KERNEL_FILENAME = "qjulia_kernel.cl"; var COMPUTE_KERNEL_METHOD_NAME = "QJuliaKernel"; var WIDTH = 512; var HEIGHT = 512; var /* cl_context */ ComputeContext; var /* cl_command_queue */ ComputeCommands; var /* cl_kernel */ ComputeKernel; var /* cl_program */ ComputeProgram; var /* cl_device_id */ ComputeDeviceId; var /* cl_device_type */ ComputeDeviceType; var /* cl_mem */ ComputeResult; var /* cl_mem */ ComputeImage; var /* size_t */ MaxWorkGroupSize; var /* int */ WorkGroupSize=[0,0]; var /* int */ WorkGroupItems = 32; var Width = WIDTH; var Height = HEIGHT; var Animated = true; var Update = true; var Reshaped = false; var newWidth, newHeight; // only when reshape var Epsilon = 0.003; var colorT = 0; var ColorA = [ 0.25, 0.45, 1, 1 ]; var ColorB = [ 0.25, 0.45, 1, 1 ]; var ColorC = [ 0.25, 0.45, 1, 1 ]; var t = 0; var MuA = [ -0.278, -0.479, 0, 0 ]; var MuB = [ 0.278, 0.479, 0, 0 ]; var MuC = [ -0.278, -0.479, -0.231, 0.235 ]; var gl; var shaderProgram; var TextureId = null; var TextureTarget; var TextureInternal; var TextureFormat; var TextureType; var TextureWidth = WIDTH; var TextureHeight = HEIGHT; var TextureTypeSize = 1; // sizeof(char); var ActiveTextureUnit; var HostImageBuffer = 0; var twBar; var canvas; var VertexPos = [ -1, -1, 1, -1, 1, 1, -1, 1 ]; var TexCoords = [ 0, 0, 1, 0, 1, 1, 0, 1 ]; var framework={ initialize: function(device_type) { log('Initializing'); document.setTitle("fbo"); canvas = document.createElement("fbo-canvas", Width, Height); // install UX callbacks document.addEventListener('resize', this.reshape); var err=this.setupGraphics(canvas); if(err != webcl.SUCCESS) return err; this.initAntTweakBar(canvas); err=this.setupComputeDevices(device_type); if(err != 0) return err; var image_support = ComputeDeviceId.getInfo(webcl.DEVICE_IMAGE_SUPPORT); if (!image_support) { printf("Unable to query device for image support"); process.exit(-1); } if (image_support == 0) { log("Application requires images: Images not supported on this device."); return webcl.IMAGE_FORMAT_NOT_SUPPORTED; } err = this.setupComputeKernel(); if (err != webcl.SUCCESS) { log("Failed to setup compute kernel! Error " + err); return err; } err = this.createComputeResult(); if(err != webcl.SUCCESS) { log ("Failed to create compute result! Error " + err); return err; } return webcl.SUCCESS; }, // ///////////////////////////////////////////////////////////////////// // OpenGL stuff // ///////////////////////////////////////////////////////////////////// createTexture: function (width, height) { log(' create texture'); if(TextureId) gl.deleteTexture(TextureId); TextureId = null; log("Creating Texture "+width+" x "+height+"..."); TextureWidth = width; TextureHeight = height; TextureTarget = gl.TEXTURE_2D; TextureInternal = gl.RGBA; TextureFormat = gl.RGBA; TextureType = gl.UNSIGNED_BYTE; ActiveTextureUnit = gl.TEXTURE0; gl.activeTexture(ActiveTextureUnit); TextureId=gl.createTexture(); gl.bindTexture(TextureTarget, TextureId); gl.texParameteri(TextureTarget, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(TextureTarget, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texImage2D(TextureTarget, 0, TextureInternal, TextureWidth, TextureHeight, 0, TextureFormat, TextureType, null); gl.bindTexture(TextureTarget, null); }, createBuffers: function() { log(' create buffers'); VertexPosBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, VertexPosBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(VertexPos), gl.STATIC_DRAW); VertexPosBuffer.itemSize = 2; VertexPosBuffer.numItems = 4; TexCoordsBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, TexCoordsBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(TexCoords), gl.STATIC_DRAW); TexCoordsBuffer.itemSize = 2; TexCoordsBuffer.numItems = 4; }, getShader: function (gl, id) { var shaders= { "shader-fs" : [ "#ifdef GL_ES", " precision mediump float;", "#endif", "varying vec2 vTextureCoord;", "uniform sampler2D uSampler;", "void main(void) {", " gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));", "}" ].join("\n"), "shader-vs" : [ "attribute vec3 aVertexPosition;", "attribute vec2 aTextureCoord;", "varying vec2 vTextureCoord;", "void main(void) {", " gl_Position = vec4(aVertexPosition, 1.0);", " vTextureCoord = aTextureCoord;", "}" ].join("\n") }; var shader; if(nodejs) { if (!shaders.hasOwnProperty(id)) return null; var str = shaders[id]; if (id.match(/-fs/)) { shader = gl.createShader(gl.FRAGMENT_SHADER); } else if (id.match(/-vs/)) { shader = gl.createShader(gl.VERTEX_SHADER); } else { return null; } } else { var shaderScript = document.getElementById(id); if (!shaderScript) { return null; } var str = ""; var k = shaderScript.firstChild; while (k) { if (k.nodeType == 3) { str += k.textContent; } k = k.nextSibling; } if (shaderScript.type == "x-shader/x-fragment") { shader = gl.createShader(gl.FRAGMENT_SHADER); } else if (shaderScript.type == "x-shader/x-vertex") { shader = gl.createShader(gl.VERTEX_SHADER); } else { return null; } } gl.shaderSource(shader, str); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { alert(gl.getShaderInfoLog(shader)); return null; } return shader; }, createShaders: function() { log(' create shaders'); var fragmentShader = this.getShader(gl, "shader-fs"); var vertexShader = this.getShader(gl, "shader-vs"); shaderProgram = gl.createProgram(); gl.attachShader(shaderProgram, vertexShader); gl.attachShader(shaderProgram, fragmentShader); gl.linkProgram(shaderProgram); if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { alert("Could not initialise shaders"); } gl.useProgram(shaderProgram); shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition"); gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord"); gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute); shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler"); }, setupGraphics:function(canvas) { log('Setup graphics'); try { gl = canvas.getContext("experimental-webgl"); gl.viewportWidth = canvas.width; gl.viewportHeight = canvas.height; } catch (e) { } if (!gl) { alert("Could not initialise WebGL, sorry :-(" ); return -1; } this.createBuffers(); this.createShaders(); this.createTexture(Width, Height); gl.clearColor (0, 0, 0, 0); gl.disable(gl.DEPTH_TEST); gl.activeTexture(gl.TEXTURE0); gl.viewport(0, 0, canvas.width,canvas.height); gl.activeTexture(gl.TEXTURE0); return webcl.SUCCESS; }, renderTexture: function( pvData ) { // we just draw a screen-aligned texture //gl.viewport( 0, 0, Width, Height ); gl.enable( TextureTarget ); gl.bindTexture( TextureTarget, TextureId ); if(pvData) gl.texSubImage2D(TextureTarget, 0, 0, 0, TextureWidth, TextureHeight, TextureFormat, TextureType, pvData); // draw screen aligned quad gl.bindBuffer(gl.ARRAY_BUFFER, VertexPosBuffer); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, VertexPosBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, TexCoordsBuffer); gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, TexCoordsBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.activeTexture(gl.TEXTURE0); gl.uniform1i(shaderProgram.samplerUniform, 0); gl.drawArrays(gl.TRIANGLE_FAN, 0, 4); gl.bindTexture( TextureTarget, null ); gl.disable( TextureTarget ); }, // ///////////////////////////////////////////////////////////////////// // OpenCL stuff // ///////////////////////////////////////////////////////////////////// setupComputeDevices:function(device_type) { log('setup compute devices'); // Pick platform var platformList = webcl.getPlatforms(); var platform = platformList[0]; var devices = platform.getDevices(ComputeDeviceType ? webcl.DEVICE_TYPE_GPU : webcl.DEVICE_TYPE_DEFAULT); ComputeDeviceId=devices[0]; // make sure we use a discrete GPU for(var i=0;i<devices.length;i++) { var vendor=devices[i].getInfo(webcl.DEVICE_VENDOR); // log('found vendor '+vendor+', is Intel? '+(vendor.indexOf('Intel')>=0)) if(vendor.indexOf('Intel')==-1) ComputeDeviceId=devices[i]; } log('found '+devices.length+' devices, using device: '+ComputeDeviceId.getInfo(webcl.DEVICE_NAME)); if(!ComputeDeviceId.enableExtension('KHR_gl_sharing')) throw new Error("Can NOT use GL sharing"); // create the OpenCL context try { ComputeContext = webcl.createContext(gl, ComputeDeviceId); } catch(err) { throw "Error: Failed to create context! "+err; } // Create a command queue // ComputeCommands = ComputeContext.createCommandQueue(ComputeDeviceId, 0); if (!ComputeCommands) { alert("Error: Failed to create a command queue!"); return -1; } // Report the device vendor and device name // var vendor_name = ComputeDeviceId.getInfo(webcl.DEVICE_VENDOR); var device_name = ComputeDeviceId.getInfo(webcl.DEVICE_NAME); log("Connecting to "+vendor_name+" "+device_name); return webcl.SUCCESS; }, setupComputeKernel:function() { log('setup compute kernel'); ComputeKernel = null; ComputeProgram = null; log("Loading kernel source from file '"+COMPUTE_KERNEL_FILENAME+"'..."); source = fs.readFileSync(__dirname + '/' + COMPUTE_KERNEL_FILENAME, 'ascii'); if (!source) { alert("Error: Failed to load kernel source!"); return -1; } var width_macro = "#define WIDTH"; var height_macro = "#define HEIGHT"; source = width_macro+" "+Width+'\n'+ height_macro+" "+Height+'\n'+ source; // Create the compute program from the source buffer // ComputeProgram = ComputeContext.createProgram(source); if (!ComputeProgram) { alert("Error: Failed to create compute program!"); return -1; } // Build the program executable // try { ComputeProgram.build(null, "-cl-fast-relaxed-math"); } catch (err) { log('Error building program: ' + err); alert("Error: Failed to build program executable!\n"+ ComputeProgram.getBuildInfo(ComputeDeviceId, webcl.PROGRAM_BUILD_LOG)); return -1; } // Create the compute kernel from within the program // log("Creating kernel '"+COMPUTE_KERNEL_METHOD_NAME+"'..."); ComputeKernel = ComputeProgram.createKernel(COMPUTE_KERNEL_METHOD_NAME); if (!ComputeKernel) { alert("Error: Failed to create compute kernel!"); return -1; } // Get the maximum work group size for executing the kernel on the device // MaxWorkGroupSize = ComputeKernel.getWorkGroupInfo(ComputeDeviceId, webcl.KERNEL_WORK_GROUP_SIZE); log("MaxWorkGroupSize: " + MaxWorkGroupSize); log("WorkGroupItems: " + WorkGroupItems); WorkGroupSize[0] = (MaxWorkGroupSize > 1) ? (MaxWorkGroupSize / WorkGroupItems) : MaxWorkGroupSize; WorkGroupSize[1] = MaxWorkGroupSize / WorkGroupSize[0]; log("WorkGroupSize: " + WorkGroupSize); return webcl.SUCCESS; }, createComputeResult: function() { log('create compute result'); ComputeImage = null; log("Allocating compute result image in device memory..."); ComputeImage = ComputeContext.createFromGLTexture(webcl.MEM_WRITE_ONLY, TextureTarget, 0, TextureId); if (!ComputeImage) { alert("Failed to create OpenGL texture reference! " + err); return -1; } ComputeResult = null; log("Allocating compute result buffer in device memory..."); ComputeResult = ComputeContext.createBuffer(webcl.MEM_WRITE_ONLY, TextureTypeSize * 4 * TextureWidth * TextureHeight); if (!ComputeResult) { log("Failed to create OpenCL array!"); return -1; } return webcl.SUCCESS; }, cleanup: function() { ComputeCommands.finish(); ComputeKernel=null; ComputeProgram=null; ComputeCommands=null; ComputeResult=null; ComputeImage=null; ComputeContext=null; }, shutdown: function() { log("Shutting down..."); this.cleanup(); process.exit(0); }, /* Before calling AntTweakBar or any other library that could use programs, * one must make sure to disable the VertexAttribArray used by the current * program otherwise this may have some unpredictable consequences aka * wrong vertex attrib arrays being used by another program! */ drawATB: function() { gl.disableVertexAttribArray(shaderProgram.vertexPositionAttribute); gl.disableVertexAttribArray(shaderProgram.textureCoordAttribute); gl.useProgram(null); gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); ATB.Draw(); gl.useProgram(shaderProgram); gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute); }, // ///////////////////////////////////////////////////////////////////// // rendering loop // ///////////////////////////////////////////////////////////////////// display: function(timestamp) { //FrameCount++; //var uiStartTime = new Date().getTime(); gl.clearColor (0, 0, 0, 0); gl.clear (gl.COLOR_BUFFER_BIT); if(Reshaped) { Reshaped=false; Width=newWidth; Height=newHeight; gl.viewport(0, 0, Width, Height); gl.clear(gl.COLOR_BUFFER_BIT); // make sure AntTweakBar is repositioned correctly and events correct ATB.WindowSize(Width,Height); } if(Animated) { t = this.updateMu( t, MuA, MuB ); this.interpolate( MuC, t, MuA, MuB ); colorT = this.updateColor( colorT, ColorA, ColorB ); this.interpolate(ColorC, colorT, ColorA, ColorB ); } var err = this.recompute(); if (err != 0) { alert("Error "+err+" from Recompute!"); process.exit(1); } this.renderTexture(HostImageBuffer); //this.reportInfo(); this.drawATB(); gl.finish(); // for timing //var uiEndTime = new Date().getTime(); //ReportStats(uiStartTime, uiEndTime); //DrawText(TextOffset[0], TextOffset[1], 1, (Animated == 0) ? "Press space to animate" : " "); return webcl.SUCCESS; }, reshape: function (evt) { newWidth=evt.width; newHeight=evt.height; log("reshape to "+newWidth+'x'+newHeight); Reshaped=true; }, keyboard: function( key, x, y ) { // TODO var fStepSize = 0.05; switch( key ) { case 27: exit(0); break; case ' ': Animated = !Animated; sprintf(InfoString, "Animated = %s\n", Animated ? "true" : "false"); ShowInfo = 1; break; case 'i': ShowInfo = ShowInfo > 0 ? false : true; break; case 's': ShowStats = ShowStats > 0 ? false : true; break; case '+': case '=': if(Epsilon >= 0.002) Epsilon *= (1.0 / 1.05); sprintf(InfoString, "Epsilon = %f\n", Epsilon); ShowInfo = true; break; case '-': if(Epsilon < 0.01) Epsilon *= 1.05; sprintf(InfoString, "Epsilon = %f\n", Epsilon); ShowInfo = true; break; case 'w': MuC[0] += fStepSize; break; case 'x': MuC[0] -= fStepSize; break; case 'q': MuC[1] += fStepSize; break; case 'z': MuC[1] -= fStepSize; break; case 'a': MuC[2] += fStepSize; break; case 'd': MuC[2] -= fStepSize; break; case 'e': MuC[3] += fStepSize; break; case 'c': MuC[3] -= fStepSize; break; case 'f': glutFullScreen(); break; } Update = true; }, interpolate: function( m, t, a, b ) { m[ 0 ] = ( 1 - t ) * a[ 0 ] + t * b[ 0 ]; m[ 1 ] = ( 1 - t ) * a[ 1 ] + t * b[ 1 ]; m[ 2 ] = ( 1 - t ) * a[ 2 ] + t * b[ 2 ]; m[ 3 ] = ( 1 - t ) * a[ 3 ] + t * b[ 3 ]; }, updateMu: function( t, a, b ) { t += 0.01; if ( t >= 1 ) { t = 0; a[ 0 ] = b[ 0 ]; a[ 1 ] = b[ 1 ]; a[ 2 ] = b[ 2 ]; a[ 3 ] = b[ 3 ]; b[ 0 ] = 2 * Math.random() - 1; b[ 1 ] = 2 * Math.random() - 1; b[ 2 ] = 2 * Math.random() - 1; b[ 3 ] = 2 * Math.random() - 1; } return t; }, randomColor: function( v ) { v[ 0 ] = 2 * Math.random() - 1; v[ 1 ] = 2 * Math.random() - 1; v[ 2 ] = 2 * Math.random() - 1; v[ 3 ] = 1; }, updateColor: function( t, a, b ) { t += 0.01; if ( t >= 1 ) { t = 0; a[ 0 ] = b[ 0 ]; a[ 1 ] = b[ 1 ]; a[ 2 ] = b[ 2 ]; a[ 3 ] = b[ 3 ]; this.randomColor(b); } return t; }, recompute: function() { if(!ComputeKernel || !ComputeResult) return webcl.SUCCESS; if(Animated || Update) { Update = false; try { ComputeKernel.setArg(0, ComputeResult); ComputeKernel.setArg(1, new Float32Array(MuC)); ComputeKernel.setArg(2, new Float32Array(ColorC)); ComputeKernel.setArg(3, new Float32Array([Epsilon])); } catch (err) { alert("Failed to set kernel args! " + err); return -10; } } var local= WorkGroupSize; var global = [ clu.DivUp(TextureWidth, local[0]) * local[0] , clu.DivUp(TextureHeight, local[1]) * local[1] ]; try { ComputeCommands.enqueueNDRangeKernel(ComputeKernel, 2, null, global, local); } catch(err) { alert("Failed to enqueue kernel! " + err); return err; } // sync GL gl.finish(); try { ComputeCommands.enqueueAcquireGLObjects(ComputeImage); } catch(err) { alert("Failed to acquire GL object! " + err); return -1; } var origin = [ 0, 0, 0]; var region = [ TextureWidth, TextureHeight, 1]; try { // TODO this should be shared between CL and GL not copied as in Apple code ComputeCommands.enqueueCopyBufferToImage(ComputeResult, ComputeImage, 0, origin, region); } catch(err) { alert("Failed to copy buffer to image! " + err); return -1; } try { ComputeCommands.enqueueReleaseGLObjects(ComputeImage); } catch (err) { alert("Failed to release GL object! " + err); return -1; } // sync CL ComputeCommands.finish(); return webcl.SUCCESS; }, initAntTweakBar: function (canvas) { ATB.Init(); ATB.Define(" GLOBAL help='Quaternion Julia using webcl.' "); // Message added to the help bar. ATB.WindowSize(canvas.width,canvas.height); twBar=new ATB.NewBar("qjulia"); twBar.AddVar("epsilon", ATB.TYPE_FLOAT, { getter: function(data){ return Epsilon; }, setter: function(val,data) { Epsilon=val; }, }, " label='epsilon' min=0.001 max=0.05 step=0.001 keyIncr=s keyDecr=S help='epsilon' "); twBar.AddVar("MuC", ATB.TYPE_QUAT4F, { getter: function(data){ return MuC; }, //setter: function(val,data) { MuC=val; }, }, " label='Mu' opened=true help='Mu' "); twBar.AddVar("Color", ATB.TYPE_COLOR4F, { getter: function(data){ return ColorC; }, //setter: function(val,data) { MuC=val; }, }, " label='Color' opened=true help='Color' "); ATB.Define(" qjulia valueswidth=fit"); // column width fits content } }; return framework; } function main() { // init window clgl=new CLGL(); if(clgl.initialize(use_gpu)==webcl.SUCCESS) { function update() { clgl.display(); requestAnimationFrame(update); } update(); } }