UNPKG

ts-game-engine

Version:

Simple WebGL game/render engine written in TypeScript

210 lines (209 loc) 8.93 kB
"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();