ts-game-engine
Version:
Simple WebGL game/render engine written in TypeScript
210 lines (209 loc) • 8.93 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const gl_matrix_1 = require("gl-matrix");
const PipelineState_1 = require("../Systems/Graphics/PipelineState");
class Shader {
constructor(scene, vsSource, fsSource) {
this.instancedAttributes = new Map();
this.uniforms = new Map();
this.context = scene.Game.GraphicsSystem.Context;
this.pipelineState = scene.Game.GraphicsSystem.PipelineState;
const vertexShader = this.CreateShader(WebGL2RenderingContext.VERTEX_SHADER, vsSource);
if (vertexShader === null) {
throw new Error();
}
const fragmentShader = this.CreateShader(WebGL2RenderingContext.FRAGMENT_SHADER, fsSource);
if (fragmentShader === null) {
this.context.deleteShader(vertexShader);
throw new Error();
}
const shaderProgram = this.CreateShaderProgram(vertexShader, fragmentShader);
if (shaderProgram === null) {
throw new Error();
}
this.program = shaderProgram;
}
get Program() { return this.program; }
get InstancedAttributes() { return this.instancedAttributes; }
;
get Uniforms() { return this.uniforms; }
;
Dispose() {
this.context.deleteProgram(this.program);
}
// Attributes and Uniforms ------------------------------------------------------------------------------------------------
DefineInstancedAttribute(name, location) {
if (location < 0) {
console.error("Instanced attribute location not valid");
return;
}
this.instancedAttributes.set(name, location);
}
DefineUniform(name, type) {
const location = this.context.getUniformLocation(this.program, name);
if (location === null) {
console.warn("Uniform not found: " + name);
return;
}
this.uniforms.set(name, { location: location, type: type, value: undefined });
}
// Uniforms are stored in shader program, so cache them in Shader to avoid unnecessary GL calls.
// The following SetUniform functions must always be called after glUseProgram.
SetInt1Uniform(uniformName, value) {
let uniformData = this.Uniforms.get(uniformName);
if (uniformData === undefined)
return;
if (uniformData.value !== undefined && uniformData.value === value)
return;
uniformData.value = value;
this.context.uniform1i(uniformData.location, value);
}
SetFloat2Uniform(uniformName, value) {
let uniformData = this.Uniforms.get(uniformName);
if (uniformData === undefined)
return;
if (uniformData.value !== undefined) {
if (gl_matrix_1.vec2.exactEquals(uniformData.value, value))
return;
}
else {
uniformData.value = gl_matrix_1.vec2.create();
}
gl_matrix_1.vec2.copy(uniformData.value, value);
this.context.uniform2f(uniformData.location, value[0], value[1]);
}
SetFloat3Uniform(uniformName, value) {
let uniformData = this.Uniforms.get(uniformName);
if (uniformData === undefined)
return;
if (uniformData.value !== undefined) {
if (gl_matrix_1.vec3.exactEquals(uniformData.value, value))
return;
}
else {
uniformData.value = gl_matrix_1.vec3.create();
}
gl_matrix_1.vec3.copy(uniformData.value, value);
this.context.uniform3f(uniformData.location, value[0], value[1], value[2]);
}
SetFloat4VectorUniform(uniformName, value) {
let uniformData = this.Uniforms.get(uniformName);
if (uniformData === undefined)
return;
if (uniformData.value !== undefined) {
let equals = true;
for (let v = 0; v < value.length; v++) {
if (uniformData.value[v] !== value[v]) {
equals = false;
break;
}
}
if (equals)
return;
}
else {
uniformData.value = new Float32Array(value.length);
}
for (let v = 0; v < value.length; v++)
uniformData.value[v] = value[v];
this.context.uniform4fv(uniformData.location, value);
}
SetMatrix3Uniform(uniformName, value) {
let uniformData = this.Uniforms.get(uniformName);
if (uniformData === undefined)
return;
if (uniformData.value !== undefined) {
if (gl_matrix_1.mat3.exactEquals(uniformData.value, value))
return;
}
else {
uniformData.value = gl_matrix_1.mat3.create();
}
gl_matrix_1.mat3.copy(uniformData.value, value);
this.context.uniformMatrix3fv(uniformData.location, false, value);
}
SetMatrix4Uniform(uniformName, value) {
let uniformData = this.Uniforms.get(uniformName);
if (uniformData === undefined)
return;
if (uniformData.value !== undefined) {
if (gl_matrix_1.mat4.exactEquals(uniformData.value, value))
return;
}
else {
uniformData.value = gl_matrix_1.mat4.create();
}
gl_matrix_1.mat4.copy(uniformData.value, value);
this.context.uniformMatrix4fv(uniformData.location, false, value);
}
SetSamplerUniform(uniformName, textureUnit, texture, type) {
if (textureUnit < 0 || textureUnit >= PipelineState_1.TEXTURE_UNIT_AMOUNT) {
console.error("Invalid texture unit value: " + textureUnit);
return;
}
let uniformData = this.Uniforms.get(uniformName);
if (uniformData === undefined)
return;
if (uniformData.value === undefined || uniformData.value !== textureUnit) {
uniformData.value = textureUnit;
this.context.uniform1i(uniformData.location, textureUnit);
}
this.pipelineState.BindTexture(type, texture, textureUnit);
}
// Shader creation --------------------------------------------------------------------------------------------------------
CreateShader(type, source) {
const shader = this.context.createShader(type);
if (shader === null) {
console.error(`Unable to create ${type === WebGL2RenderingContext.VERTEX_SHADER ? "vertex" : "fragment"} shader.`);
return null;
}
this.context.shaderSource(shader, source);
this.context.compileShader(shader);
if (!this.context.getShaderParameter(shader, WebGL2RenderingContext.COMPILE_STATUS)) {
console.error(`Error compiling the ${type === WebGL2RenderingContext.VERTEX_SHADER ? "vertex" : "fragment"} shader: ${this.context.getShaderInfoLog(shader)}`);
this.context.deleteShader(shader);
return null;
}
return shader;
}
CreateShaderProgram(vertexShader, fragmentShader) {
const shaderProgram = this.context.createProgram();
if (shaderProgram === null) {
this.context.deleteShader(vertexShader);
this.context.deleteShader(fragmentShader);
console.error("Unable to create shader program.");
return null;
}
this.context.attachShader(shaderProgram, vertexShader);
this.context.attachShader(shaderProgram, fragmentShader);
this.context.linkProgram(shaderProgram);
this.context.detachShader(shaderProgram, vertexShader);
this.context.detachShader(shaderProgram, fragmentShader);
this.context.deleteShader(vertexShader);
this.context.deleteShader(fragmentShader);
if (!this.context.getProgramParameter(shaderProgram, WebGL2RenderingContext.LINK_STATUS)) {
console.error("Unable to link shader program: " + this.context.getProgramInfoLog(shaderProgram));
this.context.deleteProgram(shaderProgram);
return null;
}
return shaderProgram;
}
static Get(scene, name, vsSource, psSource) {
let key = name;
let shader = this.shaders.get(key);
if (shader === undefined) {
shader = new Shader(scene, vsSource, psSource);
this.shaders.set(key, shader);
}
return shader;
}
static DisposeAll() {
for (let shader of this.shaders.values()) {
shader.Dispose();
}
this.shaders.clear();
}
}
exports.Shader = Shader;
// Shader Manager ---------------------------------------------------------------------------------------------------------
Shader.shaders = new Map();