UNPKG

webcl-nodep

Version:

A fork of node-webcl without dependencies other than OpenCL

562 lines (465 loc) 15.5 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(); Image = WebGL.Image; log = console.log; alert = console.log; } requestAnimationFrame = document.requestAnimationFrame; var COMPUTE_KERNEL_FILENAME = "gradient.cl"; var COMPUTE_KERNEL_NAME = "compute"; var WIDTH = 800; var HEIGHT = 800; // cl stuff var /* cl_context */ ComputeContext; var /* cl_command_queue */ ComputeCommands; var /* cl_program */ ComputeProgram; var /* cl_device_id */ ComputeDevice; var /* cl_device_type */ ComputeDeviceType = WebCL.DEVICE_TYPE_GPU; var /* cl_image */ ComputePBO; var /* cl_kernel */ ckCompute; var max_workgroup_size, max_workitem_sizes; var Width = WIDTH; var Height = HEIGHT; var Reshaped = false; var Update = false; var newWidth, newHeight; // only when reshape // gl stuff var gl; var shaderProgram; var pbo; var TextureId = null; var TextureWidth = WIDTH; var TextureHeight = HEIGHT; var VertexPosBuffer, TexCoordsBuffer; function initialize() { log('Initializing'); document.setTitle("Test Image2D creation from GL"); var canvas = document.createElement("mycanvas", Width, Height); // install UX callbacks document.addEventListener('resize', reshape); document.addEventListener('keydown', keydown); var err = init_gl(canvas); if (err != WebCL.SUCCESS) return err; err = init_cl(); if (err != 0) return err; configure_shared_data(TextureWidth, TextureHeight); Update=true; return WebCL.SUCCESS; } // ///////////////////////////////////////////////////////////////////// // OpenGL stuff // ///////////////////////////////////////////////////////////////////// function configure_shared_data(width, height) { log('configure shared data'); if (TextureId) { gl.deleteTexture(TextureId); TextureId = null; } TextureWidth = width; TextureHeight = height; gl.activeTexture(gl.TEXTURE0); TextureId = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, TextureId); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, TextureWidth, TextureHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); gl.bindTexture(gl.TEXTURE_2D, null); // Create OpenCL representation of OpenGL PBO try { ComputePBO = ComputeContext.createFromGLTexture(WebCL.MEM_WRITE_ONLY, gl.TEXTURE_2D, 0, TextureId); } catch(ex) { alert("Error: Failed to create CL PBO buffer. "+ex); return -1; } } function init_buffers() { log(' create buffers'); var VertexPos = [ -1, -1, 1, -1, 1, 1, -1, 1 ]; var TexCoords = [ 0, 0, 1, 0, 1, 1, 0, 1 ]; 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; } function compile_shader(gl, id) { var shaders = { "shader-vs" : [ "attribute vec3 aCoords;", "attribute vec2 aTexCoords;", "varying vec2 vTexCoords;", "void main(void) {", " gl_Position = vec4(aCoords, 1.0);", " vTexCoords = aTexCoords;", "}" ].join("\n"), "shader-fs" : [ "#ifdef GL_ES", " precision mediump float;", "#endif", "varying vec2 vTexCoords;", "uniform sampler2D uSampler;", "void main(void) {", " gl_FragColor = texture2D(uSampler, vTexCoords.st);", "}" ].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; } function init_shaders() { log(' Init shaders'); var fragmentShader = compile_shader(gl, "shader-fs"); var vertexShader = compile_shader(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, "aCoords"); gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTexCoords"); gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute); shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler"); } function init_gl(canvas) { log('Init GL'); 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; } init_buffers(); init_shaders(); return WebCL.SUCCESS; } // ///////////////////////////////////////////////////////////////////// // OpenCL stuff // ///////////////////////////////////////////////////////////////////// function init_cl() { log('init CL'); // Pick platform var platformList = WebCL.getPlatforms(); var platform = platformList[0]; // create the OpenCL context ComputeContext = WebCL.createContext({ deviceType: ComputeDeviceType, shareGroup: gl, platform: platform }); var device_ids = ComputeContext.getInfo(WebCL.CONTEXT_DEVICES); if (!device_ids) { alert("Error: Failed to retrieve compute devices for context!"); return -1; } var device_found = false; for(var i=0,l=device_ids.length;i<l;++i ) { device_type = device_ids[i].getInfo(WebCL.DEVICE_TYPE); if (device_type == ComputeDeviceType) { ComputeDevice = device_ids[i]; device_found = true; break; } } if (!device_found) { alert("Error: Failed to locate compute device!"); return -1; } // Create a command queue // ComputeCommands = ComputeContext.createCommandQueue(ComputeDevice, 0); if (!ComputeCommands) { alert("Error: Failed to create a command queue!"); return -1; } // Report the device vendor and device name // var vendor_name = ComputeDevice.getInfo(WebCL.DEVICE_VENDOR); var device_name = ComputeDevice.getInfo(WebCL.DEVICE_NAME); log("Connecting to " + vendor_name + " " + device_name); if (!ComputeDevice.getInfo(WebCL.DEVICE_IMAGE_SUPPORT)) { log("Application requires images: Images not supported on this device."); return WebCL.IMAGE_FORMAT_NOT_SUPPORTED; } err = init_cl_buffers(); if (err != WebCL.SUCCESS) { log("Failed to create compute result! Error " + err); return err; } err = init_cl_kernels(); if (err != WebCL.SUCCESS) { log("Failed to setup compute kernel! Error " + err); return err; } return WebCL.SUCCESS; } function init_cl_kernels() { log(' setup CL kernel'); 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; } // Create the compute program from the source buffer // try { ComputeProgram = ComputeContext.createProgram(source); } catch(ex) { alert("Error: Failed to create compute program! "+ex); return -1; } // Build the program executable // try { ComputeProgram.build(ComputeDevice, "-cl-fast-relaxed-math"); } catch (err) { log('Error building program: ' + err); alert("Error: Failed to build program executable!\n" + ComputeProgram.getBuildInfo(ComputeDevice, WebCL.PROGRAM_BUILD_LOG)); return -1; } // Create the compute kernels from within the program // try { ckCompute = ComputeProgram.createKernel(COMPUTE_KERNEL_NAME); } catch(ex) { alert("Error: Failed to create compute row kernel! "+ex); return -1; } // Get the maximum work group size for executing the kernel on the device // max_workgroup_size = ckCompute.getWorkGroupInfo(ComputeDevice, WebCL.KERNEL_WORK_GROUP_SIZE); max_workitem_sizes=ComputeDevice.getInfo(WebCL.DEVICE_MAX_WORK_ITEM_SIZES); log(' max workgroup size: '+max_workgroup_size); log(' max workitem sizes: '+max_workitem_sizes); return WebCL.SUCCESS; } function resetKernelArgs(image_width, image_height) { log('Reset kernel args to image ' + image_width + "x" + image_height); // set the kernel args try { // Set the Argument values for the row kernel ckCompute.setArg(0, ComputePBO); } catch (err) { alert("Failed to set row kernel args! " + err); return -10; } return WebCL.SUCCESS; } function init_cl_buffers() { log(' create CL buffers'); return WebCL.SUCCESS; } function cleanup() { document.removeEventListener('resize', reshape); document.removeEventListener('keydown', keydown); ComputeCommands.finish(); ckBoxRowsTex = null; ckBoxColumns = null; RowSampler = null; ComputeProgram = null; ComputeCommands = null; ComputePBO = null; ComputeBufTemp=null; ComputeContext = null; } function shutdown() { log("Shutting down..."); cleanup(); process.exit(0); } // ///////////////////////////////////////////////////////////////////// // rendering loop // ///////////////////////////////////////////////////////////////////// function display(timestamp) { //FrameCount++; //var uiStartTime = new Date().getTime(); if (Reshaped) { log('reshaping texture'); Reshaped = false; Width = newWidth; Height = newHeight; configure_shared_data(Width,Height); resetKernelArgs(Width,Height); } var err = execute_kernel(); if (err != 0) { alert("Error " + err + " from execute_kernel!"); process.exit(1); } // we just draw a screen-aligned texture gl.viewport(0, 0, Width, Height); gl.enable(gl.TEXTURE_2D); gl.bindTexture(gl.TEXTURE_2D, TextureId); // 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(gl.TEXTURE_2D, null); gl.disable(gl.TEXTURE_2D); //reportInfo(); 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; } function reshape(evt) { newWidth = evt.width; newHeight = evt.height; Reshaped = true; } function keydown(evt) { log('process key: ' + evt.which); var oldr = iRadius; switch (evt.which) { case '='.charCodeAt(0): // + or = increases filter radius if ((MaxWorkGroupSize - (((iRadius + 1 + 15) / 16) * 16) - iRadius - 1) > 0) iRadius++; break; case '-'.charCodeAt(0): // - or _ decreases filter radius if (iRadius > 1) iRadius--; break; } if (oldr != iRadius) { Update = true; } } function execute_kernel() { //log('execute_kernel...'); if (Update) { Update = false; // Update filter parameters TextureWidth=Width; TextureHeight=Height; resetKernelArgs(TextureWidth, TextureHeight); } // Sync GL and acquire buffer from GL gl.flush(); ComputeCommands.enqueueAcquireGLObjects(ComputePBO); // Set global and local work sizes for row kernel var local = [ 16, max_workgroup_size/16 ]; var global = [ clu.DivUp(TextureWidth, local[0]) * local[0], clu.DivUp(TextureHeight, local[1]) * local[1] ]; try { ComputeCommands.enqueueNDRangeKernel(ckCompute, null, global, local); } catch (err) { alert("Failed to enqueue row kernel! " + err); return err; } // Release buffer ComputeCommands.enqueueReleaseGLObjects(ComputePBO); ComputeCommands.flush(); // Update the texture from the pbo gl.bindTexture(gl.TEXTURE_2D, TextureId); return WebCL.SUCCESS; } (function main() { // init window if(initialize()==WebCL.SUCCESS) { function update() { display(); requestAnimationFrame(update); } update(); } }());