UNPKG

fury-js

Version:

WebGL Game Engine

451 lines (399 loc) 15.7 kB
// This module is essentially a GL Context Facade // There are - of necessity - a few hidden logical dependencies in this class // mostly with the render functions, binding buffers before calling a function draw /** @type {WebGL2RenderingContext} */ let gl; let currentShaderProgram, anisotropyExt, maxAnisotropy; let activeTexture = null; exports.init = function(canvas, contextAttributes) { gl = canvas.getContext('webgl2', contextAttributes); gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.enable(gl.DEPTH_TEST); // TODO: expose as method gl.enable(gl.CULL_FACE); // TODO: expose as method anisotropyExt = gl.getExtension("EXT_texture_filter_anisotropic"); if (anisotropyExt) { maxAnisotropy = gl.getParameter(anisotropyExt.MAX_TEXTURE_MAX_ANISOTROPY_EXT); } // Expect 32 texture locations, map a 0 based index to actual integer values TextureLocations.length = 0; let i = 0; while(gl["TEXTURE" + i.toString()]) { TextureLocations.push(gl["TEXTURE" + i.toString()]); i++; } }; exports.getContext = function() { return gl; }; exports.getContextLossExtension = function() { return gl.getExtension("WEBGL_lose_context"); }; // TODO: This should be called setClearColor exports.clearColor = function(r, g, b, a) { gl.clearColor(r, g, b, a); }; exports.clear = function() { gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); // TODO: this isn't necessary every frame gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); }; exports.clearDepth = function() { gl.clear(gl.DEPTH_BUFFER_BIT); }; // Shader / Shader Programs let ShaderType = exports.ShaderType = { Vertex: "vertex", Fragment: "fragment" }; exports.createShader = function(type, glsl) { let shader; if (type == ShaderType.Vertex) { shader = gl.createShader(gl.VERTEX_SHADER); } else if (type == ShaderType.Fragment) { shader = gl.createShader(gl.FRAGMENT_SHADER); } else { throw new Error("Unrecognised shader type '" + type + "'"); } gl.shaderSource(shader, glsl); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { throw new Error("Could not create shader " + gl.getShaderInfoLog(shader)); } return shader; }; exports.deleteShader = function(shader) { gl.deleteShader(shader); }; exports.createShaderProgram = function(vertexShader, fragmentShader) { let program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { throw new Error("Could not create shader program"); } return program; }; exports.useShaderProgram = function(shaderProgram) { currentShaderProgram = shaderProgram; gl.useProgram(shaderProgram); }; // Buffers exports.DataType = { "BYTE": 5120, // signed 8-bit integer "SHORT": 5122, // signed 16-bit integer "INT": 5124, // signed 32-bit integer "UNSIGNED_BYTE": 5121, // unsigned 8-bit integer "UNSIGNED_SHORT": 5123, // unsigned 16-bit integer "UNSIGNED_INT": 5125, // unsigned 32-bit integer "FLOAT": 5126, // 32-bit IEEE floating point number "HALF_FLOAT": 5131, // 16-bit IEEE floating point number }; exports.createBuffer = function(data, itemSize, indexed) { let buffer = gl.createBuffer(); if (!indexed) { gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW); } else { gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(data), gl.STATIC_DRAW); } buffer.itemSize = itemSize; buffer.numItems = Math.round(data.length / itemSize); return buffer; }; exports.createArrayBuffer = function(data, itemSize) { let buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW); buffer.itemSize = itemSize; buffer.numItems = Math.round(data.length / itemSize); return buffer; }; exports.createElementArrayBuffer = function(data, itemSize) { let buffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, gl.STATIC_DRAW); buffer.itemSize = itemSize; buffer.numItems = Math.round(data.length / itemSize); return buffer; }; // Textures let TextureLocations = exports.TextureLocations = []; exports.FilterType = { NEAREST: 9728, LINEAR: 9729, LINEAR_MIPMAP_NEAREST: 9985, LINEAR_MIPMAP_LINEAR: 9987 }; exports.createTexture = function(source, clamp, flipY, mag, min, generateMipmap, enableAniso) { let texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); // Binds into currently active texture location gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, !!flipY); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source); // If we want to create mipmaps manually provide an array source and put them into // different levels in texImage2D - you must provide all mipmap levels setTextureQuality(gl.TEXTURE_2D, mag, min, generateMipmap, enableAniso); if (clamp) { 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); } if (activeTexture && activeTexture.glTextureType == gl.TEXTURE_2D) { // rebind the active texture gl.bindTexture(activeTexture.glTextureType, activeTexture); } else { gl.bindTexture(gl.TEXTURE_2D, null); } // todo: adding properties to gl objects is arguably bad practice should really have a wrapper object // todo: test to see if on context loss if textures objects are cleared. texture.glTextureType = gl.TEXTURE_2D; return texture; }; /// width and height are of an individual texture exports.createTextureArray = function(source, width, height, imageCount, clamp, flipY, mag, min, generateMipmap, enableAniso) { let texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture); gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY); gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, gl.RGBA, width, height, imageCount, 0, gl.RGBA, gl.UNSIGNED_BYTE, source); setTextureQuality(gl.TEXTURE_2D_ARRAY, mag, min, generateMipmap, enableAniso); if (clamp) { gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); } gl.bindTexture(gl.TEXTURE_2D_ARRAY, null); texture.glTextureType = gl.TEXTURE_2D_ARRAY; return texture; }; exports.createTextureCube = function(sources) { let texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture); gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_X, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, sources[0]); gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, sources[1]); gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, sources[2]); gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Y, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, sources[3]); gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, sources[4]); gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Z, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, sources[5]); // todo: maybe reuse "setTextureQuality" gl.generateMipmap(gl.TEXTURE_CUBE_MAP); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); texture.glTextureType = gl.TEXTURE_CUBE_MAP; return texture; }; let setTextureQuality = function(glTextureType, mag, min, generateMipmap, enableAniso) { if (!mag) mag = gl.NEAREST; if (!min) min = gl.NEAREST; gl.texParameteri(glTextureType, gl.TEXTURE_MAG_FILTER, mag); gl.texParameteri(glTextureType, gl.TEXTURE_MIN_FILTER, min); if (enableAniso && anisotropyExt) { gl.texParameterf(glTextureType, anisotropyExt.TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy); } if (generateMipmap) { gl.generateMipmap(glTextureType); } }; exports.setTexture = function(location, texture) { gl.activeTexture(TextureLocations[location]); gl.bindTexture(texture.glTextureType, texture); activeTexture = texture; }; exports.DepthEquation = { LessThanOrEqual: "LEQUAL", LessThan: "LESS", GreaterThanOrEqual: "GEQUAL", GreaterThan: "GREATER", } exports.setDepthFunction = function(depthEquation) { gl.depthFunc(gl[depthEquation]); } // Blending exports.BlendEquation = { Add: "FUNC_ADD", Subtract: "FUNC_SUBTRACT", ReverseSubtract: "FUNC_REVERSE_SUBTRACT" }; exports.BlendType = { Zero: "ZERO", One: "ONE", ConstantAlpha: "CONSTANT_ALPHA", ConstantColour: "CONSTANT_COLOR", DestinationAlpha: "DST_ALPHA", DestinationColour: "DST_COLOR", SourceAlpha: "SRC_ALPHA", SourceColour: "SRC_COLOR", OneMinusConstantAlpha: "ONE_MINUS_CONSTANT_ALPHA", OneMinusConstantColour: "ONE_MINUS_CONSTANT_COLOR", OneMinusDestinationAlpha: "ONE_MINUS_DST_ALPHA", OneMinusDestinationColour: "ONE_MINUS_DST_COLOR", OneMinusSourceAlpha: "ONE_MINUS_SRC_ALPHA", OneMinusSourceColour: "ONE_MINUS_SRC_COLOR", SourceAlphaSaturate: "SRC_ALPHA_SATURATE" }; exports.enableBlending = function(sourceBlend, destinationBlend, equation) { if (equation) { gl.blendEquation(gl[equation]); } if(sourceBlend && destinationBlend) { gl.blendFunc(gl[sourceBlend], gl[destinationBlend]); } gl.enable(gl.BLEND); gl.depthMask(false); }; exports.enableSeparateBlending = function(sourceColorBlend, destinationColorBlend, sourceAlphaBlend, destinationAlphaBlend, equation) { gl.enable(gl.BLEND); if (equation) { // Does WebGL support separate blend equations? Do we want to? gl.blendEquation(gl[equation]); } if (sourceColorBlend && sourceAlphaBlend && destinationColorBlend && destinationAlphaBlend) { gl.blendFuncSeparate(gl[sourceColorBlend], gl[destinationColorBlend], gl[sourceAlphaBlend], gl[destinationAlphaBlend]); } gl.depthMask(false); } exports.disableBlending = function() { gl.disable(gl.BLEND); gl.depthMask(true); }; // Attributes and Uniforms exports.initAttribute = function(shaderProgram, name) { if(!shaderProgram.attributeLocations) { shaderProgram.attributeLocations = {}; } shaderProgram.attributeLocations[name] = gl.getAttribLocation(shaderProgram, name); }; exports.initUniform = function(shaderProgram, name) { if(!shaderProgram.uniformLocations) { shaderProgram.uniformLocations = {}; } shaderProgram.uniformLocations[name] = gl.getUniformLocation(shaderProgram, name); }; exports.enableAttribute = function(name) { let index = currentShaderProgram.attributeLocations[name]; if (index >= 0) { gl.enableVertexAttribArray(index); } else { console.warn("Attribute '" + name + "' does not have a valid attribute location to enable"); } }; exports.disableAttribute = function(name) { let index = currentShaderProgram.attributeLocations[name]; if (index >= 0) { gl.disableVertexAttribArray(currentShaderProgram.attributeLocations[name]); } else { console.warn("Attribute '" + name + "' does not have a valid attribute location to disable"); } }; exports.setAttribute = function(name, buffer) { gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.vertexAttribPointer(currentShaderProgram.attributeLocations[name], buffer.itemSize, gl.FLOAT, false, 0, 0); }; exports.setAttributeFloat = function(name, buffer, type) { /* Supported types: gl.BYTE, gl.SHORT, gl.UNSIGNED_BYTE, gl.UNSIGNED_SHORT, gl.FLOAT, gl.HALF_FLOAT: */ gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.vertexAttribPointer(currentShaderProgram.attributeLocations[name], buffer.itemSize, type, false, 0, 0); }; exports.setAttributeInteger = function(name, buffer, type) { /* Supported types: gl.BYTE, gl.UNSIGNED_BYTE, gl.SHORT, gl.UNSIGNED_SHORT, gl.INT, gl.UNSIGNED_INT */ gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.vertexAttribIPointer(currentShaderProgram.attributeLocations[name], buffer.itemSize, type, 0, 0); }; exports.setIndexedAttribute = function(buffer) { gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer); }; exports.setUniformBoolean = function(name, value) { gl.uniform1i(currentShaderProgram.uniformLocations[name], value); }; exports.setUniformFloat = function(name, value) { gl.uniform1f(currentShaderProgram.uniformLocations[name], value); }; exports.setUniformFloat2 = function(name, value1, value2) { gl.uniform2f(currentShaderProgram.uniformLocations[name], value1, value2); }; exports.setUniformFloat3 = function(name, value1, value2, value3) { gl.uniform3f(currentShaderProgram.uniformLocations[name], value1, value2, value3); }; exports.setUniformFloat4 = function(name, value1, value2, value3, value4) { gl.uniform4f(currentShaderProgram.uniformLocations[name], value1, value2, value3, value4); } exports.setUniformInteger = function(name, value) { gl.uniform1i(currentShaderProgram.uniformLocations[name], value); }; exports.setUniformVector2 = function(name, value) { gl.uniform2fv(currentShaderProgram.uniformLocations[name], value); } exports.setUniformVector3 = function(name, value) { gl.uniform3fv(currentShaderProgram.uniformLocations[name], value); }; exports.setUniformVector4 = function(name, value) { gl.uniform4fv(currentShaderProgram.uniformLocations[name], value); }; exports.setUniformMatrix3 = function(name, value) { gl.uniformMatrix3fv(currentShaderProgram.uniformLocations[name], false, value); }; exports.setUniformMatrix4 = function(name, value) { gl.uniformMatrix4fv(currentShaderProgram.uniformLocations[name], false, value); }; // Draw Functions let RenderMode = exports.RenderMode = { Triangles: "triangles", TriangleStrip: "triangleStrip", Lines: "lines", Points: "points" }; let drawTriangles = exports.drawTriangles = function(count) { gl.drawArrays(gl.TRIANGLES, 0, count); }; let drawTriangleStrip = exports.drawTriangleStrip = function(count) { gl.drawArrays(gl.TRIANGLE_STRIP, 0, count); }; let drawLines = exports.drawLines = function(count) { gl.drawArrays(gl.LINES, 0, count); }; let drawPoints = exports.drawPoints = function(count) { gl.drawArrays(gl.POINTS, 0, count); }; let drawIndexedTriangles = exports.drawIndexedTriangles = function(count, offset) { gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_SHORT, offset); }; let drawIndexedTriangleStrip = exports.drawIndexedTriangleStrip = function(count, offset) { gl.drawElements(gl.TRIANGLE_STRIP, count, gl.UNSIGNED_SHORT, offset); } let drawIndexedLines = exports.drawIndexedLines = function(count, offset) { gl.drawElements(gl.LINES, count, gl.UNSIGNED_SHORT, offset); }; let drawIndexedPoints = exports.drawIndexedPoints = function(count, offset) { gl.drawElements(gl.POINTS, count, gl.UNSIGNED_SHORT, offset); }; exports.draw = function(renderMode, count, indexed, offset) { switch (renderMode) { case RenderMode.Triangles: if (!indexed) { drawTriangles(count); } else { drawIndexedTriangles(count, offset); } break; case RenderMode.TriangleStrip: if (!indexed) { drawTriangleStrip(count); } else { drawIndexedTriangleStrip(count); } break; case RenderMode.Lines: if (!indexed) { drawLines(count); } else { drawIndexedLines(count, offset); } break; case RenderMode.Points: if (!indexed) { drawPoints(count); } else { drawIndexedPoints(count, offset); } break; default: throw new Error("Unrecognised renderMode '" + renderMode + "'"); } };