Commit abfba202 authored by amandaghassaei's avatar amandaghassaei

reworking gpu stuff

parent 900122ea
/**
* Created by ghassaei on 2/24/16.
*/
function initGPUMath(){
var glBoilerplate = initBoilerPlate();
var canvas = document.getElementById("glcanvas");
var gl = canvas.getContext("webgl", {antialias:false}) || canvas.getContext("experimental-webgl", {antialias:false});
gl.getExtension('OES_texture_float');
gl.disable(gl.DEPTH_TEST);
var maxTexturesInFragmentShader = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
console.log(maxTexturesInFragmentShader + " textures max");
function GPUMath(){
this.reset();
}
GPUMath.prototype.createProgram = function(programName, vertexShader, fragmentShader){
var programs = this.programs;
var program = programs[programName];
if (program) {
console.warn("already a program with the name " + programName);
return;
}
program = glBoilerplate.createProgramFromScripts(gl, vertexShader, fragmentShader);
gl.useProgram(program);
glBoilerplate.loadVertexData(gl, program);
programs[programName] = {
program: program,
uniforms: {}
};
};
GPUMath.prototype.initTextureFromData = function(name, width, height, typeName, data, shouldReplace){
var texture = this.textures[name];
if (!shouldReplace && texture) {
console.warn("already a texture with the name " + name);
return;
}
texture = glBoilerplate.makeTexture(gl, width, height, gl[typeName], data);
this.textures[name] = texture;
};
GPUMath.prototype.initFrameBufferForTexture = function(textureName){
var framebuffer = this.frameBuffers[textureName];
if (framebuffer) {
console.warn("framebuffer already exists for texture " + textureName);
return;
}
var texture = this.textures[textureName];
if (!texture){
console.warn("texture " + textureName + " does not exist");
return;
}
framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
this.frameBuffers[textureName] = framebuffer;
};
GPUMath.prototype.setUniformForProgram = function(programName, name, val, type){
if (!this.programs[programName]){
console.warn("no program with name " + programName);
return;
}
var uniforms = this.programs[programName].uniforms;
var location = uniforms[name];
if (!location) {
location = gl.getUniformLocation(this.programs[programName].program, name);
uniforms[name] = location;
}
if (type == "1f") gl.uniform1f(location, val);
else if (type == "2f") gl.uniform2f(location, val[0], val[1]);
else if (type == "3f") gl.uniform3f(location, val[0], val[1], val[2]);
else if (type == "1i") gl.uniform1i(location, val);
else {
console.warn("no uniform for type " + type);
}
};
GPUMath.prototype.setSize = function(width, height){
gl.viewport(0, 0, width, height);
canvas.clientWidth = width;
canvas.clientHeight = height;
};
GPUMath.prototype.setProgram = function(programName){
gl.useProgram(this.programs[programName].program);
};
GPUMath.prototype.step = function(programName, inputTextures, outputTexture){
gl.useProgram(this.programs[programName].program);
gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffers[outputTexture]);
for (var i=0;i<inputTextures.length;i++){
gl.activeTexture(gl.TEXTURE0 + i);
gl.bindTexture(gl.TEXTURE_2D, this.textures[inputTextures[i]]);
}
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);//draw to framebuffer
};
GPUMath.prototype.swapTextures = function(texture1Name, texture2Name){
var temp = this.textures[texture1Name];
this.textures[texture1Name] = this.textures[texture2Name];
this.textures[texture2Name] = temp;
temp = this.frameBuffers[texture1Name];
this.frameBuffers[texture1Name] = this.frameBuffers[texture2Name];
this.frameBuffers[texture2Name] = temp;
};
GPUMath.prototype.swap3Textures = function(texture1Name, texture2Name, texture3Name){
var temp = this.textures[texture3Name];
this.textures[texture3Name] = this.textures[texture2Name];
this.textures[texture2Name] = this.textures[texture1Name];
this.textures[texture1Name] = temp;
temp = this.frameBuffers[texture3Name];
this.frameBuffers[texture3Name] = this.frameBuffers[texture2Name];
this.frameBuffers[texture2Name] = this.frameBuffers[texture1Name];
this.frameBuffers[texture1Name] = temp;
};
GPUMath.prototype.readyToRead = function(){
return gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE;
};
GPUMath.prototype.readPixels = function(xMin, yMin, width, height, array){
gl.readPixels(xMin, yMin, width, height, gl.RGBA, gl.UNSIGNED_BYTE, array);
};
GPUMath.prototype.reset = function(){
this.programs = {};
this.frameBuffers = {};
this.textures = {};
this.index = 0;
};
return new GPUMath;
}
\ No newline at end of file
//from http://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html //from http://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html
/** function initBoilerPlate(){
* Creates and compiles a shader.
* /**
* @param {!WebGLRenderingContext} gl The WebGL Context. * Creates and compiles a shader.
* @param {string} shaderSource The GLSL source code for the shader. *
* @param {number} shaderType The type of shader, VERTEX_SHADER or * @param {!WebGLRenderingContext} gl The WebGL Context.
* FRAGMENT_SHADER. * @param {string} shaderSource The GLSL source code for the shader.
* @return {!WebGLShader} The shader. * @param {number} shaderType The type of shader, VERTEX_SHADER or
*/ * FRAGMENT_SHADER.
function compileShader(gl, shaderSource, shaderType) { * @return {!WebGLShader} The shader.
// Create the shader object */
var shader = gl.createShader(shaderType); function compileShader(gl, shaderSource, shaderType) {
// Create the shader object
// Set the shader source code. var shader = gl.createShader(shaderType);
gl.shaderSource(shader, shaderSource);
// Set the shader source code.
// Compile the shader gl.shaderSource(shader, shaderSource);
gl.compileShader(shader);
// Compile the shader
// Check if it compiled gl.compileShader(shader);
var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (!success) { // Check if it compiled
// Something went wrong during compilation; get the error var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
throw "could not compile shader:" + gl.getShaderInfoLog(shader); if (!success) {
} // Something went wrong during compilation; get the error
throw "could not compile shader:" + gl.getShaderInfoLog(shader);
return shader; }
}
return shader;
/** }
* Creates a program from 2 shaders.
* /**
* @param {!WebGLRenderingContext) gl The WebGL context. * Creates a program from 2 shaders.
* @param {!WebGLShader} vertexShader A vertex shader. *
* @param {!WebGLShader} fragmentShader A fragment shader. * @param {!WebGLRenderingContext) gl The WebGL context.
* @return {!WebGLProgram} A program. * @param {!WebGLShader} vertexShader A vertex shader.
*/ * @param {!WebGLShader} fragmentShader A fragment shader.
function createProgram(gl, vertexShader, fragmentShader) { * @return {!WebGLProgram} A program.
// create a program. */
var program = gl.createProgram(); function createProgram(gl, vertexShader, fragmentShader) {
// create a program.
// attach the shaders. var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader); // attach the shaders.
gl.attachShader(program, vertexShader);
// link the program. gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
// link the program.
// Check if it linked. gl.linkProgram(program);
var success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!success) { // Check if it linked.
// something went wrong with the link var success = gl.getProgramParameter(program, gl.LINK_STATUS);
throw ("program filed to link:" + gl.getProgramInfoLog (program)); if (!success) {
} // something went wrong with the link
throw ("program filed to link:" + gl.getProgramInfoLog (program));
return program; }
}
return program;
/** }
* Creates a shader from the content of a script tag.
* /**
* @param {!WebGLRenderingContext} gl The WebGL Context. * Creates a shader from the content of a script tag.
* @param {string} scriptId The id of the script tag. *
* @param {string} opt_shaderType. The type of shader to create. * @param {!WebGLRenderingContext} gl The WebGL Context.
* If not passed in will use the type attribute from the * @param {string} scriptId The id of the script tag.
* script tag. * @param {string} opt_shaderType. The type of shader to create.
* @return {!WebGLShader} A shader. * If not passed in will use the type attribute from the
*/ * script tag.
function createShaderFromScript(gl, scriptId, opt_shaderType) { * @return {!WebGLShader} A shader.
// look up the script tag by id. */
var shaderScript = document.getElementById(scriptId); function createShaderFromSource(gl, shaderSource, shaderType) {
if (!shaderScript) { return compileShader(gl, shaderSource, shaderType);
throw("*** Error: unknown script element" + scriptId); }
}
/**
// extract the contents of the script tag. * Creates a program from 2 script tags.
var shaderSource = shaderScript.text; *
* @param {!WebGLRenderingContext} gl The WebGL Context.
// If we didn't pass in a type, use the 'type' from * @param {string} vertexShaderId The id of the vertex shader script tag.
// the script tag. * @param {string} fragmentShaderId The id of the fragment shader script tag.
if (!opt_shaderType) { * @return {!WebGLProgram} A program
if (shaderScript.type == "x-shader/x-vertex") { */
opt_shaderType = gl.VERTEX_SHADER; function createProgramFromSource(
} else if (shaderScript.type == "x-shader/x-fragment") { gl, vertexShader, fragmentShader) {
opt_shaderType = gl.FRAGMENT_SHADER; var vertexShader = createShaderFromSource(gl, vertexShader, gl.VERTEX_SHADER);
} else if (!opt_shaderType) { var fragmentShader = createShaderFromSource(gl, fragmentShader, gl.FRAGMENT_SHADER);
throw("*** Error: shader type not set"); return createProgram(gl, vertexShader, fragmentShader);
}
function createProgramFromScripts(gl, vertexShaderId, fragmentShaderId) {
var vertexShader = createShaderFromScript(gl, vertexShaderId);
var fragmentShader = createShaderFromScript(gl, fragmentShaderId);
return createProgram(gl, vertexShader, fragmentShader);
}
function createShaderFromScript(gl, scriptId, opt_shaderType) {
// look up the script tag by id.
var shaderScript = document.getElementById(scriptId);
if (!shaderScript) {
throw("*** Error: unknown script element" + scriptId);
}
// extract the contents of the script tag.
var shaderSource = shaderScript.text;
// If we didn't pass in a type, use the 'type' from
// the script tag.
if (!opt_shaderType) {
if (shaderScript.type == "x-shader/x-vertex") {
opt_shaderType = gl.VERTEX_SHADER;
} else if (shaderScript.type == "x-shader/x-fragment") {
opt_shaderType = gl.FRAGMENT_SHADER;
} else if (!opt_shaderType) {
throw("*** Error: shader type not set");
}
}
return compileShader(gl, shaderSource, opt_shaderType);
}
function loadVertexData(gl, program) {
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ -1,-1, 1,-1, -1, 1, 1, 1]), gl.STATIC_DRAW);
// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
}
function makeTexture(gl, width, height, type, data){
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// Set the parameters so we can render any size image.
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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, type, data);
return texture;
}
return {
createProgramFromSource: createProgramFromSource,
createProgramFromScripts: createProgramFromScripts,
loadVertexData: loadVertexData,
makeTexture: makeTexture
} }
} };
\ No newline at end of file
return compileShader(gl, shaderSource, opt_shaderType);
}
/**
* Creates a program from 2 script tags.
*
* @param {!WebGLRenderingContext} gl The WebGL Context.
* @param {string} vertexShaderId The id of the vertex shader script tag.
* @param {string} fragmentShaderId The id of the fragment shader script tag.
* @return {!WebGLProgram} A program
*/
function createProgramFromScripts(
gl, vertexShaderId, fragmentShaderId) {
var vertexShader = createShaderFromScript(gl, vertexShaderId);
var fragmentShader = createShaderFromScript(gl, fragmentShaderId);
return createProgram(gl, vertexShader, fragmentShader);
}
\ No newline at end of file
...@@ -20,14 +20,36 @@ ...@@ -20,14 +20,36 @@
<script id="2d-render-shader" type="x-shader/x-fragment"> <script id="2d-render-shader" type="x-shader/x-fragment">
precision mediump float; precision mediump float;
uniform sampler2D u_image; //uniform sampler2D u_velocities;
uniform vec2 u_textureSize; //uniform vec2 u_textureSize;
void main() {
gl_FragColor = vec4(1, 0, 1, 1);
}
</script>
<script id="advectShader" type="x-shader/x-fragment">
precision mediump float;
const float COLOR_MIN = 0.2, COLOR_MAX = 0.4; //uniform sampler2D u_velocities;
//uniform sampler2D u_material;
//uniform vec2 u_textureSize;
//uniform float u_dt;
void main() { void main() {
float v = (COLOR_MAX - texture2D(u_image, gl_FragCoord.xy / u_textureSize)).y / (COLOR_MAX - COLOR_MIN);
gl_FragColor = vec4(v, v, v, 1); //vec2 fragCoord = gl_FragCoord.xy;
//vec2 currentVelocity = texture2D(u_velocities, fragCoord/u_textureSize).xy;
//implicitly solve advection
//vec2 pos = fragCoord - u_dt * currentVelocity;
// bilinear interp between nearest cells
//gl_FragColor = texture2D(u_material, pos/u_textureSize);
gl_FragColor = vec4(1,1,1,1);
} }
</script> </script>
...@@ -74,7 +96,8 @@ ...@@ -74,7 +96,8 @@
<script type="text/javascript" src="dependencies/jquery-3.1.0.min.js"></script> <script type="text/javascript" src="dependencies/jquery-3.1.0.min.js"></script>
<script type="text/javascript" src="dependencies/flat-ui.min.js"></script> <script type="text/javascript" src="dependencies/flat-ui.min.js"></script>
<script type="text/javascript" src="GlBoilerplate.js"></script> <script type="text/javascript" src="GLBoilerplate.js"></script>
<script type="text/javascript" src="GPUMath.js"></script>
<script type="text/javascript" src="main.js"></script> <script type="text/javascript" src="main.js"></script>
</head> </head>
<body> <body>
...@@ -89,9 +112,10 @@ ...@@ -89,9 +112,10 @@
<div class="modal-body"> <div class="modal-body">
<b>Fluid Simulation Shader</b><br/><br/> <b>Fluid Simulation Shader</b><br/><br/>
I used the following sources to write this simulation:<br/><br/> I used the following sources to write this simulation:<br/><br/>
<a href="https://pdfs.semanticscholar.org/84b8/c7b7eecf90ebd9d54a51544ca0f8ff93c137.pdf" target="_blank">Real-time ink simulation using a grid-particle method</a><br/> <a href="https://pdfs.semanticscholar.org/84b8/c7b7eecf90ebd9d54a51544ca0f8ff93c137.pdf" target="_blank">Real-time ink simulation using a grid-particle method</a> - a method for real-time simulation of ink
in water using a coarse-grained fluid simulation with a particle simulation on top.<br/>
<a href="http://http.developer.nvidia.com/GPUGems/gpugems_ch38.html" target="_blank">Fast Fluid Dynamics Simulation on the GPU</a> - a very well written tutorial about programming the Navier-Stokes equations on a GPU.<br/> <a href="http://http.developer.nvidia.com/GPUGems/gpugems_ch38.html" target="_blank">Fast Fluid Dynamics Simulation on the GPU</a> - a very well written tutorial about programming the Navier-Stokes equations on a GPU.<br/>
<a href="http://www.dgp.toronto.edu/people/stam/reality/Research/pdf/ns.pdf" target="_blank">Stable Fluids</a> - a paper about numerical methods for evaluating Navier-Stokes on a discrete grid.<br/> <a href="http://www.dgp.toronto.edu/people/stam/reality/Research/pdf/ns.pdf" target="_blank">Stable Fluids</a> - a paper about stable numerical methods for evaluating Navier-Stokes on a discrete grid.<br/>
<br/> <br/>
By <a href="http://www.amandaghassaei.com/" target="_blank">Amanda Ghassaei</a>, code on <a href="https://github.com/amandaghassaei/FluidSimulation" target="_blank">Github</a>. By <a href="http://www.amandaghassaei.com/" target="_blank">Amanda Ghassaei</a>, code on <a href="https://github.com/amandaghassaei/FluidSimulation" target="_blank">Github</a>.
<br/><br/> <br/><br/>
......
//used a lot of ideas from https://bl.ocks.org/robinhouston/ed597847175cf692ecce to clean this code up //used a lot of ideas from https://bl.ocks.org/robinhouston/ed597847175cf692ecce to clean this code up
var gl; var width, height;
var canvas;
var frameBuffers;
var states = [null, null];
var resizedLastState;
var resizedCurrentState;
var width;
var height;
var stepProgram;
var renderProgram;
var textureSizeLocation;
var textureSizeLocationRender;
var mouseCoordLocation; var mouseCoordLocation;
var mouseCoordinates = [null, null]; var mouseCoordinates = [null, null];
...@@ -24,6 +9,8 @@ var mouseEnable = false; ...@@ -24,6 +9,8 @@ var mouseEnable = false;
var paused = false;//while window is resizing var paused = false;//while window is resizing
var GPU;
window.onload = initGL; window.onload = initGL;
function initGL() { function initGL() {
...@@ -33,10 +20,7 @@ function initGL() { ...@@ -33,10 +20,7 @@ function initGL() {
$("#aboutModal").modal('show'); $("#aboutModal").modal('show');
}); });
// Get A WebGL context
canvas = document.getElementById("glcanvas"); canvas = document.getElementById("glcanvas");
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
canvas.onmousemove = onMouseMove; canvas.onmousemove = onMouseMove;
canvas.onmousedown = onMouseDown; canvas.onmousedown = onMouseDown;
...@@ -45,125 +29,31 @@ function initGL() { ...@@ -45,125 +29,31 @@ function initGL() {
window.onresize = onResize; window.onresize = onResize;
gl = canvas.getContext("webgl", {antialias:false}) || canvas.getContext("experimental-webgl", {antialias:false}); GPU = initGPUMath();
if (!gl) {
alert('Could not initialize WebGL, try another browser');
return;
}
gl.disable(gl.DEPTH_TEST);
gl.getExtension('OES_texture_float');
// setup a GLSL program // setup a GLSL programs
stepProgram = createProgramFromScripts(gl, "2d-vertex-shader", "2d-fragment-shader"); GPU.createProgram("advect", "2d-vertex-shader", "advectShader");
renderProgram = createProgramFromScripts(gl, "2d-vertex-shader", "2d-render-shader"); GPU.createProgram("render", "2d-vertex-shader", "2d-render-shader");
gl.useProgram(renderProgram);
loadVertexData(gl, renderProgram);
textureSizeLocationRender = gl.getUniformLocation(renderProgram, "u_textureSize");
gl.useProgram(stepProgram);
loadVertexData(gl, stepProgram);
textureSizeLocation = gl.getUniformLocation(stepProgram, "u_textureSize");
mouseCoordLocation = gl.getUniformLocation(stepProgram, "u_mouseCoord");
mouseEnableLocation = gl.getUniformLocation(stepProgram, "u_mouseEnable");
frameBuffers = [makeFrameBuffer(), makeFrameBuffer()];
resetWindow(); resetWindow();
gl.bindTexture(gl.TEXTURE_2D, states[0]);//original texture GPU.initFrameBufferForTexture("velocities");
render(); render();
} }
function loadVertexData(gl, program) {
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ -1,-1, 1,-1, -1, 1, 1, 1]), gl.STATIC_DRAW);
// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
}
function makeFrameBuffer(){
return gl.createFramebuffer();
}
function makeRandomArray(rgba){
for (var x=0;x<width;x++) {
for (var y=0;y<height;y++) {
var ii = (y*width + x) * 4;
var central_square = (x > width/2-10 && x < width/2+10 && y > height/2-10 && y < height/2+10);
if (central_square) {
rgba[ii] = 0.5 + Math.random() * 0.02 - 0.01;
rgba[ii + 1] = 0.25 + Math.random() * 0.02 - 0.01;
} else {
rgba[ii] = 1.0;
rgba[ii + 1] = 0;
}
}
}
return rgba;
}
function makeTexture(gl, data){
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// Set the parameters so we can render any size image.
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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, data);
return texture;
}
function render(){ function render(){
if (!paused) { if (!paused) {
gl.useProgram(stepProgram);
if (mouseEnable){
gl.uniform1f(mouseEnableLocation, 1);
gl.uniform2f(mouseCoordLocation, mouseCoordinates[0], mouseCoordinates[1]);
} else gl.uniform1f(mouseEnableLocation, 0);
if (resizedLastState) {
states[0] = resizedLastState;
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffers[0]);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, states[0], 0);
resizedLastState = null;
}
if (resizedCurrentState) {
states[1] = resizedCurrentState;
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffers[1]);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl