2019-09-16 21:18:01 +02:00
|
|
|
import { Dictionary } from './util';
|
|
|
|
|
|
|
|
function error(msg: string) {
|
2019-09-17 20:19:04 +02:00
|
|
|
console.error(msg);
|
2019-09-16 21:18:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const defaultShaderType = [
|
|
|
|
"VERTEX_SHADER",
|
|
|
|
"FRAGMENT_SHADER"
|
|
|
|
];
|
|
|
|
|
2019-09-17 20:19:04 +02:00
|
|
|
/// Create Shader from Source string
|
2019-09-16 21:18:01 +02:00
|
|
|
function loadShader(
|
|
|
|
gl: WebGLRenderingContext,
|
|
|
|
shaderSource: string,
|
|
|
|
shaderType: number,
|
|
|
|
opt_errorCallback: any,
|
|
|
|
): WebGLShader {
|
|
|
|
var errFn = opt_errorCallback || error;
|
|
|
|
// Create the shader object
|
|
|
|
var shader = gl.createShader(shaderType);
|
|
|
|
|
|
|
|
// Load the shader source
|
|
|
|
gl.shaderSource(shader, shaderSource);
|
|
|
|
|
|
|
|
// Compile the shader
|
|
|
|
gl.compileShader(shader);
|
|
|
|
|
|
|
|
// Check the compile status
|
|
|
|
var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
|
|
|
|
if (!compiled) {
|
|
|
|
// Something went wrong during compilation; get the error
|
|
|
|
var lastError = gl.getShaderInfoLog(shader);
|
|
|
|
errFn("*** Error compiling shader '" + shader + "':" + lastError);
|
|
|
|
gl.deleteShader(shader);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log("created shader with source");
|
|
|
|
console.log(shaderSource);
|
|
|
|
|
|
|
|
return shader;
|
|
|
|
}
|
|
|
|
|
2019-09-17 20:19:04 +02:00
|
|
|
/// Actually Create Program with Shader's
|
2019-09-16 21:18:01 +02:00
|
|
|
function createProgram(
|
|
|
|
gl: WebGLRenderingContext,
|
|
|
|
shaders: WebGLShader[],
|
|
|
|
opt_attribs: string[],
|
|
|
|
opt_locations: number[],
|
|
|
|
opt_errorCallback: any,
|
|
|
|
): WebGLProgram {
|
|
|
|
var errFn = opt_errorCallback || error;
|
|
|
|
var program = gl.createProgram();
|
|
|
|
shaders.forEach(function (shader) {
|
|
|
|
gl.attachShader(program, shader);
|
|
|
|
});
|
|
|
|
if (opt_attribs) {
|
|
|
|
opt_attribs.forEach(function (attrib, ndx) {
|
|
|
|
gl.bindAttribLocation(
|
|
|
|
program,
|
|
|
|
opt_locations ? opt_locations[ndx] : ndx,
|
|
|
|
attrib);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
gl.linkProgram(program);
|
|
|
|
|
|
|
|
// Check the link status
|
|
|
|
var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
|
|
|
|
if (!linked) {
|
|
|
|
// something went wrong with the link
|
|
|
|
var lastError = gl.getProgramInfoLog(program);
|
|
|
|
errFn("Error in program linking:" + lastError);
|
|
|
|
|
|
|
|
gl.deleteProgram(program);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return program;
|
|
|
|
}
|
|
|
|
|
2019-09-17 20:19:04 +02:00
|
|
|
export class ShaderFactory {
|
|
|
|
frag_source: string;
|
|
|
|
vert_source: string;
|
2019-09-16 21:18:01 +02:00
|
|
|
|
2019-09-17 20:19:04 +02:00
|
|
|
static async create_factory(frag_url: string, vert_url: string): Promise<ShaderFactory> {
|
|
|
|
const sources = await Promise.all([
|
|
|
|
fetch(frag_url).then((r) => r.text()),
|
|
|
|
fetch(vert_url).then((r) => r.text()),
|
|
|
|
]);
|
2019-09-16 21:18:01 +02:00
|
|
|
|
2019-09-17 20:19:04 +02:00
|
|
|
return new ShaderFactory(sources[0], sources[1]);
|
2019-09-16 21:18:01 +02:00
|
|
|
}
|
|
|
|
|
2019-09-17 20:19:04 +02:00
|
|
|
constructor(frag_source: string, vert_source: string ) {
|
|
|
|
this.frag_source = frag_source;
|
|
|
|
this.vert_source = vert_source;
|
|
|
|
}
|
2019-09-16 21:18:01 +02:00
|
|
|
|
2019-09-17 20:19:04 +02:00
|
|
|
create_shader(
|
2019-09-16 21:18:01 +02:00
|
|
|
gl: WebGLRenderingContext,
|
2019-09-17 20:19:04 +02:00
|
|
|
context?: Dictionary<string>,
|
2019-09-16 21:18:01 +02:00
|
|
|
opt_attribs?: string[],
|
|
|
|
opt_locations?: number[],
|
|
|
|
opt_errorCallback?: any,
|
|
|
|
): Shader {
|
2019-09-17 20:19:04 +02:00
|
|
|
let vert = this.vert_source.slice();
|
|
|
|
let frag = this.frag_source.slice();
|
|
|
|
for (let key in context) {
|
|
|
|
vert = vert.replace(new RegExp("\\$" + key, 'g'), context[key]);
|
|
|
|
frag = frag.replace(new RegExp("\\$" + key, 'g'), context[key]);
|
2019-09-16 21:18:01 +02:00
|
|
|
}
|
2019-09-17 20:19:04 +02:00
|
|
|
|
|
|
|
const shaders = [
|
|
|
|
loadShader(gl, vert, gl.VERTEX_SHADER, opt_errorCallback),
|
|
|
|
loadShader(gl, frag, gl.FRAGMENT_SHADER, opt_errorCallback),
|
|
|
|
];
|
|
|
|
|
2019-09-16 21:18:01 +02:00
|
|
|
return new Shader(createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback));
|
|
|
|
}
|
2019-09-17 20:19:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export class Shader {
|
|
|
|
shader: WebGLProgram;
|
|
|
|
uniformCache: Dictionary<WebGLUniformLocation>;
|
|
|
|
attribCache: Dictionary<number>;
|
2019-09-16 21:18:01 +02:00
|
|
|
|
|
|
|
static async createProgramFromUrls(
|
|
|
|
gl: WebGLRenderingContext,
|
|
|
|
vert_url: string,
|
|
|
|
frag_url: string,
|
|
|
|
context?: Dictionary<string>,
|
|
|
|
opt_attribs?: string[],
|
|
|
|
opt_locations?: number[],
|
|
|
|
opt_errorCallback?: any,
|
|
|
|
): Promise<Shader> {
|
|
|
|
const sources = (await Promise.all([
|
|
|
|
fetch(vert_url).then((r) => r.text()),
|
|
|
|
fetch(frag_url).then((r) => r.text()),
|
|
|
|
])).map(x => {
|
|
|
|
for (let key in context) {
|
|
|
|
x = x.replace(new RegExp("\\$" + key, 'g'), context[key]);
|
|
|
|
}
|
|
|
|
return x;
|
|
|
|
});
|
|
|
|
|
|
|
|
const shaders = [
|
|
|
|
loadShader(gl, sources[0], 35633, opt_errorCallback),
|
|
|
|
loadShader(gl, sources[1], 35632, opt_errorCallback),
|
|
|
|
];
|
|
|
|
return new Shader(createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback));
|
|
|
|
}
|
|
|
|
|
|
|
|
constructor(shader: WebGLProgram) {
|
|
|
|
this.shader = shader;
|
|
|
|
this.uniformCache = {};
|
|
|
|
this.attribCache = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
bind(gl: WebGLRenderingContext) {
|
|
|
|
gl.useProgram(this.shader);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Different locations have different types :/
|
|
|
|
getUniformLocation(gl: WebGLRenderingContext, name: string): WebGLUniformLocation {
|
|
|
|
if (this.uniformCache[name] === undefined) {
|
|
|
|
this.uniformCache[name] = gl.getUniformLocation(this.shader, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.uniformCache[name];
|
|
|
|
}
|
|
|
|
|
|
|
|
getAttribLocation(gl: WebGLRenderingContext, name: string): number {
|
|
|
|
if (this.attribCache[name] === undefined) {
|
|
|
|
this.attribCache[name] = gl.getAttribLocation(this.shader, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.attribCache[name];
|
|
|
|
}
|
|
|
|
|
|
|
|
uniform<T extends Uniform>(
|
|
|
|
gl: WebGLRenderingContext,
|
|
|
|
name: string,
|
|
|
|
uniform: T,
|
|
|
|
) {
|
|
|
|
this.bind(gl);
|
|
|
|
const location = this.getUniformLocation(gl, name);
|
|
|
|
if (location < 0) {
|
|
|
|
console.log("No location found with name " + name);
|
|
|
|
}
|
|
|
|
|
|
|
|
uniform.setUniform(gl, location);
|
|
|
|
}
|
2019-09-17 20:19:04 +02:00
|
|
|
|
|
|
|
clear(gl: WebGLRenderingContext) {
|
|
|
|
gl.deleteProgram(this.shader);
|
|
|
|
}
|
2019-09-16 21:18:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface Uniform {
|
|
|
|
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation): void;
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Uniform2fv implements Uniform {
|
|
|
|
data: number[];
|
|
|
|
constructor(data: number[]) {
|
|
|
|
this.data = data;
|
|
|
|
}
|
|
|
|
|
|
|
|
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
|
|
|
|
gl.uniform2fv(location, this.data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Uniform3fv implements Uniform {
|
|
|
|
data: number[];
|
|
|
|
constructor(data: number[]) {
|
|
|
|
this.data = data;
|
|
|
|
}
|
|
|
|
|
|
|
|
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
|
|
|
|
gl.uniform3fv(location, this.data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-17 20:19:04 +02:00
|
|
|
export class Uniform3f implements Uniform {
|
|
|
|
x: number;
|
|
|
|
y: number;
|
|
|
|
z: number;
|
|
|
|
|
|
|
|
constructor(x: number, y: number, z: number) {
|
|
|
|
this.x = x;
|
|
|
|
this.y = y;
|
|
|
|
this.z = z;
|
|
|
|
}
|
|
|
|
|
|
|
|
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
|
|
|
|
gl.uniform3f(location, this.x ,this.y, this.z);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-16 21:18:01 +02:00
|
|
|
export class Uniform1iv implements Uniform {
|
|
|
|
data: number[];
|
|
|
|
constructor(data: number[]) {
|
|
|
|
this.data = data;
|
|
|
|
}
|
|
|
|
|
|
|
|
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
|
|
|
|
gl.uniform1iv(location, this.data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Uniform1i implements Uniform {
|
|
|
|
texture: number;
|
|
|
|
|
|
|
|
constructor(texture: number) {
|
|
|
|
this.texture = texture;
|
|
|
|
}
|
|
|
|
|
|
|
|
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
|
|
|
|
gl.uniform1i(location, this.texture);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Uniform1f implements Uniform {
|
|
|
|
texture: number;
|
|
|
|
|
|
|
|
constructor(texture: number) {
|
|
|
|
this.texture = texture;
|
|
|
|
}
|
|
|
|
|
|
|
|
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
|
|
|
|
gl.uniform1f(location, this.texture);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Uniform2f implements Uniform {
|
|
|
|
x: number;
|
|
|
|
y: number;
|
|
|
|
|
2019-09-17 20:19:04 +02:00
|
|
|
constructor(xy: number[]) {
|
|
|
|
this.x = xy[0];
|
|
|
|
this.y = xy[1];
|
2019-09-16 21:18:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
|
|
|
|
gl.uniform2f(location, this.x, this.y);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Uniform4f implements Uniform {
|
|
|
|
v0: number;
|
|
|
|
v1: number;
|
|
|
|
v2: number;
|
|
|
|
v3: number;
|
|
|
|
|
2019-09-17 20:19:04 +02:00
|
|
|
constructor(xyzw: number[]) {
|
|
|
|
this.v0 = xyzw[0];
|
|
|
|
this.v1 = xyzw[1];
|
|
|
|
this.v2 = xyzw[2];
|
|
|
|
this.v3 = xyzw[3];
|
2019-09-16 21:18:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
|
|
|
|
gl.uniform4f(location, this.v0, this.v1, this.v2, this.v3);
|
|
|
|
}
|
|
|
|
}
|