import * as WebGLConst from 'webgl-constants';
import BlendMode from '../../BlendMode';
import Cache from './Cache';
import Context from '../../Context';
import DirectionalLight from '../../Lights/DirectionalLight';
import { Instances, BufferData } from './Instances';
import PointLight from '../../Lights/PointLight';
import { ProgramElement } from '../../Program';
import RenderAPI from '../RenderAPI';
import { FaceCulling, StateBlock } from '../../StateBlock';
import TextureCube from '../../Textures/TextureCube';
import TextureVideo from '../../Textures/TextureVideo';
import Type from '../../Types';
import TypesConverter from './TypesConverter';
import { VertexElement } from '../../VertexFormat';
// Unique instance
let instance = null;
/**
* WebGL renderer
*
* @category WebGL
* @extends {RenderAPI}
*/
class WebGL extends RenderAPI {
/**
* Constructor
*/
constructor() {
super();
// Singleton
if (!instance) {
instance = this;
}
/**
* Cache
*
* @type {WebGL.Cache}
* @private
*/
this.cache = new Cache();
/**
* Array with enabled attribut
*
* @type {Array.<boolean>}
* @private
*/
this.enabledVertexAttribArray = [];
/**
* WebGL instances
*
* @type {Instances}
* @private
*/
this.instances = new Instances();
/**
* Active states
*
* @type {StateBlock}
* @private
*/
this.state = new StateBlock();
// Init state block and types
TypesConverter.init();
this.initStateBlockWithDefaultValues(this.state);
return instance;
}
/**
* Get unique instance
*/
static getInstance() {
if (!instance) {
instance = new WebGL();
}
return instance;
}
/**
* Bind light
*
* @param {Light} light A Light instance
*/
bindLight(light) {
this.cache.lights.push(light);
}
/**
* Bind the given framebuffer
*
* @param {number} framebufferID An identifier, -1 to bind default the frame buffer
*/
bindFrameBuffer(framebufferID) {
const gl = Context.getActive();
if (framebufferID === -1) {
gl.bindFramebuffer(WebGLConst.GL_FRAMEBUFFER, null);
} else {
const webGLBuffer = this.instances.frameBuffers[framebufferID];
if (!webGLBuffer) {
return;
}
gl.bindFramebuffer(WebGLConst.GL_FRAMEBUFFER, webGLBuffer);
}
}
/**
* Bind texture to the the given slot
*
* @param {number} slot Targeted slot's index
* @param {Private.TextureInterface} texture A Texture instance
*/
bindTexture(slot, texture) {
if (!texture.isReady()) {
return;
}
const isTextureVideo = (texture instanceof TextureVideo);
let needUpdate = false;
// Retrieve context
const gl = Context.getActive();
// Create WebGL instance
let webGLTexture = this.instances.textures[texture.getUID()];
if (!webGLTexture) {
webGLTexture = gl.createTexture();
this.instances.textures[texture.getUID()] = webGLTexture;
needUpdate = true;
}
// Bind it!
if (this.cache.texture !== texture) {
gl.activeTexture(WebGLConst.GL_TEXTURE0 + slot);
gl.bindTexture(WebGLConst.GL_TEXTURE_2D, webGLTexture);
}
// Need to update the texture?
if (needUpdate) {
let imageSize = [0, 0];
// Unflip textures
gl.pixelStorei(WebGLConst.GL_UNPACK_FLIP_Y_WEBGL, true);
// Upload to the GPU
if (isTextureVideo) {
gl.texImage2D(WebGLConst.GL_TEXTURE_2D, 0, WebGLConst.GL_RGBA, WebGLConst.GL_RGBA, WebGLConst.GL_PIXEL_UNSIGNED_BYTE, texture.getVideoData());
} else {
// WebGL support image loading from HTMLImage instance and from array of pixels
const image = texture.getImage();
const data = image.getData();
imageSize = image.getSize();
if (data instanceof Image) {
gl.texImage2D(WebGLConst.GL_TEXTURE_2D, 0, WebGLConst.GL_RGBA, WebGLConst.GL_RGBA, WebGLConst.GL_PIXEL_UNSIGNED_BYTE, data);
} else {
gl.texImage2D(WebGLConst.GL_TEXTURE_2D, 0, WebGLConst.GL_RGBA, imageSize[0], imageSize[1], 0, WebGLConst.GL_RGBA, WebGLConst.GL_PIXEL_UNSIGNED_BYTE, data);
}
}
const isPOT = ((imageSize[0] & (imageSize[0] - 1)) === 0) && ((imageSize[1] & (imageSize[1] - 1)) === 0);
// Apply filters.
gl.texParameteri(WebGLConst.GL_TEXTURE_2D, WebGLConst.GL_TEXTURE_WRAP_S, (texture.isRepeated() ? WebGLConst.GL_REPEAT : WebGLConst.GL_CLAMP_TO_EDGE));
gl.texParameteri(WebGLConst.GL_TEXTURE_2D, WebGLConst.GL_TEXTURE_WRAP_T, (texture.isRepeated() ? WebGLConst.GL_REPEAT : WebGLConst.GL_CLAMP_TO_EDGE));
gl.texParameteri(WebGLConst.GL_TEXTURE_2D, WebGLConst.GL_TEXTURE_MAG_FILTER, (texture.isSmoothed() ? WebGLConst.GL_LINEAR : WebGLConst.GL_NEAREST));
const minFilter = (isPOT && texture.isMipmaped()) ? WebGLConst.GL_LINEAR_MIPMAP_NEAREST : WebGLConst.GL_LINEAR;
gl.texParameteri(WebGLConst.GL_TEXTURE_2D, WebGLConst.GL_TEXTURE_MIN_FILTER, (texture.isSmoothed() ? minFilter : WebGLConst.GL_NEAREST));
if (!isTextureVideo && isPOT && texture.isMipmaped()) {
gl.generateMipmap(WebGLConst.GL_TEXTURE_2D);
}
} else if (isTextureVideo) {
// Video needs to be updated continuously
gl.texImage2D(WebGLConst.GL_TEXTURE_2D, 0, WebGLConst.GL_RGBA, WebGLConst.GL_RGBA, WebGLConst.GL_PIXEL_UNSIGNED_BYTE, texture.getVideoData());
}
this.cache.texture = texture;
}
/**
* Bind texture cube to the the given slot
*
* @param {number} slot Targeted slot's index
* @param {TextureCube} texture A TextureCube instance
*/
bindTextureCube(slot, texture) {
// Cache
if (!texture.isReady()) {
return;
}
// Retrieve context
const gl = Context.getActive();
// Create geometry's data
let needUpdate = false;
let webGLTexture = this.instances.textures[texture.getUID()];
if (!webGLTexture) {
webGLTexture = gl.createTexture();
this.instances.textures[texture.getUID()] = webGLTexture;
needUpdate = true;
}
if (this.cache.texture !== texture) {
gl.activeTexture(WebGLConst.GL_TEXTURE0 + slot);
gl.bindTexture(WebGLConst.GL_TEXTURE_CUBE_MAP, webGLTexture);
}
// Need to update the texture?
if (needUpdate) {
const images = texture.getImages();
// Upload to the GPU
gl.texImage2D(WebGLConst.GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, WebGLConst.GL_RGBA, WebGLConst.GL_RGBA, WebGLConst.GL_PIXEL_UNSIGNED_BYTE, images[TextureCube.Face.Left].getData());
gl.texImage2D(WebGLConst.GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, WebGLConst.GL_RGBA, WebGLConst.GL_RGBA, WebGLConst.GL_PIXEL_UNSIGNED_BYTE, images[TextureCube.Face.Right].getData());
gl.texImage2D(WebGLConst.GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, WebGLConst.GL_RGBA, WebGLConst.GL_RGBA, WebGLConst.GL_PIXEL_UNSIGNED_BYTE, images[TextureCube.Face.Up].getData());
gl.texImage2D(WebGLConst.GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, WebGLConst.GL_RGBA, WebGLConst.GL_RGBA, WebGLConst.GL_PIXEL_UNSIGNED_BYTE, images[TextureCube.Face.Down].getData());
gl.texImage2D(WebGLConst.GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, WebGLConst.GL_RGBA, WebGLConst.GL_RGBA, WebGLConst.GL_PIXEL_UNSIGNED_BYTE, images[TextureCube.Face.Front].getData());
gl.texImage2D(WebGLConst.GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, WebGLConst.GL_RGBA, WebGLConst.GL_RGBA, WebGLConst.GL_PIXEL_UNSIGNED_BYTE, images[TextureCube.Face.Back].getData());
// Apply filters
gl.texParameteri(WebGLConst.GL_TEXTURE_CUBE_MAP, WebGLConst.GL_TEXTURE_MAG_FILTER, WebGLConst.GL_NEAREST);
gl.texParameteri(WebGLConst.GL_TEXTURE_CUBE_MAP, WebGLConst.GL_TEXTURE_MIN_FILTER, WebGLConst.GL_NEAREST);
}
this.cache.texture = texture;
}
/**
* Clear the rendering target
*
* @param {Color} color A Color instance
*/
clear(color) {
// Apply color.
if (!color.isEqual(this.cache.clearColor)) {
Context.getActive().clearColor(color.r, color.g, color.b, color.a);
this.cache.clearColor = color;
}
// Clear buffers
Context.getActive().clear(WebGLConst.GL_COLOR_BUFFER_BIT | WebGLConst.GL_DEPTH_BUFFER_BIT | WebGLConst.GL_DEPTH_BUFFER_BIT);
}
/**
* Clear cache.
*/
clearCache() {
this.cache.program = null;
this.cache.lights.length = 0;
}
/**
* Create a new frame buffer
*
* @return {number} An identifier to work with it later
*/
createFrameBuffer() {
const identifier = this.instances.frameBuffers.length;
const frameBuffer = Context.getActive().createFramebuffer();
this.instances.frameBuffers.push(frameBuffer);
return identifier;
}
/**
* Draw indexed primitives
*
* @param {DrawingMode} drawingMode Drawing mode to use
* @param {number} firstVertexIndex Index of the first vertex to draw, useful to draw some parts
* @param {number} vertexCount Vertex count to draw
*/
drawIndexedPrimitives(drawingMode, firstVertexIndex, vertexCount) {
Context.getActive().drawElements(TypesConverter.drawingModeToConstant.get(drawingMode), vertexCount, WebGLConst.GL_DATA_UNSIGNED_SHORT, firstVertexIndex);
this.disableVertexAttribArray();
}
/**
* Draw primitives
*
* @param {DrawingMode} drawingMode Drawing mode to use
* @param {number} firstVertexIndex Index of the first vertex to draw, useful to draw some parts
* @param {number} vertexCount Vertex count to draw
*/
drawPrimitives(drawingMode, firstVertexIndex, vertexCount) {
Context.getActive().drawArrays(TypesConverter.drawingModeToConstant.get(drawingMode), firstVertexIndex, vertexCount);
this.disableVertexAttribArray();
}
/**
* Disable enabled vertex attributs array
*
* @private
*/
disableVertexAttribArray() {
// Retrieve context.
const gl = Context.getActive();
// Disable attributs
for (const i in this.enabledVertexAttribArray) {
gl.disableVertexAttribArray(i);
}
}
/**
* Init frame buffer: attach it to textures, depth buffer and/or a stencil buffer
*
* @param {number} framebufferID Targeted slot's index
* @param {Array.<Texture>} textures An array of Texture instances
* @param {boolean=} useDepthBuffer True to use a depth buffer
* @param {boolean=} useStencilBuffer True to use a stencil buffer
*/
initFrameBuffer(framebufferID, textures, useDepthBuffer = true, useStencilBuffer = false) {
// Ensure FBO is ready
const webGLBuffer = this.instances.frameBuffers[framebufferID];
if (!webGLBuffer) {
return;
}
// Retrieve context
const gl = Context.getActive();
let size = [0, 0]; // We will retrieve FBO's size from his textures
// Bind frame buffer
this.bindFrameBuffer(framebufferID);
// Attach textures.
for (let i = 0; i < textures.length; i += 1) {
// Force texture creation
this.bindTexture(i, textures[i]);
// Retrieve size
size = textures[i].getImage().getSize();
// Attach texture
const webGLTexture = this.instances.textures[textures[i].getUID()];
// Multiple attachements are not supported by WebGL
gl.framebufferTexture2D(WebGLConst.GL_FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, WebGLConst.GL_TEXTURE_2D, webGLTexture, 0);
}
// Attach depth and/or stencil buffers.
if (useDepthBuffer || useStencilBuffer) {
const renderBuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(WebGLConst.GL_RENDERBUFFER, renderBuffer);
if (!useStencilBuffer) {
gl.renderbufferStorage(WebGLConst.GL_RENDERBUFFER, WebGLConst.GL_DEPTH_COMPONENT16, size[0], size[1]);
gl.framebufferRenderbuffer(WebGLConst.GL_FRAMEBUFFER, WebGLConst.GL_DEPTH_ATTACHMENT, WebGLConst.GL_RENDERBUFFER, renderBuffer);
} else {
gl.renderbufferStorage(WebGLConst.GL_RENDERBUFFER, WebGLConst.GL_DEPTH_STENCIL, size[0], size[1]);
gl.framebufferRenderbuffer(WebGLConst.GL_FRAMEBUFFER, WebGLConst.GL_DEPTH_STENCIL_ATTACHMENT, WebGLConst.GL_RENDERBUFFER, renderBuffer);
}
gl.bindRenderbuffer(WebGLConst.GL_RENDERBUFFER, null);
}
// Unbind FBO safely
this.bindFrameBuffer(-1);
}
/**
* Set default values on the state block instance
*
* @private
*/
initStateBlockWithDefaultValues() {
this.state.depthTest = false;
this.state.depthWrite = false;
this.state.stencilTest = false;
}
/**
* Send lights to the given program
*
* @param {Program} program A Program instance
* @private
*/
sendLights(program) {
const webGLProgram = this.instances.programs[program.getUID()];
if (!webGLProgram) {
return;
}
const lightCount = this.cache.lights.length;
// Fill arrays.
let needData = false;
let needDirection = false;
for (let i = 0, j = 0; i < this.cache.lights.length; i += 1, j += 3) {
const light = this.cache.lights[i];
// Type of light
if (light instanceof PointLight) {
this.cache.lightsType[i] = 0;
} else if (light instanceof DirectionalLight) {
this.cache.lightsType[i] = 1;
} else {
this.cache.lightsType[i] = 2;
}
// Ambient
const ambient = light.getAmbientColor();
this.cache.lightsAmbient[j] = ambient.r;
this.cache.lightsAmbient[j + 1] = ambient.g;
this.cache.lightsAmbient[j + 2] = ambient.b;
// Data (linear, quadratic and constant data)
if (this.cache.lightsType[i] !== 1) {
const values = light.getValues();
this.cache.lightsData[j] = values[0];
this.cache.lightsData[j + 1] = values[1];
this.cache.lightsData[j + 2] = values[2];
needData = true;
} else {
this.cache.lightsData[j] = 0;
this.cache.lightsData[j + 1] = 0;
this.cache.lightsData[j + 2] = 0;
}
// Diffuse.
const diffuse = light.getDiffuseColor();
this.cache.lightsDiffuse[j] = diffuse.r;
this.cache.lightsDiffuse[j + 1] = diffuse.g;
this.cache.lightsDiffuse[j + 2] = diffuse.b;
// Direction.
if (this.cache.lightsType[i] !== 0) {
const direction = light.getDirection();
this.cache.lightsDirection[j] = direction[0];
this.cache.lightsDirection[j + 1] = direction[1];
this.cache.lightsDirection[j + 2] = direction[2];
needDirection = true;
} else {
this.cache.lightsDirection[j] = 0;
this.cache.lightsDirection[j + 1] = 0;
this.cache.lightsDirection[j + 2] = 0;
}
const position = light.getPosition();
this.cache.lightsPosition[j] = position[0];
this.cache.lightsPosition[j + 1] = position[1];
this.cache.lightsPosition[j + 2] = position[2];
// Specular.
const specular = light.getSpecularColor();
this.cache.lightsSpecular[j] = specular.r;
this.cache.lightsSpecular[j + 1] = specular.g;
this.cache.lightsSpecular[j + 2] = specular.b;
}
// Send data.
if (this.cache.lights.length) {
this.setUniform(program, 'uCameraPosition', Type.Float, this.activeCamera.getPosition());
this.setUniform(program, 'lights.count', Type.Int, lightCount);
this.setUniform(program, 'lights.ambient', Type.Float, this.cache.lightsAmbient, 3);
if (needData) {
this.setUniform(program, 'lights.data', Type.Float, this.cache.lightsData, 3);
}
this.setUniform(program, 'lights.diffuse', Type.Float, this.cache.lightsDiffuse, 3);
if (needDirection) {
this.setUniform(program, 'lights.direction', Type.Float, this.cache.lightsDirection, 3);
}
this.setUniform(program, 'lights.position', Type.Float, this.cache.lightsPosition, 3);
this.setUniform(program, 'lights.specular', Type.Float, this.cache.lightsSpecular, 3);
this.setUniform(program, 'lights.type', Type.Int, this.cache.lightsType, 1);
}
}
/**
* Set camera to use
*
* @param {Camera} camera A Camera instance
*/
setActiveCamera(camera) {
super.setActiveCamera(camera);
const viewport = camera.getViewport();
Context.getActive().viewport(viewport[0], viewport[1], viewport[2], viewport[3]);
}
/**
* Set blend mode to apply
*
* @param {BlendMode} blendMode A BlendMode instance
*/
setBlendMode(blendMode) {
// Avoid useless operations
if (blendMode.isEqual(this.state.blendMode)) {
return;
}
// Retrieve context
const gl = Context.getActive();
// Disable blending.
if (blendMode.colorSourceFactor === BlendMode.Factor.One && blendMode.colorDestinationFactor === BlendMode.Factor.Zero) {
gl.disable(WebGLConst.GL_BLEND);
} else {
// Enable it
gl.enable(WebGLConst.GL_BLEND);
// Apply functions and equations
/* eslint max-len: ["error", { "code": 400 }] */
gl.blendEquationSeparate(TypesConverter.blendingEquationToConstant.get(blendMode.colorEquation), TypesConverter.blendingEquationToConstant.get(blendMode.alphaEquation));
gl.blendFuncSeparate(TypesConverter.blendingFactorToConstant.get(blendMode.colorSourceFactor),
TypesConverter.blendingFactorToConstant.get(blendMode.colorDestinationFactor),
TypesConverter.blendingFactorToConstant.get(blendMode.alphaSourceFactor),
TypesConverter.blendingFactorToConstant.get(blendMode.alphaDestinationFactor));
}
this.state.blendMode = blendMode;
}
/**
* Set depth state
*
* @param {boolean} depthTest True to activate depth testing, otherwise false
* @param {boolean} writeTest True to activate depth writing otherwise false
* @param {DepthFunction} depthFunction Depth function to apply
*/
setDepthState(depthTest, writeTest, depthFunction) {
const gl = Context.getActive();
if (!depthTest && this.state.depthTest) {
gl.disable(WebGLConst.GL_DEPTH_TEST);
} else if (depthTest) {
if (!this.state.depthTest) {
gl.enable(WebGLConst.GL_DEPTH_TEST);
}
if (this.state.writeTest !== writeTest) {
gl.depthMask(writeTest);
this.state.writeTest = writeTest;
}
if (this.state.depthFunction !== depthFunction) {
gl.depthFunc(TypesConverter.depthFunctionToConstant.get(depthFunction));
this.state.depthFunction = depthFunction;
}
}
this.state.depthTest = depthTest;
}
/**
* Set face culling state
*
* @param {FaceCulling} mode Face culling mode to set
*/
setFaceCulling(mode) {
// Avoid useless operations
if (this.state.faceCulling === mode) {
return;
}
// Retrieve context
const gl = Context.getActive();
// Apply state
if (mode === FaceCulling.None) {
gl.disable(WebGLConst.GL_CULL_FACE);
} else {
if (this.state.faceCulling === FaceCulling.None) {
gl.enable(WebGLConst.GL_CULL_FACE);
}
if (mode === FaceCulling.Front) {
gl.cullFace(WebGLConst.GL_FRONT);
} else {
gl.cullFace(WebGLConst.GL_BACK);
}
}
this.state.faceCulling = mode;
}
/**
* Set index buffer to use
*
* @param {number|WebGLBuffer} buffer A buffer instance
*/
setIndexBuffer(buffer) {
Context.getActive().bindBuffer(WebGLConst.GL_ELEMENT_ARRAY_BUFFER, buffer);
}
/**
* Set geometry to use
*
* @param {Geometry} geometry A Geometry instance
*/
setGeometry(geometry) {
// Ensure valid format is present
if (!geometry.getVertexFormat()) {
return;
}
// Set vertex format to use
this.setVertexFormat(geometry.getVertexFormat());
// Retrieve context
const gl = Context.getActive();
// Create geometry's data
let geometryInstances = this.instances.buffers[geometry.getUID()];
if (!geometryInstances) {
geometryInstances = new BufferData();
this.instances.buffers[geometry.getUID()] = geometryInstances;
}
// Prepare/Set index buffer
if (!geometryInstances.indexBuffer) {
geometryInstances.indexBuffer = gl.createBuffer();
}
this.setIndexBuffer(geometryInstances.indexBuffer);
// Update buffer data
if (this.cache.vertexFormat.isIndicesWaitingUpdate()) {
gl.bufferData(WebGLConst.GL_ELEMENT_ARRAY_BUFFER, geometry.getIndices(), WebGLConst.GL_STATIC_DRAW);
this.cache.vertexFormat.setIndicesAsWaitingUpdate(false);
}
// Prepare/Set vertex buffer
const vertexElements = this.cache.vertexFormat.getElements();
for (let i = 0; i < vertexElements.length; i += 1) {
if (!geometryInstances.vertexBuffers[i]) {
geometryInstances.vertexBuffers[i] = gl.createBuffer();
}
// Apply buffer
this.setVertexBuffer(i, geometryInstances.vertexBuffers[i]);
// Fill it
if (this.cache.vertexFormat.isStreamWaitingUpdate(vertexElements[i].stream)) {
const streamType = TypesConverter.streamTypeToConstant.get(this.cache.vertexFormat.getStreamType(vertexElements[i].stream));
switch (vertexElements[i].usage) {
case VertexElement.Usage.Position:
gl.bufferData(WebGLConst.GL_ARRAY_BUFFER, geometry.getVerticesPositions(), streamType);
break;
case VertexElement.Usage.Color:
gl.bufferData(WebGLConst.GL_ARRAY_BUFFER, geometry.getVerticesColors(), streamType);
break;
case VertexElement.Usage.UVS:
gl.bufferData(WebGLConst.GL_ARRAY_BUFFER, geometry.getVerticesUVs(), streamType);
break;
case VertexElement.Usage.Normal:
gl.bufferData(WebGLConst.GL_ARRAY_BUFFER, geometry.getVerticesNormals(), streamType);
break;
default:
case VertexElement.Usage.Tangent:
console.log('Given vertex element is not supported for now.');
break;
}
this.cache.vertexFormat.setStreamAsWaitingUpdate(vertexElements[i].usage, false);
}
}
}
/**
* Set program to use
*
* @param {Program} program A Program instance to use
* @return {number} -1: an error occured, 0: everything is ok, 1 : program have been changed
*/
setProgram(program) {
let webGLProgram = this.instances.programs[program.getUID()];
// Retrieve context.
const gl = Context.getActive();
// Create program.
if (!webGLProgram) {
if (!program.isReady()) {
return -1;
}
const sources = program.getSources();
const programID = gl.createProgram();
// Load vertex and fragment shaders
const vertexShader = gl.createShader(WebGLConst.GL_VERTEX_SHADER);
gl.shaderSource(vertexShader, sources[0]);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(WebGLConst.GL_FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, sources[1]);
gl.compileShader(fragmentShader);
// Link to the program
gl.attachShader(programID, vertexShader);
gl.attachShader(programID, fragmentShader);
// Bind default locations
gl.bindAttribLocation(programID, VertexElement.Usage.Position, 'aPosition');
gl.bindAttribLocation(programID, VertexElement.Usage.UVS, 'aTexCoord');
gl.bindAttribLocation(programID, VertexElement.Usage.Color, 'aColor');
gl.bindAttribLocation(programID, VertexElement.Usage.Normal, 'aNormal');
gl.bindAttribLocation(programID, VertexElement.Usage.Tangent, 'aTangent');
// Link program
gl.linkProgram(programID);
// Remove vertex and fragment from memory
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
// Save it
this.instances.programs[program.getUID()] = programID;
webGLProgram = programID;
// Get uniforms and attributs informations
let i;
const activeUniforms = gl.getProgramParameter(programID, WebGLConst.GL_ACTIVE_UNIFORMS);
const uniforms = program.getUniforms();
for (i = 0; i < activeUniforms; i += 1) {
const uniform = gl.getActiveUniform(programID, i);
let finalName = uniform.name;
const arrayPos = uniform.name.indexOf('[', uniform.name.length - 3);
if (arrayPos >= 0) {
finalName = uniform.name.substring(0, arrayPos);
}
uniforms[finalName] = new ProgramElement(gl.getUniformLocation(programID, uniform.name),
finalName,
TypesConverter.toShaderTypes(uniform.type),
uniform.size);
}
const activeAttributes = gl.getProgramParameter(programID, WebGLConst.GL_ACTIVE_ATTRIBUTES);
const attributes = program.getAttributes();
for (i = 0; i < activeAttributes; i += 1) {
const attribute = gl.getActiveAttrib(programID, i);
attributes[attribute.name] = new ProgramElement(gl.getAttribLocation(programID, attribute.name),
attribute.name,
TypesConverter.toShaderTypes(attribute.type),
attribute.size);
}
}
// Bind program.
if (this.cache.program !== webGLProgram) {
// Use Program
gl.useProgram(webGLProgram);
this.cache.program = webGLProgram;
// Send lights's informations
this.sendLights(program);
return 1;
}
return 0;
}
/**
* Set uniform value
*
* @param {Program} program A Program instance to use
* @param {string} name Uniform's name
* @param {Type} type Type of value to send
* @param {?Array<number>|Texture|boolean|number|Float32Array} value A value
* @param {number=} groupCount When an element is an array, you can create group (like sub-array)
* @return {boolean} True if uniform has been set successfully, otherwise false
*/
setUniform(program, name, type, value, groupCount = 0) {
// Check if program need to be set
this.setProgram(program);
const uniform = program.getUniform(name);
if (!uniform || !value) {
return false;
}
// Retrieve context
const gl = Context.getActive();
// Send value to the program/shaders
switch (type) {
case Type.Float:
{
if (value instanceof Array) {
if (groupCount && groupCount >= 1) {
if (groupCount === 2) {
gl.uniform2fv(uniform.location, value);
} else if (groupCount === 3) {
gl.uniform3fv(uniform.location, value);
} else if (groupCount === 4) {
gl.uniform4fv(uniform.location, value);
} else if (groupCount === 1) {
gl.uniform1fv(uniform.location, value);
}
} else if (value.length === 3) {
gl.uniform3f(uniform.location, value[0], value[1], value[2]);
} else if (value.length === 4) {
gl.uniform4f(uniform.location, value[0], value[1], value[2], value[3]);
} else if (value.length === 3) {
gl.uniform2f(uniform.location, value[0], value[1]);
}
} else {
gl.uniform1f(uniform.location, value);
}
break;
}
case Type.Int:
{
if (value instanceof Array) {
if (groupCount && groupCount >= 1) {
if (groupCount === 2) {
gl.uniform2iv(uniform.location, value);
} else if (groupCount === 3) {
gl.uniform3iv(uniform.location, value);
} else if (groupCount === 4) {
gl.uniform4iv(uniform.location, value);
} else if (groupCount === 1) {
gl.uniform1iv(uniform.location, value);
}
} else if (value.length === 3) {
gl.uniform3i(uniform.location, value[0], value[1], value[2]);
} else if (value.length === 4) {
gl.uniform4i(uniform.location, value[0], value[1], value[2], value[3]);
} else if (value.length === 2) {
gl.uniform2i(uniform.location, value[0], value[1]);
}
} else {
gl.uniform1i(uniform.location, value);
}
break;
}
case Type.Matrix:
{
if (value.length === 16) {
gl.uniformMatrix4fv(uniform.location, false, value);
} else if (value.length === 4) {
gl.uniformMatrix2fv(uniform.location, false, value);
} else if (value.length === 9) {
gl.uniformMatrix3fv(uniform.location, false, value);
}
break;
}
default:
break;
}
return true;
}
/**
* Set scissor test state
*
* @param {boolean} state True to activate scissor testing, otherwise false
* @param {number} x Position on x from the left of the screen
* @param {number} y Position on y from the bottom of the screen
* @param {number} w Width of the rectangle
* @param {number} h Height of the rectangle
*/
setScissorTest(state, x, y, w, h) {
// Retrieve context
const gl = Context.getActive();
if (!state) {
gl.disable(WebGLConst.GL_SCISSOR_TEST);
} else {
gl.enable(WebGLConst.GL_SCISSOR_TEST);
gl.scissor(x, y, w, h);
}
}
/**
* Set stencil test state
*
* @param {boolean} activate True to activate stencil test, otherwise false
* @param {number} writeMask Stencil writing value
*/
setStencilState(activate, writeMask) {
// Retrieve context
const gl = Context.getActive();
if (!activate && this.state.stencilTest) {
gl.disable(WebGLConst.GL_STENCIL_TEST);
} else if (activate) {
if (!this.state.stencilTest) {
gl.enable(WebGLConst.GL_STENCIL_TEST);
}
if (this.state.stencilWrite !== writeMask) {
gl.stencilMask(writeMask);
this.state.stencilWrite = writeMask;
}
}
this.state.stencilTest = activate;
}
/**
* Set stencil function to use
*
* @param {StencilFunction} stencilFunction Function to use
* @param {number} reference Reference value
* @param {number} mask Mask to use
*/
setStencilFunction(stencilFunction, reference, mask) {
if (this.state.stencilFunction !== stencilFunction || this.state.stencilReference !== reference || this.state.stencilMask !== mask) {
Context.getActive().stencilFunc(TypesConverter.stencilFunctionToConstant.get(stencilFunction), reference, mask);
this.state.stencilFunction = stencilFunction;
this.state.stencilReference = reference;
this.state.stencilMask = mask;
}
}
/**
* Set stencil operations to use
*
* @param {StencilOperation} sFail Function to use
* @param {StencilOperation} dpFail Reference value
* @param {StencilOperation} dppPass Mask to use
*/
setStencilOperations(sFail, dpFail, dppPass) {
if (this.state.stencilTestFail !== sFail || this.state.stencilDepthTestFail !== dpFail || this.state.stencilSuccess !== dppPass) {
Context.getActive().stencilOp(TypesConverter.stencilOperationToConstant.get(sFail),
TypesConverter.stencilOperationToConstant.get(dpFail),
TypesConverter.stencilOperationToConstant.get(dppPass));
this.state.stencilTestFail = sFail;
this.state.stencilDepthTestFail = dpFail;
this.state.stencilSuccess = dppPass;
}
}
/**
* Set vertex buffer to use
*
* Warning: You must call "setVertexFormat" before!
* @param {number} stream An integer representing stream to use
* @param {number|WebGLBuffer} buffer A buffer instance
*/
setVertexBuffer(stream, buffer) {
// Retrieve context
const gl = Context.getActive();
// Bind buffer
gl.bindBuffer(WebGLConst.GL_ARRAY_BUFFER, buffer);
// Enable vertex data
const vertexElements = this.cache.vertexFormat.getElements();
for (let i = 0; i < vertexElements.length; i += 1) {
if (vertexElements[i].stream === stream) {
// Enable
gl.enableVertexAttribArray(vertexElements[i].usage);
gl.vertexAttribPointer(vertexElements[i].usage,
vertexElements[i].count,
TypesConverter.vertexTypeToConstant.get(vertexElements[i].type),
vertexElements[i].normalize,
this.cache.vertexFormat.getStreamStride(vertexElements[i].stream),
vertexElements[i].offset);
// Save attribut's state
this.enabledVertexAttribArray[vertexElements[i].usage] = true;
}
}
}
/**
* Set vertex format to use
*
* @param {VertexFormat} format A VertexFormat instance
*/
setVertexFormat(format) {
this.cache.vertexFormat = format;
}
}
export default WebGL;
Source