2022-06-25 00:23:03 +08:00

1396 lines
34 KiB
JavaScript

import State from './state';
import { enums } from './enums';
import Texture2D from './texture-2d';
import TextureCube from './texture-cube';
const GL_INT = 5124;
const GL_FLOAT = 5126;
const GL_FLOAT_VEC2 = 35664;
const GL_FLOAT_VEC3 = 35665;
const GL_FLOAT_VEC4 = 35666;
const GL_INT_VEC2 = 35667;
const GL_INT_VEC3 = 35668;
const GL_INT_VEC4 = 35669;
const GL_BOOL = 35670;
const GL_BOOL_VEC2 = 35671;
const GL_BOOL_VEC3 = 35672;
const GL_BOOL_VEC4 = 35673;
const GL_FLOAT_MAT2 = 35674;
const GL_FLOAT_MAT3 = 35675;
const GL_FLOAT_MAT4 = 35676;
const GL_SAMPLER_2D = 35678;
const GL_SAMPLER_CUBE = 35680;
/**
* _type2uniformCommit
*/
let _type2uniformCommit = {
[GL_INT]: function (gl, id, value) {
gl.uniform1i(id, value);
},
[GL_FLOAT]: function (gl, id, value) {
gl.uniform1f(id, value);
},
[GL_FLOAT_VEC2]: function (gl, id, value) {
gl.uniform2fv(id, value);
},
[GL_FLOAT_VEC3]: function (gl, id, value) {
gl.uniform3fv(id, value);
},
[GL_FLOAT_VEC4]: function (gl, id, value) {
gl.uniform4fv(id, value);
},
[GL_INT_VEC2]: function (gl, id, value) {
gl.uniform2iv(id, value);
},
[GL_INT_VEC3]: function (gl, id, value) {
gl.uniform3iv(id, value);
},
[GL_INT_VEC4]: function (gl, id, value) {
gl.uniform4iv(id, value);
},
[GL_BOOL]: function (gl, id, value) {
gl.uniform1i(id, value);
},
[GL_BOOL_VEC2]: function (gl, id, value) {
gl.uniform2iv(id, value);
},
[GL_BOOL_VEC3]: function (gl, id, value) {
gl.uniform3iv(id, value);
},
[GL_BOOL_VEC4]: function (gl, id, value) {
gl.uniform4iv(id, value);
},
[GL_FLOAT_MAT2]: function (gl, id, value) {
gl.uniformMatrix2fv(id, false, value);
},
[GL_FLOAT_MAT3]: function (gl, id, value) {
gl.uniformMatrix3fv(id, false, value);
},
[GL_FLOAT_MAT4]: function (gl, id, value) {
gl.uniformMatrix4fv(id, false, value);
},
[GL_SAMPLER_2D]: function (gl, id, value) {
gl.uniform1i(id, value);
},
[GL_SAMPLER_CUBE]: function (gl, id, value) {
gl.uniform1i(id, value);
},
};
/**
* _type2uniformArrayCommit
*/
let _type2uniformArrayCommit = {
[GL_INT]: function (gl, id, value) {
gl.uniform1iv(id, value);
},
[GL_FLOAT]: function (gl, id, value) {
gl.uniform1fv(id, value);
},
[GL_FLOAT_VEC2]: function (gl, id, value) {
gl.uniform2fv(id, value);
},
[GL_FLOAT_VEC3]: function (gl, id, value) {
gl.uniform3fv(id, value);
},
[GL_FLOAT_VEC4]: function (gl, id, value) {
gl.uniform4fv(id, value);
},
[GL_INT_VEC2]: function (gl, id, value) {
gl.uniform2iv(id, value);
},
[GL_INT_VEC3]: function (gl, id, value) {
gl.uniform3iv(id, value);
},
[GL_INT_VEC4]: function (gl, id, value) {
gl.uniform4iv(id, value);
},
[GL_BOOL]: function (gl, id, value) {
gl.uniform1iv(id, value);
},
[GL_BOOL_VEC2]: function (gl, id, value) {
gl.uniform2iv(id, value);
},
[GL_BOOL_VEC3]: function (gl, id, value) {
gl.uniform3iv(id, value);
},
[GL_BOOL_VEC4]: function (gl, id, value) {
gl.uniform4iv(id, value);
},
[GL_FLOAT_MAT2]: function (gl, id, value) {
gl.uniformMatrix2fv(id, false, value);
},
[GL_FLOAT_MAT3]: function (gl, id, value) {
gl.uniformMatrix3fv(id, false, value);
},
[GL_FLOAT_MAT4]: function (gl, id, value) {
gl.uniformMatrix4fv(id, false, value);
},
[GL_SAMPLER_2D]: function (gl, id, value) {
gl.uniform1iv(id, value);
},
[GL_SAMPLER_CUBE]: function (gl, id, value) {
gl.uniform1iv(id, value);
},
};
/**
* _commitBlendStates
*/
function _commitBlendStates(gl, cur, next) {
// enable/disable blend
if (cur.blend !== next.blend) {
if (!next.blend) {
gl.disable(gl.BLEND);
return;
}
gl.enable(gl.BLEND);
if (
next.blendSrc === enums.BLEND_CONSTANT_COLOR ||
next.blendSrc === enums.BLEND_ONE_MINUS_CONSTANT_COLOR ||
next.blendDst === enums.BLEND_CONSTANT_COLOR ||
next.blendDst === enums.BLEND_ONE_MINUS_CONSTANT_COLOR
) {
gl.blendColor(
(next.blendColor >> 24) / 255,
(next.blendColor >> 16 & 0xff) / 255,
(next.blendColor >> 8 & 0xff) / 255,
(next.blendColor & 0xff) / 255
);
}
if (next.blendSep) {
gl.blendFuncSeparate(next.blendSrc, next.blendDst, next.blendSrcAlpha, next.blendDstAlpha);
gl.blendEquationSeparate(next.blendEq, next.blendAlphaEq);
} else {
gl.blendFunc(next.blendSrc, next.blendDst);
gl.blendEquation(next.blendEq);
}
return;
}
// nothing to update
if (next.blend === false) {
return;
}
// blend-color
if (cur.blendColor !== next.blendColor) {
gl.blendColor(
(next.blendColor >> 24) / 255,
(next.blendColor >> 16 & 0xff) / 255,
(next.blendColor >> 8 & 0xff) / 255,
(next.blendColor & 0xff) / 255
);
}
// separate diff, reset all
if (cur.blendSep !== next.blendSep) {
if (next.blendSep) {
gl.blendFuncSeparate(next.blendSrc, next.blendDst, next.blendSrcAlpha, next.blendDstAlpha);
gl.blendEquationSeparate(next.blendEq, next.blendAlphaEq);
} else {
gl.blendFunc(next.blendSrc, next.blendDst);
gl.blendEquation(next.blendEq);
}
return;
}
if (next.blendSep) {
// blend-func-separate
if (
cur.blendSrc !== next.blendSrc ||
cur.blendDst !== next.blendDst ||
cur.blendSrcAlpha !== next.blendSrcAlpha ||
cur.blendDstAlpha !== next.blendDstAlpha
) {
gl.blendFuncSeparate(next.blendSrc, next.blendDst, next.blendSrcAlpha, next.blendDstAlpha);
}
// blend-equation-separate
if (
cur.blendEq !== next.blendEq ||
cur.blendAlphaEq !== next.blendAlphaEq
) {
gl.blendEquationSeparate(next.blendEq, next.blendAlphaEq);
}
} else {
// blend-func
if (
cur.blendSrc !== next.blendSrc ||
cur.blendDst !== next.blendDst
) {
gl.blendFunc(next.blendSrc, next.blendDst);
}
// blend-equation
if (cur.blendEq !== next.blendEq) {
gl.blendEquation(next.blendEq);
}
}
}
/**
* _commitDepthStates
*/
function _commitDepthStates(gl, cur, next) {
// enable/disable depth-test
if (cur.depthTest !== next.depthTest) {
if (!next.depthTest) {
gl.disable(gl.DEPTH_TEST);
return;
}
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(next.depthFunc);
gl.depthMask(next.depthWrite);
return;
}
// commit depth-write
if (cur.depthWrite !== next.depthWrite) {
gl.depthMask(next.depthWrite);
}
// check if depth-write enabled
if (next.depthTest === false) {
if (next.depthWrite) {
next.depthTest = true;
next.depthFunc = enums.DS_FUNC_ALWAYS;
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(next.depthFunc);
}
return;
}
// depth-func
if (cur.depthFunc !== next.depthFunc) {
gl.depthFunc(next.depthFunc);
}
}
/**
* _commitStencilStates
*/
function _commitStencilStates(gl, cur, next) {
// inherit stencil states
if (next.stencilTest === enums.STENCIL_INHERIT) {
return;
}
if (next.stencilTest !== cur.stencilTest) {
if (next.stencilTest === enums.STENCIL_DISABLE) {
gl.disable(gl.STENCIL_TEST);
return;
}
gl.enable(gl.STENCIL_TEST);
if (next.stencilSep) {
gl.stencilFuncSeparate(gl.FRONT, next.stencilFuncFront, next.stencilRefFront, next.stencilMaskFront);
gl.stencilMaskSeparate(gl.FRONT, next.stencilWriteMaskFront);
gl.stencilOpSeparate(gl.FRONT, next.stencilFailOpFront, next.stencilZFailOpFront, next.stencilZPassOpFront);
gl.stencilFuncSeparate(gl.BACK, next.stencilFuncBack, next.stencilRefBack, next.stencilMaskBack);
gl.stencilMaskSeparate(gl.BACK, next.stencilWriteMaskBack);
gl.stencilOpSeparate(gl.BACK, next.stencilFailOpBack, next.stencilZFailOpBack, next.stencilZPassOpBack);
} else {
gl.stencilFunc(next.stencilFuncFront, next.stencilRefFront, next.stencilMaskFront);
gl.stencilMask(next.stencilWriteMaskFront);
gl.stencilOp(next.stencilFailOpFront, next.stencilZFailOpFront, next.stencilZPassOpFront);
}
return;
}
// fast return
if (next.stencilTest === enums.STENCIL_DISABLE) {
return;
}
if (cur.stencilSep !== next.stencilSep) {
if (next.stencilSep) {
gl.stencilFuncSeparate(gl.FRONT, next.stencilFuncFront, next.stencilRefFront, next.stencilMaskFront);
gl.stencilMaskSeparate(gl.FRONT, next.stencilWriteMaskFront);
gl.stencilOpSeparate(gl.FRONT, next.stencilFailOpFront, next.stencilZFailOpFront, next.stencilZPassOpFront);
gl.stencilFuncSeparate(gl.BACK, next.stencilFuncBack, next.stencilRefBack, next.stencilMaskBack);
gl.stencilMaskSeparate(gl.BACK, next.stencilWriteMaskBack);
gl.stencilOpSeparate(gl.BACK, next.stencilFailOpBack, next.stencilZFailOpBack, next.stencilZPassOpBack);
} else {
gl.stencilFunc(next.stencilFuncFront, next.stencilRefFront, next.stencilMaskFront);
gl.stencilMask(next.stencilWriteMaskFront);
gl.stencilOp(next.stencilFailOpFront, next.stencilZFailOpFront, next.stencilZPassOpFront);
}
return;
}
if (next.stencilSep) {
// front
if (
cur.stencilFuncFront !== next.stencilFuncFront ||
cur.stencilRefFront !== next.stencilRefFront ||
cur.stencilMaskFront !== next.stencilMaskFront
) {
gl.stencilFuncSeparate(gl.FRONT, next.stencilFuncFront, next.stencilRefFront, next.stencilMaskFront);
}
if (cur.stencilWriteMaskFront !== next.stencilWriteMaskFront) {
gl.stencilMaskSeparate(gl.FRONT, next.stencilWriteMaskFront);
}
if (
cur.stencilFailOpFront !== next.stencilFailOpFront ||
cur.stencilZFailOpFront !== next.stencilZFailOpFront ||
cur.stencilZPassOpFront !== next.stencilZPassOpFront
) {
gl.stencilOpSeparate(gl.FRONT, next.stencilFailOpFront, next.stencilZFailOpFront, next.stencilZPassOpFront);
}
// back
if (
cur.stencilFuncBack !== next.stencilFuncBack ||
cur.stencilRefBack !== next.stencilRefBack ||
cur.stencilMaskBack !== next.stencilMaskBack
) {
gl.stencilFuncSeparate(gl.BACK, next.stencilFuncBack, next.stencilRefBack, next.stencilMaskBack);
}
if (cur.stencilWriteMaskBack !== next.stencilWriteMaskBack) {
gl.stencilMaskSeparate(gl.BACK, next.stencilWriteMaskBack);
}
if (
cur.stencilFailOpBack !== next.stencilFailOpBack ||
cur.stencilZFailOpBack !== next.stencilZFailOpBack ||
cur.stencilZPassOpBack !== next.stencilZPassOpBack
) {
gl.stencilOpSeparate(gl.BACK, next.stencilFailOpBack, next.stencilZFailOpBack, next.stencilZPassOpBack);
}
} else {
if (
cur.stencilFuncFront !== next.stencilFuncFront ||
cur.stencilRefFront !== next.stencilRefFront ||
cur.stencilMaskFront !== next.stencilMaskFront
) {
gl.stencilFunc(next.stencilFuncFront, next.stencilRefFront, next.stencilMaskFront);
}
if (cur.stencilWriteMaskFront !== next.stencilWriteMaskFront) {
gl.stencilMask(next.stencilWriteMaskFront);
}
if (
cur.stencilFailOpFront !== next.stencilFailOpFront ||
cur.stencilZFailOpFront !== next.stencilZFailOpFront ||
cur.stencilZPassOpFront !== next.stencilZPassOpFront
) {
gl.stencilOp(next.stencilFailOpFront, next.stencilZFailOpFront, next.stencilZPassOpFront);
}
}
}
/**
* _commitCullMode
*/
function _commitCullMode(gl, cur, next) {
if (cur.cullMode === next.cullMode) {
return;
}
if (next.cullMode === enums.CULL_NONE) {
gl.disable(gl.CULL_FACE);
return;
}
gl.enable(gl.CULL_FACE);
gl.cullFace(next.cullMode);
}
/**
* _commitVertexBuffers
*/
function _commitVertexBuffers(device, gl, cur, next) {
let attrsDirty = false;
// nothing changed for vertex buffer
if (next.maxStream === -1) {
return;
}
if (cur.maxStream !== next.maxStream) {
attrsDirty = true;
} else if (cur.program !== next.program) {
attrsDirty = true;
} else {
for (let i = 0; i < next.maxStream + 1; ++i) {
if (
cur.vertexBuffers[i] !== next.vertexBuffers[i] ||
cur.vertexBufferOffsets[i] !== next.vertexBufferOffsets[i]
) {
attrsDirty = true;
break;
}
}
}
if (attrsDirty) {
for (let i = 0; i < device._caps.maxVertexAttribs; ++i) {
device._newAttributes[i] = 0;
}
for (let i = 0; i < next.maxStream + 1; ++i) {
let vb = next.vertexBuffers[i];
let vbOffset = next.vertexBufferOffsets[i];
if (!vb || vb._glID === -1) {
continue;
}
gl.bindBuffer(gl.ARRAY_BUFFER, vb._glID);
for (let j = 0; j < next.program._attributes.length; ++j) {
let attr = next.program._attributes[j];
let el = vb._format.element(attr.name);
if (!el) {
console.warn(`Can not find vertex attribute: ${attr.name}`);
continue;
}
if (device._enabledAttributes[attr.location] === 0) {
gl.enableVertexAttribArray(attr.location);
device._enabledAttributes[attr.location] = 1;
}
device._newAttributes[attr.location] = 1;
gl.vertexAttribPointer(
attr.location,
el.num,
el.type,
el.normalize,
el.stride,
el.offset + vbOffset * el.stride
);
}
}
// disable unused attributes
for (let i = 0; i < device._caps.maxVertexAttribs; ++i) {
if (device._enabledAttributes[i] !== device._newAttributes[i]) {
gl.disableVertexAttribArray(i);
device._enabledAttributes[i] = 0;
}
}
}
}
/**
* _commitTextures
*/
function _commitTextures(gl, cur, next) {
for (let i = 0; i < next.maxTextureSlot + 1; ++i) {
if (cur.textureUnits[i] !== next.textureUnits[i]) {
let texture = next.textureUnits[i];
if (texture && texture._glID !== -1) {
gl.activeTexture(gl.TEXTURE0 + i);
gl.bindTexture(texture._target, texture._glID);
}
}
}
}
/**
* _attach
*/
function _attach(gl, location, attachment, face = 0) {
if (attachment instanceof Texture2D) {
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
location,
gl.TEXTURE_2D,
attachment._glID,
0
);
} else if (attachment instanceof TextureCube) {
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
location,
gl.TEXTURE_CUBE_MAP_POSITIVE_X + face,
attachment._glID,
0
);
} else {
gl.framebufferRenderbuffer(
gl.FRAMEBUFFER,
location,
gl.RENDERBUFFER,
attachment._glID
);
}
}
export default class Device {
/**
* @property caps
*/
get caps() {
return this._caps;
}
/**
* @param {HTMLElement} canvasEL
* @param {object} opts
*/
constructor(canvasEL, opts) {
let gl;
// default options
opts = opts || {};
if (opts.alpha === undefined) {
opts.alpha = false;
}
if (opts.stencil === undefined) {
opts.stencil = true;
}
if (opts.depth === undefined) {
opts.depth = true;
}
if (opts.antialias === undefined) {
opts.antialias = false;
}
// NOTE: it is said the performance improved in mobile device with this flag off.
if (opts.preserveDrawingBuffer === undefined) {
opts.preserveDrawingBuffer = false;
}
try {
gl = canvasEL.getContext('webgl', opts)
|| canvasEL.getContext('experimental-webgl', opts)
|| canvasEL.getContext('webkit-3d', opts)
|| canvasEL.getContext('moz-webgl', opts);
} catch (err) {
console.error(err);
return;
}
// No errors are thrown using try catch
// Tested through ios baidu browser 4.14.1
if (!gl) {
console.error('This device does not support webgl');
}
// statics
/**
* @type {WebGLRenderingContext}
*/
this._gl = gl;
this._extensions = {};
this._caps = {}; // capability
this._stats = {
texture: 0,
vb: 0,
ib: 0,
drawcalls: 0,
};
// https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL_API/Using_Extensions
this._initExtensions([
'EXT_texture_filter_anisotropic',
'EXT_shader_texture_lod',
'OES_standard_derivatives',
'OES_texture_float',
'OES_texture_float_linear',
'OES_texture_half_float',
'OES_texture_half_float_linear',
'OES_vertex_array_object',
'WEBGL_compressed_texture_atc',
'WEBGL_compressed_texture_etc',
'WEBGL_compressed_texture_etc1',
'WEBGL_compressed_texture_pvrtc',
'WEBGL_compressed_texture_s3tc',
'WEBGL_depth_texture',
'WEBGL_draw_buffers',
]);
this._initCaps();
this._initStates();
// runtime
State.initDefault(this);
this._current = new State(this);
this._next = new State(this);
this._uniforms = {}; // name: { value, num, dirty }
this._vx = this._vy = this._vw = this._vh = 0;
this._sx = this._sy = this._sw = this._sh = 0;
this._framebuffer = null;
//
this._enabledAttributes = new Array(this._caps.maxVertexAttribs);
this._newAttributes = new Array(this._caps.maxVertexAttribs);
for (let i = 0; i < this._caps.maxVertexAttribs; ++i) {
this._enabledAttributes[i] = 0;
this._newAttributes[i] = 0;
}
}
_initExtensions(extensions) {
const gl = this._gl;
for (let i = 0; i < extensions.length; ++i) {
let name = extensions[i];
let vendorPrefixes = ["", "WEBKIT_", "MOZ_"];
for (var j = 0; j < vendorPrefixes.length; j++) {
try {
let ext = gl.getExtension(vendorPrefixes[j] + name);
if (ext) {
this._extensions[name] = ext;
break;
}
} catch (e) {
console.error(e);
}
}
}
}
_initCaps() {
const gl = this._gl;
const extDrawBuffers = this.ext('WEBGL_draw_buffers');
this._caps.maxVertexStreams = 4;
this._caps.maxVertexTextures = gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS);
this._caps.maxFragUniforms = gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS);
this._caps.maxTextureUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
this._caps.maxVertexAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
this._caps.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
this._caps.maxDrawBuffers = extDrawBuffers ? gl.getParameter(extDrawBuffers.MAX_DRAW_BUFFERS_WEBGL) : 1;
this._caps.maxColorAttachments = extDrawBuffers ? gl.getParameter(extDrawBuffers.MAX_COLOR_ATTACHMENTS_WEBGL) : 1;
}
_initStates() {
const gl = this._gl;
// gl.frontFace(gl.CCW);
gl.disable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ZERO);
gl.blendEquation(gl.FUNC_ADD);
gl.blendColor(1,1,1,1);
gl.colorMask(true, true, true, true);
gl.enable(gl.CULL_FACE);
gl.cullFace(gl.BACK);
gl.disable(gl.DEPTH_TEST);
gl.depthFunc(gl.LESS);
gl.depthMask(false);
gl.disable(gl.POLYGON_OFFSET_FILL);
gl.depthRange(0,1);
gl.disable(gl.STENCIL_TEST);
gl.stencilFunc(gl.ALWAYS, 0, 0xFF);
gl.stencilMask(0xFF);
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
// TODO:
// this.setAlphaToCoverage(false);
// this.setTransformFeedbackBuffer(null);
// this.setRaster(true);
// this.setDepthBias(false);
gl.clearDepth(1);
gl.clearColor(0, 0, 0, 0);
gl.clearStencil(0);
gl.disable(gl.SCISSOR_TEST);
}
_restoreTexture(unit) {
const gl = this._gl;
let texture = this._current.textureUnits[unit];
if (texture && texture._glID !== -1) {
gl.bindTexture(texture._target, texture._glID);
} else {
gl.bindTexture(gl.TEXTURE_2D, null);
}
}
_restoreIndexBuffer () {
const gl = this._gl;
let ib = this._current.indexBuffer;
if (ib && ib._glID !== -1) {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ib._glID);
}
else {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
}
/**
* @method ext
* @param {string} name
*/
ext(name) {
return this._extensions[name];
}
allowFloatTexture() {
return this.ext("OES_texture_float") != null;
}
// ===============================
// Immediate Settings
// ===============================
/**
* @method setFrameBuffer
* @param {FrameBuffer} fb - null means use the backbuffer
*/
setFrameBuffer(fb) {
if (this._framebuffer === fb) {
return;
}
this._framebuffer = fb;
const gl = this._gl;
if (!fb) {
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
return;
}
gl.bindFramebuffer(gl.FRAMEBUFFER, fb._glID);
let numColors = fb._colors.length;
for (let i = 0; i < numColors; ++i) {
let colorBuffer = fb._colors[i];
_attach(gl, gl.COLOR_ATTACHMENT0 + i, colorBuffer);
// TODO: what about cubemap face??? should be the target parameter for colorBuffer
}
for (let i = numColors; i < this._caps.maxColorAttachments; ++i) {
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0 + i,
gl.TEXTURE_2D,
null,
0
);
}
if (fb._depth) {
_attach(gl, gl.DEPTH_ATTACHMENT, fb._depth);
}
if (fb._stencil) {
_attach(gl, gl.STENCIL_ATTACHMENT, fb._stencil);
}
if (fb._depthStencil) {
_attach(gl, gl.DEPTH_STENCIL_ATTACHMENT, fb._depthStencil);
}
}
/**
* @method setViewport
* @param {Number} x
* @param {Number} y
* @param {Number} w
* @param {Number} h
*/
setViewport(x, y, w, h) {
if (
this._vx !== x ||
this._vy !== y ||
this._vw !== w ||
this._vh !== h
) {
this._gl.viewport(x, y, w, h);
this._vx = x;
this._vy = y;
this._vw = w;
this._vh = h;
}
}
/**
* @method setScissor
* @param {Number} x
* @param {Number} y
* @param {Number} w
* @param {Number} h
*/
setScissor(x, y, w, h) {
if (
this._sx !== x ||
this._sy !== y ||
this._sw !== w ||
this._sh !== h
) {
this._gl.scissor(x, y, w, h);
this._sx = x;
this._sy = y;
this._sw = w;
this._sh = h;
}
}
/**
* @method clear
* @param {Object} opts
* @param {Array} opts.color
* @param {Number} opts.depth
* @param {Number} opts.stencil
*/
clear(opts) {
if (opts.color === undefined && opts.depth === undefined && opts.stencil === undefined) {
return;
}
const gl = this._gl;
let flags = 0;
if (opts.color !== undefined) {
flags |= gl.COLOR_BUFFER_BIT;
gl.clearColor(opts.color[0], opts.color[1], opts.color[2], opts.color[3]);
}
if (opts.depth !== undefined) {
flags |= gl.DEPTH_BUFFER_BIT;
gl.clearDepth(opts.depth);
gl.enable(gl.DEPTH_TEST);
gl.depthMask(true);
gl.depthFunc(gl.ALWAYS);
}
if (opts.stencil !== undefined) {
flags |= gl.STENCIL_BUFFER_BIT;
gl.clearStencil(opts.stencil);
}
gl.clear(flags);
// restore depth-write
if (opts.depth !== undefined) {
if (this._current.depthTest === false) {
gl.disable(gl.DEPTH_TEST);
} else {
if (this._current.depthWrite === false) {
gl.depthMask(false);
}
if (this._current.depthFunc !== enums.DS_FUNC_ALWAYS) {
gl.depthFunc(this._current.depthFunc);
}
}
}
}
// ===============================
// Deferred States
// ===============================
/**
* @method enableBlend
*/
enableBlend() {
this._next.blend = true;
}
/**
* @method enableDepthTest
*/
enableDepthTest() {
this._next.depthTest = true;
}
/**
* @method enableDepthWrite
*/
enableDepthWrite() {
this._next.depthWrite = true;
}
/**
* @method enableStencilTest
* @param {Number} stencilTest
*/
setStencilTest(stencilTest) {
this._next.stencilTest = stencilTest;
}
/**
* @method setStencilFunc
* @param {DS_FUNC_*} func
* @param {Number} ref
* @param {Number} mask
*/
setStencilFunc(func, ref, mask) {
this._next.stencilSep = false;
this._next.stencilFuncFront = this._next.stencilFuncBack = func;
this._next.stencilRefFront = this._next.stencilRefBack = ref;
this._next.stencilMaskFront = this._next.stencilMaskBack = mask;
}
/**
* @method setStencilFuncFront
* @param {DS_FUNC_*} func
* @param {Number} ref
* @param {Number} mask
*/
setStencilFuncFront(func, ref, mask) {
this._next.stencilSep = true;
this._next.stencilFuncFront = func;
this._next.stencilRefFront = ref;
this._next.stencilMaskFront = mask;
}
/**
* @method setStencilFuncBack
* @param {DS_FUNC_*} func
* @param {Number} ref
* @param {Number} mask
*/
setStencilFuncBack(func, ref, mask) {
this._next.stencilSep = true;
this._next.stencilFuncBack = func;
this._next.stencilRefBack = ref;
this._next.stencilMaskBack = mask;
}
/**
* @method setStencilOp
* @param {STENCIL_OP_*} failOp
* @param {STENCIL_OP_*} zFailOp
* @param {STENCIL_OP_*} zPassOp
* @param {Number} writeMask
*/
setStencilOp(failOp, zFailOp, zPassOp, writeMask) {
this._next.stencilFailOpFront = this._next.stencilFailOpBack = failOp;
this._next.stencilZFailOpFront = this._next.stencilZFailOpBack = zFailOp;
this._next.stencilZPassOpFront = this._next.stencilZPassOpBack = zPassOp;
this._next.stencilWriteMaskFront = this._next.stencilWriteMaskBack = writeMask;
}
/**
* @method setStencilOpFront
* @param {STENCIL_OP_*} failOp
* @param {STENCIL_OP_*} zFailOp
* @param {STENCIL_OP_*} zPassOp
* @param {Number} writeMask
*/
setStencilOpFront(failOp, zFailOp, zPassOp, writeMask) {
this._next.stencilSep = true;
this._next.stencilFailOpFront = failOp;
this._next.stencilZFailOpFront = zFailOp;
this._next.stencilZPassOpFront = zPassOp;
this._next.stencilWriteMaskFront = writeMask;
}
/**
* @method setStencilOpBack
* @param {STENCIL_OP_*} failOp
* @param {STENCIL_OP_*} zFailOp
* @param {STENCIL_OP_*} zPassOp
* @param {Number} writeMask
*/
setStencilOpBack(failOp, zFailOp, zPassOp, writeMask) {
this._next.stencilSep = true;
this._next.stencilFailOpBack = failOp;
this._next.stencilZFailOpBack = zFailOp;
this._next.stencilZPassOpBack = zPassOp;
this._next.stencilWriteMaskBack = writeMask;
}
/**
* @method setDepthFunc
* @param {DS_FUNC_*} depthFunc
*/
setDepthFunc(depthFunc) {
this._next.depthFunc = depthFunc;
}
/**
* @method setBlendColor32
* @param {Number} rgba
*/
setBlendColor32(rgba) {
this._next.blendColor = rgba;
}
/**
* @method setBlendColor
* @param {Number} r
* @param {Number} g
* @param {Number} b
* @param {Number} a
*/
setBlendColor(r, g, b, a) {
this._next.blendColor = ((r * 255) << 24 | (g * 255) << 16 | (b * 255) << 8 | a * 255) >>> 0;
}
/**
* @method setBlendFunc
* @param {BELND_*} src
* @param {BELND_*} dst
*/
setBlendFunc(src, dst) {
this._next.blendSep = false;
this._next.blendSrc = src;
this._next.blendDst = dst;
}
/**
* @method setBlendFuncSep
* @param {BELND_*} src
* @param {BELND_*} dst
* @param {BELND_*} srcAlpha
* @param {BELND_*} dstAlpha
*/
setBlendFuncSep(src, dst, srcAlpha, dstAlpha) {
this._next.blendSep = true;
this._next.blendSrc = src;
this._next.blendDst = dst;
this._next.blendSrcAlpha = srcAlpha;
this._next.blendDstAlpha = dstAlpha;
}
/**
* @method setBlendEq
* @param {BELND_FUNC_*} eq
*/
setBlendEq(eq) {
this._next.blendSep = false;
this._next.blendEq = eq;
}
/**
* @method setBlendEqSep
* @param {BELND_FUNC_*} eq
* @param {BELND_FUNC_*} alphaEq
*/
setBlendEqSep(eq, alphaEq) {
this._next.blendSep = true;
this._next.blendEq = eq;
this._next.blendAlphaEq = alphaEq;
}
/**
* @method setCullMode
* @param {CULL_*} mode
*/
setCullMode(mode) {
this._next.cullMode = mode;
}
/**
* @method setVertexBuffer
* @param {Number} stream
* @param {VertexBuffer} buffer
* @param {Number} start - start vertex
*/
setVertexBuffer(stream, buffer, start = 0) {
this._next.vertexBuffers[stream] = buffer;
this._next.vertexBufferOffsets[stream] = start;
if (this._next.maxStream < stream) {
this._next.maxStream = stream;
}
}
/**
* @method setIndexBuffer
* @param {IndexBuffer} buffer
*/
setIndexBuffer(buffer) {
this._next.indexBuffer = buffer;
}
/**
* @method setProgram
* @param {Program} program
*/
setProgram(program) {
this._next.program = program;
}
/**
* @method setTexture
* @param {String} name
* @param {Texture} texture
* @param {Number} slot
*/
setTexture(name, texture, slot) {
if (slot >= this._caps.maxTextureUnits) {
console.warn(`Can not set texture ${name} at stage ${slot}, max texture exceed: ${this._caps.maxTextureUnits}`);
return;
}
this._next.textureUnits[slot] = texture;
this.setUniform(name, slot);
if (this._next.maxTextureSlot < slot) {
this._next.maxTextureSlot = slot;
}
}
/**
* @method setTextureArray
* @param {String} name
* @param {Array} textures
* @param {Int32Array} slots
*/
setTextureArray(name, textures, slots) {
let len = textures.length;
if (len >= this._caps.maxTextureUnits) {
console.warn(`Can not set ${len} textures for ${name}, max texture exceed: ${this._caps.maxTextureUnits}`);
return;
}
for (let i = 0; i < len; ++i) {
let slot = slots[i];
this._next.textureUnits[slot] = textures[i];
if (this._next.maxTextureSlot < slot) {
this._next.maxTextureSlot = slot;
}
}
this.setUniform(name, slots);
}
/**
* @method setUniform
* @param {String} name
* @param {*} value
*/
setUniform(name, value) {
let uniform = this._uniforms[name];
let sameType = false;
let isArray = false, isFloat32Array = false, isInt32Array = false;
do {
if (!uniform) {
break;
}
isFloat32Array = Array.isArray(value) || value instanceof Float32Array;
isInt32Array = value instanceof Int32Array;
isArray = isFloat32Array || isInt32Array;
if (uniform.isArray !== isArray) {
break;
}
if (uniform.isArray && uniform.value.length !== value.length) {
break;
}
sameType = true;
} while (false);
if (!sameType) {
let newValue = value;
if (isFloat32Array) {
newValue = new Float32Array(value);
}
else if (isInt32Array) {
newValue = new Int32Array(value);
}
uniform = {
dirty: true,
value: newValue,
isArray: isArray
};
} else {
let oldValue = uniform.value;
let dirty = false;
if (uniform.isArray) {
for (let i = 0, l = oldValue.length; i < l; i++) {
if (oldValue[i] !== value[i]) {
dirty = true;
oldValue[i] = value[i];
}
}
}
else {
if (oldValue !== value) {
dirty = true;
uniform.value = value;
}
}
if (dirty) {
uniform.dirty = true;
}
}
this._uniforms[name] = uniform;
}
setUniformDirectly(name, value) {
let uniform = this._uniforms[name];
if (!uniform) {
this._uniforms[name] = uniform = {};
}
uniform.dirty = true;
uniform.value = value;
}
/**
* @method setPrimitiveType
* @param {PT_*} type
*/
setPrimitiveType(type) {
this._next.primitiveType = type;
}
/**
* @method resetDrawCalls
*/
resetDrawCalls () {
this._stats.drawcalls = 0;
}
/**
* @method getDrawCalls
*/
getDrawCalls () {
return this._stats.drawcalls;
}
/**
* @method draw
* @param {Number} base
* @param {Number} count
*/
draw(base, count) {
const gl = this._gl;
let cur = this._current;
let next = this._next;
// commit blend
_commitBlendStates(gl, cur, next);
// commit depth
_commitDepthStates(gl, cur, next);
// commit stencil
_commitStencilStates(gl, cur, next);
// commit cull
_commitCullMode(gl, cur, next);
// commit vertex-buffer
_commitVertexBuffers(this, gl, cur, next);
// commit index-buffer
if (cur.indexBuffer !== next.indexBuffer) {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, next.indexBuffer && next.indexBuffer._glID !== -1 ? next.indexBuffer._glID : null);
}
// commit program
let programDirty = false;
if (cur.program !== next.program) {
if (next.program._linked) {
gl.useProgram(next.program._glID);
} else {
console.warn('Failed to use program: has not linked yet.');
}
programDirty = true;
}
// commit texture/sampler
_commitTextures(gl, cur, next);
// commit uniforms
for (let i = 0; i < next.program._uniforms.length; ++i) {
let uniformInfo = next.program._uniforms[i];
let uniform = this._uniforms[uniformInfo.name];
if (!uniform) {
// console.warn(`Can not find uniform ${uniformInfo.name}`);
continue;
}
if (!programDirty && !uniform.dirty) {
continue;
}
uniform.dirty = false;
// TODO: please consider array uniform: uniformInfo.size > 0
let commitFunc = (uniformInfo.size === undefined) ? _type2uniformCommit[uniformInfo.type] : _type2uniformArrayCommit[uniformInfo.type];
if (!commitFunc) {
console.warn(`Can not find commit function for uniform ${uniformInfo.name}`);
continue;
}
commitFunc(gl, uniformInfo.location, uniform.value);
}
if (count) {
// drawPrimitives
if (next.indexBuffer) {
gl.drawElements(
this._next.primitiveType,
count,
next.indexBuffer._format,
base * next.indexBuffer._bytesPerIndex
);
} else {
gl.drawArrays(
this._next.primitiveType,
base,
count
);
}
// update stats
this._stats.drawcalls++;
}
// TODO: autogen mipmap for color buffer
// if (this._framebuffer && this._framebuffer.colors[0].mipmap) {
// gl.bindTexture(this._framebuffer.colors[i]._target, colors[i]._glID);
// gl.generateMipmap(this._framebuffer.colors[i]._target);
// }
// reset states
cur.set(next);
next.reset();
}
}