// HILO_DEBUG_START
import WebGLDebugUtils from 'webgl-debug';
// HILO_DEBUG_END
import Class from '../core/Class';
import Node from '../core/Node';
import semantic from '../material/semantic';
import Color from '../math/Color';
import Shader from '../shader/Shader';
import Program from './Program';
import RenderInfo from './RenderInfo';
import RenderList from './RenderList';
import VertexArrayObject from './VertexArrayObject';
import Buffer from './Buffer';
import Framebuffer from './Framebuffer';
import extensions from './extensions';
import capabilities from './capabilities';
import glType from './glType';
import WebGLState from './WebGLState';
import WebGLResourceManager from './WebGLResourceManager';
import LightManager from '../light/LightManager';
import EventMixin from '../core/EventMixin';
import Texture from '../texture/Texture';
import constants from '../constants';
const {
DEPTH_TEST,
STENCIL_TEST,
SAMPLE_ALPHA_TO_COVERAGE,
CULL_FACE,
FRONT_AND_BACK,
BLEND,
LINES,
STATIC_DRAW,
DYNAMIC_DRAW
} = constants;
/**
* WebGL渲染器
* @class
* @fires init 初始化事件
* @fires beforeRender 渲染前事件
* @fires beforeRenderScene 渲染场景前事件
* @fires afterRender 渲染后事件
* @fires initFailed 初始化失败事件
* @fires webglContextLost webglContextLost 事件
* @fires webglContextRestored webglContextRestored 事件
* @mixes EventMixin
*/
const WebGLRenderer = Class.create(/** @lends WebGLRenderer.prototype */ {
Mixes: EventMixin,
/**
* @default WebGLRenderer
* @type {String}
*/
className: 'WebGLRenderer',
/**
* @default true
* @type {Boolean}
*/
isWebGLRenderer: true,
/**
* gl
* @default null
* @type {WebGLRenderingContext}
*/
gl: null,
/**
* 宽
* @type {Number}
* @default 0
*/
width: 0,
/**
* 高
* @type {Number}
* @default 0
*/
height: 0,
/**
* 像素密度
* @type {Number}
* @default 1
*/
pixelRatio: 1,
/**
* dom元素
* @type {HTMLCanvasElement}
* @default null
*/
domElement: null,
/**
* 是否使用instanced
* @type {Boolean}
* @default false
*/
useInstanced: false,
/**
* 是否使用VAO
* @type {Boolean}
* @default true
*/
useVao: true,
/**
* 是否开启透明背景
* @type {Boolean}
* @default false
*/
alpha: false,
/**
* @type {Boolean}
* @default true
*/
depth: true,
/**
* @type {Boolean}
* @default false
*/
stencil: false,
/**
* 是否开启抗锯齿
* @type {Boolean}
* @default true
*/
antialias: true,
/**
* Boolean that indicates that the page compositor will assume the drawing buffer contains colors with pre-multiplied alpha.
* @type {Boolean}
* @default true
*/
premultipliedAlpha: true,
/**
* If the value is true the buffers will not be cleared and will preserve their values until cleared or overwritten by the author.
* @type {Boolean}
* @default false
*/
preserveDrawingBuffer: false,
/**
* Boolean that indicates if a context will be created if the system performance is low.
* @type {Boolean}
* @default false
*/
failIfMajorPerformanceCaveat: false,
/**
* 游戏模式, UC浏览器专用
* @default false
* @type {Boolean}
*/
gameMode: false,
/**
* 是否使用framebuffer
* @type {Boolean}
* @default false
*/
useFramebuffer: false,
/**
* framebuffer配置
* @type {Object}
* @default {}
*/
framebufferOption: {},
/**
* 是否使用对数深度
* @type {Boolean}
* @default false
*/
useLogDepth: false,
/**
* 顶点着色器精度, 可以是以下值:highp, mediump, lowp
* @type {String}
* @default highp
*/
vertexPrecision: 'highp',
/**
* 片段着色器精度, 可以是以下值:highp, mediump, lowp
* @type {String}
* @default mediump
*/
fragmentPrecision: 'highp',
/**
* 雾
* @type {Fog}
* @default null
*/
fog: null,
/**
* 偏移值
* @type {Number}
* @default 0
*/
offsetX: 0,
/**
* 偏移值
* @type {Number}
* @default 0
*/
offsetY: 0,
/**
* 强制渲染时使用的材质
* @type {Material}
* @default null
*/
forceMaterial: null,
/**
* 是否初始化失败
* @default false
* @type {Boolean}
*/
isInitFailed: false,
/**
* 是否初始化
* @type {Boolean}
* @default false
* @private
*/
_isInit: false,
/**
* 是否lost context
* @type {Boolean}
* @default false
* @private
*/
_isContextLost: false,
/**
* 是否是 WebGL2
* @type {Boolean}
* @default false
*/
isWebGL2: false,
/**
* 是否优先使用 WebGL2
* @type {Boolean}
* @default false
*/
preferWebGL2: false,
/**
* @constructs
* @param {Object} [params] 初始化参数,所有params都会复制到实例上
*/
constructor(params) {
/**
* 背景色
* @type {Color}
* @default new Color(1, 1, 1, 1)
*/
this.clearColor = new Color(1, 1, 1);
Object.assign(this, params);
/**
* 渲染信息
* @type {RenderInfo}
* @default new RenderInfo
*/
this.renderInfo = new RenderInfo();
/**
* 渲染列表
* @type {RenderList}
* @default new RenderList
*/
this.renderList = new RenderList();
/**
* 灯光管理器
* @type {ILightManager}
* @default new LightManager
*/
this.lightManager = new LightManager();
/**
* 资源管理器
* @type {WebGLResourceManager}
* @default new WebGLResourceManager
*/
this.resourceManager = new WebGLResourceManager();
},
/**
* 改变大小
* @param {Number} width 宽
* @param {Number} height 高
* @param {Boolean} [force=false] 是否强制刷新
*/
resize(width, height, force) {
if (force || this.width !== width || this.height !== height) {
const canvas = this.domElement;
this.width = width;
this.height = height;
canvas.width = width;
canvas.height = height;
if (this.framebuffer) {
this.framebuffer.resize(this.width, this.height, force);
}
this.viewport();
}
},
/**
* 设置viewport偏移值
* @param {Number} x x
* @param {Number} y y
*/
setOffset(x, y) {
if (this.offsetX !== x || this.offsetY !== y) {
this.offsetX = x;
this.offsetY = y;
this.viewport();
}
},
/**
* 设置viewport
* @param {Number} [x=this.offsetX] x
* @param {Number} [y=this.offsetY] y
* @param {Number} [width=this.gl.drawingBufferWidth] width
* @param {Number} [height=this.gl.drawingBufferHeight] height
*/
viewport(x, y, width, height) {
const {
state,
gl
} = this;
if (state) {
if (x === undefined) {
x = this.offsetX;
} else {
this.offsetX = x;
}
if (y === undefined) {
y = this.offsetY;
} else {
this.offsetY = y;
}
if (width === undefined) {
width = gl.drawingBufferWidth;
}
if (height === undefined) {
height = gl.drawingBufferHeight;
}
state.viewport(x, y, width, height);
}
},
/**
* 是否初始化
* @type {Boolean}
* @default false
* @readOnly
*/
isInit: {
get() {
return this._isInit && !this.isInitFailed;
}
},
/**
* 初始化回调
* @return {WebGLRenderer} this
*/
onInit(callback) {
if (this._isInit) {
callback(this);
} else {
this.on('init', () => {
callback(this);
}, true);
}
},
/**
* 初始化 context
*/
initContext() {
if (!this._isInit) {
this._isInit = true;
try {
this._initContext();
this.fire('init');
} catch (e) {
this.isInitFailed = true;
this.fire('initFailed', e);
}
}
},
_initContext() {
const contextAttributes = {
alpha: this.alpha,
depth: this.depth,
stencil: this.stencil,
antialias: this.antialias,
premultipliedAlpha: this.premultipliedAlpha,
failIfMajorPerformanceCaveat: this.failIfMajorPerformanceCaveat
};
// fix ios bug...
if (this.preserveDrawingBuffer === true) {
contextAttributes.preserveDrawingBuffer = true;
}
if (this.gameMode === true) {
contextAttributes.gameMode = true;
}
if (this.preferWebGL2) {
try {
this.gl = this.domElement.getContext('webgl2', contextAttributes);
this.isWebGL2 = true;
} catch (e) {
this.isWebGL2 = false;
this.gl = null;
}
}
if (!this.gl) {
this.gl = this.domElement.getContext('webgl', contextAttributes);
this.isWebGL2 = false;
}
let gl = this.gl;
// HILO_DEBUG_START
gl = this.gl = WebGLDebugUtils.makeDebugContext(gl, (err, funcName) => {
throw new Error(`${WebGLDebugUtils.GLenumToString(err)} called by ${funcName}`);
});
// HILO_DEBUG_END
gl.viewport(0, 0, this.width, this.height);
glType.init(gl);
extensions.init(gl);
capabilities.init(gl);
Shader.init(this);
/**
* state,初始化后生成。
* @type {WebGLState}
* @default null
*/
this.state = new WebGLState(gl);
if (!extensions.instanced) {
this.useInstanced = false;
}
this.renderList.useInstanced = this.useInstanced;
if (this.useFramebuffer) {
/**
* framebuffer,只在 useFramebuffer 为 true 时初始化后生成
* @type {Framebuffer}
* @default null
*/
this.framebuffer = new Framebuffer(this, Object.assign({
useVao: this.useVao,
width: this.width,
height: this.height
}, this.framebufferOption));
}
this.domElement.addEventListener('webglcontextlost', (e) => {
this._onContextLost(e);
}, false);
this.domElement.addEventListener('webglcontextrestored', (e) => {
this._onContextRestore(e);
}, false);
},
_onContextLost(e) {
const gl = this.gl;
this._isContextLost = true;
e.preventDefault();
Program.reset(gl);
Shader.reset(gl);
Texture.reset(gl);
Buffer.reset(gl);
VertexArrayObject.reset(gl);
this.state.reset(gl);
this._lastMaterial = null;
this._lastProgram = null;
this.fire('webglContextLost');
},
_onContextRestore(e) { // eslint-disable-line no-unused-vars
const gl = this.gl;
this._isContextLost = false;
extensions.reset(gl);
Framebuffer.reset(gl);
this.fire('webglContextRestored');
},
/**
* 设置深度检测
* @param {Material} material
*/
setupDepthTest(material) {
const state = this.state;
if (material.depthTest) {
state.enable(DEPTH_TEST);
state.depthFunc(material.depthFunc);
state.depthMask(material.depthMask);
state.depthRange(material.depthRange[0], material.depthRange[1]);
} else {
state.disable(DEPTH_TEST);
}
},
/**
* 设置alphaToCoverage
* @param {Material} material
*/
setupSampleAlphaToCoverage(material) {
const state = this.state;
if (material.sampleAlphaToCoverage) {
state.enable(SAMPLE_ALPHA_TO_COVERAGE);
} else {
state.disable(SAMPLE_ALPHA_TO_COVERAGE);
}
},
/**
* 设置背面剔除
* @param {Material} material
*/
setupCullFace(material) {
const state = this.state;
state.frontFace(material.frontFace);
if (material.cullFace && material.cullFaceType !== FRONT_AND_BACK) {
state.enable(CULL_FACE);
state.cullFace(material.cullFaceType);
} else {
state.disable(CULL_FACE);
}
},
/**
* 设置混合
* @param {Material} material
*/
setupBlend(material) {
const state = this.state;
if (material.blend) {
state.enable(BLEND);
state.blendFuncSeparate(
material.blendSrc,
material.blendDst,
material.blendSrcAlpha,
material.blendDstAlpha
);
state.blendEquationSeparate(
material.blendEquation,
material.blendEquationAlpha
);
} else {
state.disable(BLEND);
}
},
/**
* 设置模板
* @param {Material} material
*/
setupStencil(material) {
if (!this.stencil) {
return;
}
const state = this.state;
if (material.stencilTest) {
state.enable(STENCIL_TEST);
state.stencilMask(material.stencilMask);
state.stencilFunc(material.stencilFunc, material.stencilFuncRef, material.stencilFuncMask);
state.stencilOp(material.stencilOpFail, material.stencilOpZFail, material.stencilOpZPass);
} else {
state.disable(STENCIL_TEST);
}
},
/**
* 设置通用的 uniform
* @param {Program} program
* @param {Mesh} mesh
* @param {Boolean} [force=false] 是否强制更新
*/
setupUniforms(program, mesh, useInstanced, force) {
const material = this.forceMaterial || mesh.material;
if (this.isWebGL2) {
const uniformBlocks = material.uniformBlocks;
for (let name in program.uniformBlocks) {
const uniformBlock = uniformBlocks[name];
if (uniformBlock) {
program[name] = uniformBlock;
}
}
}
for (let name in program.uniforms) {
const uniformInfo = material.getUniformInfo(name);
const programUniformInfo = program.uniforms[name];
if (!uniformInfo.isBlankInfo) {
if (force || (uniformInfo.isDependMesh && !useInstanced)) {
const uniformData = uniformInfo.get(mesh, material, programUniformInfo);
if (uniformData !== undefined && uniformData !== null) {
program[name] = uniformData;
}
}
}
}
},
/**
* 设置vao
* @param {VertexArrayObject} vao
* @param {Program} program
* @param {Mesh} mesh
*/
setupVao(vao, program, mesh) {
const geometry = mesh.geometry;
const isStatic = geometry.isStatic;
if (vao.isDirty || !isStatic || geometry.isDirty) {
vao.isDirty = false;
const material = this.forceMaterial || mesh.material;
const materialAttributes = material.attributes;
const usage = isStatic ? STATIC_DRAW : DYNAMIC_DRAW;
for (let name in materialAttributes) {
const programAttribute = program.attributes[name];
if (programAttribute) {
const data = material.getAttributeData(name, mesh, programAttribute);
if (data !== undefined && data !== null) {
vao.addAttribute(data, programAttribute, usage);
}
}
}
if (geometry.indices) {
vao.addIndexBuffer(geometry.indices, usage);
}
geometry.isDirty = false;
}
if (geometry.vertexCount) {
vao.vertexCount = geometry.vertexCount;
}
},
/**
* 设置材质
* @param {Program} program
* @param {Mesh} mesh
*/
setupMaterial(program, mesh, useInstanced, needForceUpdateUniforms = false) {
const material = this.forceMaterial || mesh.material;
if (material.isDirty || this._lastMaterial !== material) {
this.setupDepthTest(material);
this.setupSampleAlphaToCoverage(material);
this.setupCullFace(material);
this.setupBlend(material);
this.setupStencil(material);
needForceUpdateUniforms = true;
}
this.setupUniforms(program, mesh, useInstanced, needForceUpdateUniforms);
material.isDirty = false;
this._lastMaterial = material;
},
/**
* 设置mesh
* @param {Mesh} mesh
* @param {Boolean} useInstanced
* @return {Object} res
* @return {VertexArrayObject} res.vao
* @return {Program} res.program
* @return {Geometry} res.geometry
*/
setupMesh(mesh, useInstanced) {
const gl = this.gl;
const state = this.state;
const lightManager = this.lightManager;
const resourceManager = this.resourceManager;
const geometry = mesh.geometry;
const material = this.forceMaterial || mesh.material;
const shader = Shader.getShader(mesh, material, useInstanced, lightManager, this.fog, this.useLogDepth);
const program = Program.getProgram(shader, state);
program.useProgram();
this.setupMaterial(program, mesh, useInstanced, this._lastProgram !== program);
this._lastProgram = program;
if (mesh.material.wireframe && geometry.mode !== LINES) {
geometry.convertToLinesMode();
}
const vaoId = geometry.id + program.id;
const vao = VertexArrayObject.getVao(gl, vaoId, {
useInstanced,
useVao: this.useVao,
mode: geometry.mode
});
this.setupVao(vao, program, mesh);
resourceManager.addMeshResources(mesh, [vao, shader, program]);
return {
vao,
program,
geometry
};
},
/**
* 增加渲染信息
* @param {Number} faceCount 面数量
* @param {Number} drawCount 绘图数量
*/
addRenderInfo(faceCount, drawCount) {
const renderInfo = this.renderInfo;
renderInfo.addFaceCount(faceCount);
renderInfo.addDrawCount(drawCount);
},
/**
* 渲染
* @param {Stage|Node} stage
* @param {Camera} camera
* @param {Boolean} [fireEvent=false] 是否发送事件
*/
render(stage, camera, fireEvent = false) {
this.initContext();
if (this.isInitFailed || this._isContextLost) {
return;
}
const {
renderList,
renderInfo,
lightManager,
resourceManager,
state
} = this;
this.fog = stage.fog;
lightManager.reset();
renderInfo.reset();
renderList.reset();
semantic.init(this, state, camera, lightManager, this.fog);
stage.updateMatrixWorld();
camera.updateViewProjectionMatrix();
const lights = [];
stage.traverse((node) => {
if (!node.visible) {
return Node.TRAVERSE_STOP_CHILDREN;
}
if (node.isMesh) {
renderList.addMesh(node, camera);
} else if (node.isLight) {
lights.push(node);
}
return Node.TRAVERSE_STOP_NONE;
});
renderList.sort();
lightManager.update(this, camera, lights);
if (fireEvent) {
this.fire('beforeRender');
}
if (this.useFramebuffer) {
this.framebuffer.bind();
}
this.clear();
if (fireEvent) {
this.fire('beforeRenderScene');
}
this.renderScene();
if (this.useFramebuffer) {
this.renderToScreen(this.framebuffer);
}
if (fireEvent) {
this.fire('afterRender');
}
resourceManager.destroyUnsuedResource(stage);
},
/**
* 渲染场景
*/
renderScene() {
const renderList = this.renderList;
renderList.traverse((mesh) => {
this.renderMesh(mesh);
}, (instancedMeshes) => {
this.renderInstancedMeshes(instancedMeshes);
});
this._gameModeSumbit();
},
_gameModeSumbit() {
const gl = this.gl;
if (this.gameMode && gl && gl.submit) {
gl.submit();
}
},
/**
* 清除背景
* @param {Color} [clearColor=this.clearColor]
*/
clear(clearColor) {
const {
gl,
state
} = this;
clearColor = clearColor || this.clearColor;
this._lastMaterial = null;
this._lastProgram = null;
gl.clearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
state.depthMask(true);
let clearMask = gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT;
if (this.stencil) {
state.stencilMask(true);
clearMask |= gl.STENCIL_BUFFER_BIT;
}
gl.clear(clearMask);
},
/**
* 清除深度
*/
clearDepth() {
const {
gl,
state
} = this;
state.depthMask(true);
gl.clear(gl.DEPTH_BUFFER_BIT);
},
/**
* 清除模板
*/
clearStencil() {
const {
gl,
state
} = this;
state.stencilMask(true);
gl.clear(gl.STENCIL_BUFFER_BIT);
},
/**
* 将framebuffer渲染到屏幕
* @param {Framebuffer} framebuffer
*/
renderToScreen(framebuffer) {
this.state.bindSystemFramebuffer();
framebuffer.render(0, 0, 1, 1, this.clearColor);
},
/**
* 渲染一个mesh
* @param {Mesh} mesh
*/
renderMesh(mesh) {
const vao = this.setupMesh(mesh, false).vao;
vao.draw();
this.addRenderInfo(vao.vertexCount / 3, 1);
},
/**
* 渲染一组 instanced mesh
* @param {Mesh[]} meshes
*/
renderInstancedMeshes(meshes) {
const mesh = meshes[0];
if (!mesh) {
return;
}
const material = this.forceMaterial || mesh.material;
const {
vao,
program
} = this.setupMesh(mesh, true);
const instancedUniforms = material.getInstancedUniforms();
instancedUniforms.forEach((uniformObj) => {
const name = uniformObj.name;
const info = uniformObj.info;
const attribute = program.attributes[name];
if (attribute) {
vao.addInstancedAttribute(attribute, meshes, (mesh) => {
return info.get(mesh);
});
}
});
vao.drawInstance(meshes.length);
this.addRenderInfo(vao.vertexCount / 3 * meshes.length, 1);
},
/**
* 渲染一组普通mesh
* @param {Mesh[]} meshes
*/
renderMultipleMeshes(meshes) {
meshes.forEach((mesh) => {
this.renderMesh(mesh);
});
},
/**
* 销毁 WebGL 资源
*/
releaseGLResource() {
const gl = this.gl;
if (gl) {
Program.reset(gl);
Shader.reset(gl);
Buffer.reset(gl);
VertexArrayObject.reset(gl);
this.state.reset(gl);
Texture.reset(gl);
Framebuffer.destroy(gl);
}
}
});
export default WebGLRenderer;