openseadragon/test/demo/old-plugins/via-webgl/viawebgl.js

202 lines
6.9 KiB
JavaScript

/*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~
/* viaWebGL
/* Set shaders on Image or Canvas with WebGL
/* Built on 2016-9-9
/* http://via.hoff.in
*/
ViaWebGL = function(incoming) {
/* Custom WebGL API calls
~*~*~*~*~*~*~*~*~*~*~*~*/
this['gl-drawing'] = function(e) { return e; };
this['gl-loaded'] = function(e) { return e; };
this.ready = function(e) { return e; };
var gl = this.maker();
this.flat = document.createElement('canvas').getContext('2d');
this.tile_size = 'u_tile_size';
this.vShader = 'vShader.glsl';
this.fShader = 'fShader.glsl';
this.wrap = gl.CLAMP_TO_EDGE;
this.tile_pos = 'a_tile_pos';
this.filter = gl.NEAREST;
this.pos = 'a_pos';
this.height = 128;
this.width = 128;
this.on = 0;
this.gl = gl;
// Assign from incoming terms
for (var key in incoming) {
this[key] = incoming[key];
}
};
ViaWebGL.prototype = {
init: function(source) {
var ready = this.ready;
// Allow for mouse actions on click
if (this.hasOwnProperty('container') && this.hasOwnProperty('onclick')) {
this.container.onclick = this[this.onclick].bind(this);
}
if (source && source.height && source.width) {
this.ready = this.toCanvas.bind(this,source);
this.height = source.height;
this.width = source.width;
}
this.source = source;
this.gl.canvas.width = this.width;
this.gl.canvas.height = this.height;
this.gl.viewport(0, 0, this.width, this.height);
// Load the shaders when ready and return the promise
var step = [[this.vShader, this.fShader].map(this.getter)];
step.push(this.toProgram.bind(this), this.toBuffers.bind(this));
return Promise.all(step[0]).then(step[1]).then(step[2]).then(this.ready);
},
// Make a canvas
maker: function(options){
return this.context(document.createElement('canvas'));
},
context: function(a){
return a.getContext('experimental-webgl') || a.getContext('webgl');
},
// Get a file as a promise
getter: function(where) {
return new Promise(function(done){
// Return if not a valid filename
if (where.slice(-4) != 'glsl') {
return done(where);
}
var bid = new XMLHttpRequest();
var win = function(){
if (bid.status == 200) {
return done(bid.response);
}
return done(where);
};
bid.open('GET', where, true);
bid.onerror = bid.onload = win;
bid.send();
});
},
// Link shaders from strings
toProgram: function(files) {
var gl = this.gl;
var program = gl.createProgram();
var ok = function(kind,status,value,sh) {
if (!gl['get'+kind+'Parameter'](value, gl[status+'_STATUS'])){
console.log((sh||'LINK')+':\n'+gl['get'+kind+'InfoLog'](value));
}
return value;
}
// 1st is vertex; 2nd is fragment
files.map(function(given,i) {
var sh = ['VERTEX_SHADER', 'FRAGMENT_SHADER'][i];
var shader = gl.createShader(gl[sh]);
gl.shaderSource(shader, given);
gl.compileShader(shader);
gl.attachShader(program, shader);
ok('Shader','COMPILE',shader,sh);
});
gl.linkProgram(program);
return ok('Program','LINK',program);
},
// Load data to the buffers
toBuffers: function(program) {
// Allow for custom loading
this.gl.useProgram(program);
this['gl-loaded'].call(this, program);
// Unchangeable square array buffer fills viewport with texture
var boxes = [[-1, 1,-1,-1, 1, 1, 1,-1], [0, 1, 0, 0, 1, 1, 1, 0]];
var buffer = new Float32Array([].concat.apply([], boxes));
var bytes = buffer.BYTES_PER_ELEMENT;
var gl = this.gl;
var count = 4;
// Get uniform term
var tile_size = gl.getUniformLocation(program, this.tile_size);
gl.uniform2f(tile_size, gl.canvas.height, gl.canvas.width);
// Get attribute terms
this.att = [this.pos, this.tile_pos].map(function(name, number) {
var index = Math.min(number, boxes.length-1);
var vec = Math.floor(boxes[index].length/count);
var vertex = gl.getAttribLocation(program, name);
return [vertex, vec, gl.FLOAT, 0, vec*bytes, count*index*vec*bytes];
});
// Get texture
this.tex = {
texParameteri: [
[gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, this.wrap],
[gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, this.wrap],
[gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, this.filter],
[gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this.filter]
],
texImage2D: [gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE],
bindTexture: [gl.TEXTURE_2D, gl.createTexture()],
drawArrays: [gl.TRIANGLE_STRIP, 0, count],
pixelStorei: [gl.UNPACK_FLIP_Y_WEBGL, 1]
};
// Build the position and texture buffer
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, buffer, gl.STATIC_DRAW);
},
// Turns image or canvas into a rendered canvas
toCanvas: function(tile) {
// Stop Rendering
if (this.on%2 !== 0) {
if(tile.nodeName == 'IMG') {
this.flat.canvas.width = tile.width;
this.flat.canvas.height = tile.height;
this.flat.drawImage(tile,0,0,tile.width,tile.height);
return this.flat.canvas;
}
return tile;
}
// Allow for custom drawing in webGL
this['gl-drawing'].call(this,tile);
var gl = this.gl;
// Set Attributes for GLSL
this.att.map(function(x){
gl.enableVertexAttribArray(x.slice(0,1));
gl.vertexAttribPointer.apply(gl, x);
});
// Set Texture for GLSL
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture.apply(gl, this.tex.bindTexture);
gl.pixelStorei.apply(gl, this.tex.pixelStorei);
// Apply texture parameters
this.tex.texParameteri.map(function(x){
gl.texParameteri.apply(gl, x);
});
// Send the tile into the texture.
var output = this.tex.texImage2D.concat([tile]);
gl.texImage2D.apply(gl, output);
// Draw everything needed to canvas
gl.drawArrays.apply(gl, this.tex.drawArrays);
// Apply to container if needed
if (this.container) {
this.container.appendChild(this.gl.canvas);
}
return this.gl.canvas;
},
toggle: function() {
this.on ++;
this.container.innerHTML = '';
this.container.appendChild(this.toCanvas(this.source));
}
}