初始化

This commit is contained in:
SmallMain
2022-06-25 00:23:03 +08:00
commit ef0589e8e5
2264 changed files with 617829 additions and 0 deletions

View File

@@ -0,0 +1,415 @@
// Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
import { RecyclePool } from '../memop';
import enums from '../enums';
import { Vec2, Vec4, Mat3, Mat4, Color, Vec3 } from '../../core/value-types';
import ProgramLib from './program-lib';
import View from './view';
import gfx from '../gfx';
let _m4_tmp = new Mat4();
let _stageInfos = new RecyclePool(() => {
return {
stage: null,
items: null,
};
}, 8);
let _float2_pool = new RecyclePool(() => {
return new Float32Array(2);
}, 8);
let _float3_pool = new RecyclePool(() => {
return new Float32Array(3);
}, 8);
let _float4_pool = new RecyclePool(() => {
return new Float32Array(4);
}, 8);
let _float9_pool = new RecyclePool(() => {
return new Float32Array(9);
}, 8);
let _float16_pool = new RecyclePool(() => {
return new Float32Array(16);
}, 8);
let _float64_pool = new RecyclePool(() => {
return new Float32Array(64);
}, 8);
let _int2_pool = new RecyclePool(() => {
return new Int32Array(2);
}, 8);
let _int3_pool = new RecyclePool(() => {
return new Int32Array(3);
}, 8);
let _int4_pool = new RecyclePool(() => {
return new Int32Array(4);
}, 8);
let _int64_pool = new RecyclePool(() => {
return new Int32Array(64);
}, 8);
export default class Base {
/**
* @param {gfx.Device} device
* @param {Object} opts
* @param {gfx.Texture2D} opts.defaultTexture
* @param {gfx.TextureCube} opts.defaultTextureCube
*/
constructor (device, opts) {
this._device = device;
this._programLib = new ProgramLib(device);
this._opts = opts;
this._type2defaultValue = {
[enums.PARAM_INT]: 0,
[enums.PARAM_INT2]: new Vec2(0, 0),
[enums.PARAM_INT3]: new Vec3(0, 0, 0),
[enums.PARAM_INT4]: new Vec4(0, 0, 0, 0),
[enums.PARAM_FLOAT]: 0.0,
[enums.PARAM_FLOAT2]: new Vec2(0, 0),
[enums.PARAM_FLOAT3]: new Vec3(0, 0, 0),
[enums.PARAM_FLOAT4]: new Vec4(0, 0, 0, 0),
[enums.PARAM_COLOR4]: new Color(0, 0, 0, 1),
[enums.PARAM_MAT3]: new Mat3(),
[enums.PARAM_MAT4]: new Mat4(),
[enums.PARAM_TEXTURE_2D]: opts.defaultTexture,
[enums.PARAM_TEXTURE_CUBE]: opts.defaultTextureCube,
};
this._stage2fn = {};
this._usedTextureUnits = 0;
this._viewPools = new RecyclePool(() => {
return new View();
}, 8);
this._drawItemsPools = new RecyclePool(() => {
return {
model: null,
node: null,
ia: null,
effect: null,
defines: null,
uniforms: null
};
}, 100);
this._stageItemsPools = new RecyclePool(() => {
return new RecyclePool(() => {
return {
model: null,
node: null,
ia: null,
effect: null,
defines: null,
passes: [],
sortKey: -1,
uniforms: null
};
}, 100);
}, 16);
this._definesChanged = false;
}
_resetTextuerUnit () {
this._usedTextureUnits = 0;
}
_allocTextureUnit () {
const device = this._device;
let unit = this._usedTextureUnits;
if (unit >= device._caps.maxTextureUnits) {
console.warn(`Trying to use ${unit} texture units while this GPU supports only ${device._caps.maxTextureUnits}`);
}
this._usedTextureUnits += 1;
return unit;
}
_registerStage (name, fn) {
this._stage2fn[name] = fn;
}
clear () {
this._programLib.clear();
this.reset();
}
reset () {
this._viewPools.reset();
this._stageItemsPools.reset();
this._definesChanged = false;
}
_requestView () {
return this._viewPools.add();
}
_render (view, scene) {
const device = this._device;
// setup framebuffer
device.setFrameBuffer(view._framebuffer);
// setup viewport
device.setViewport(
view._rect.x,
view._rect.y,
view._rect.w,
view._rect.h
);
// setup clear
let clearOpts = {};
if (view._clearFlags & enums.CLEAR_COLOR) {
clearOpts.color = Vec4.toArray([], view._color);
}
if (view._clearFlags & enums.CLEAR_DEPTH) {
clearOpts.depth = view._depth;
}
if (view._clearFlags & enums.CLEAR_STENCIL) {
clearOpts.stencil = view._stencil;
}
device.clear(clearOpts);
// get all draw items
this._drawItemsPools.reset();
for (let i = 0; i < scene._models.length; ++i) {
let model = scene._models.data[i];
// filter model by view
if ((model._cullingMask & view._cullingMask) === 0) {
continue;
}
let drawItem = this._drawItemsPools.add();
model.extractDrawItem(drawItem);
}
// dispatch draw items to different stage
_stageInfos.reset();
for (let i = 0; i < view._stages.length; ++i) {
let stage = view._stages[i];
let stageItems = this._stageItemsPools.add();
stageItems.reset();
for (let j = 0; j < this._drawItemsPools.length; ++j) {
let drawItem = this._drawItemsPools.data[j];
let passes = drawItem.effect.stagePasses[stage];
if (!passes || passes.length === 0) continue;
let stageItem = stageItems.add();
stageItem.passes = passes;
stageItem.model = drawItem.model;
stageItem.node = drawItem.node;
stageItem.ia = drawItem.ia;
stageItem.effect = drawItem.effect;
stageItem.defines = drawItem.defines;
stageItem.sortKey = -1;
stageItem.uniforms = drawItem.uniforms;
}
let stageInfo = _stageInfos.add();
stageInfo.stage = stage;
stageInfo.items = stageItems;
}
// render stages
for (let i = 0; i < _stageInfos.length; ++i) {
let info = _stageInfos.data[i];
let fn = this._stage2fn[info.stage];
fn(view, info.items);
}
}
_setProperty (prop) {
const device = this._device;
let param = prop.value;
if (param === undefined) {
param = prop.val;
}
if (param === undefined) {
param = this._type2defaultValue[prop.type];
}
if (param === undefined) {
console.warn(`Failed to set technique property ${prop.name}, value not found.`);
return;
}
if (
prop.type === enums.PARAM_TEXTURE_2D ||
prop.type === enums.PARAM_TEXTURE_CUBE
) {
if (Array.isArray(param)) {
if (param.length > prop.count) {
console.error(`Failed to set property [${prop.name}] : The length of texture array [${param.length}] is bigger than [${prop.count}].`);
return;
}
let slots = _int64_pool.add();
for (let index = 0; index < param.length; ++index) {
slots[index] = this._allocTextureUnit();
}
device.setTextureArray(prop.name, param, slots);
} else {
device.setTexture(prop.name, param, this._allocTextureUnit());
}
} else {
if (prop.directly) {
device.setUniformDirectly(prop.name, param);
}
else {
device.setUniform(prop.name, param);
}
}
}
_draw (item) {
const device = this._device;
const programLib = this._programLib;
const { node, ia, passes, effect } = item;
// reset the pool
// NOTE: we can use drawCounter optimize this
// TODO: should be configurable
_float2_pool.reset();
_float3_pool.reset();
_float4_pool.reset();
_float9_pool.reset();
_float16_pool.reset();
_float64_pool.reset();
_int2_pool.reset();
_int3_pool.reset();
_int4_pool.reset();
_int64_pool.reset();
// set common uniforms
// TODO: try commit this depends on effect
// {
node.getWorldMatrix(_m4_tmp);
device.setUniform('cc_matWorld', Mat4.toArray(_float16_pool.add(), _m4_tmp));
// let wq = node.getWorldRotation(cc.quat());
Mat4.invert(_m4_tmp, _m4_tmp);
Mat4.transpose(_m4_tmp, _m4_tmp);
device.setUniform('cc_matWorldIT', Mat4.toArray(_float16_pool.add(), _m4_tmp));
// }
let defines = this._defines;
// for each pass
for (let i = 0; i < passes.length; ++i) {
let pass = passes[i];
if (this._definesChanged) {
pass._programKey = null;
}
let count = ia.count;
// set vertex buffer
if (ia._vertexBuffer) {
device.setVertexBuffer(0, ia._vertexBuffer);
}
// set index buffer
if (ia._indexBuffer) {
device.setIndexBuffer(ia._indexBuffer);
}
// set primitive type
device.setPrimitiveType(ia._primitiveType);
// set program
Object.setPrototypeOf(defines, pass._defines);
let program = programLib.getProgram(pass, defines, effect.name);
device.setProgram(program);
let uniforms = program._uniforms;
let variants = pass._properties;
for (let j = 0; j < uniforms.length; j++) {
let prop = variants[uniforms[j].name];
if (prop !== undefined)
this._setProperty(prop);
}
// cull mode
device.setCullMode(pass._cullMode);
// blend
if (pass._blend) {
device.enableBlend();
device.setBlendFuncSep(
pass._blendSrc,
pass._blendDst,
pass._blendSrcAlpha,
pass._blendDstAlpha
);
device.setBlendEqSep(
pass._blendEq,
pass._blendAlphaEq
);
device.setBlendColor32(pass._blendColor);
}
// depth test & write
if (pass._depthTest) {
device.enableDepthTest();
device.setDepthFunc(pass._depthFunc);
}
if (pass._depthWrite) {
device.enableDepthWrite();
}
// stencil
device.setStencilTest(pass._stencilTest);
if (pass._stencilTest === gfx.STENCIL_ENABLE) {
// front
device.setStencilFuncFront(
pass._stencilFuncFront,
pass._stencilRefFront,
pass._stencilMaskFront
);
device.setStencilOpFront(
pass._stencilFailOpFront,
pass._stencilZFailOpFront,
pass._stencilZPassOpFront,
pass._stencilWriteMaskFront
);
// back
device.setStencilFuncBack(
pass._stencilFuncBack,
pass._stencilRefBack,
pass._stencilMaskBack
);
device.setStencilOpBack(
pass._stencilFailOpBack,
pass._stencilZFailOpBack,
pass._stencilZPassOpBack,
pass._stencilWriteMaskBack
);
}
// draw pass
device.draw(ia._start, count);
this._resetTextuerUnit();
}
}
}

View File

@@ -0,0 +1,13 @@
let RenderQueue = {
OPAQUE: 0,
TRANSPARENT: 1,
OVERLAY: 2
};
let PassStage = {
DEFAULT: 1,
FORWARD: 2,
SHADOWCAST: 4
};
export { RenderQueue, PassStage };

View File

@@ -0,0 +1,35 @@
// Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
import gfx from '../gfx';
export default class InputAssembler {
constructor(vb, ib, pt = gfx.PT_TRIANGLES) {
this._vertexBuffer = vb;
this._indexBuffer = ib;
this._primitiveType = pt;
this._start = 0;
this._count = -1;
// TODO: instancing data
// this._stream = 0;
}
/**
* @property {Number} count The number of indices or vertices to dispatch in the draw call.
*/
get count() {
if (this._count !== -1) {
return this._count;
}
if (this._indexBuffer) {
return this._indexBuffer.count;
}
if (this._vertexBuffer) {
return this._vertexBuffer.count;
}
return 0;
}
}

View File

@@ -0,0 +1,246 @@
// Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
import gfx from '../gfx';
import enums from '../enums';
import ValueType from '../../core/value-types/value-type';
export default class Pass {
constructor (name, detailName, programName, stage, properties = {}, defines = {}) {
this._name = name;
this._detailName = detailName;
this._programName = programName;
this._programKey = null;
this._stage = stage;
this._properties = properties;
this._defines = defines;
this._propertyNames = Object.keys(properties)
this._defineNames = Object.keys(defines)
// cullmode
this._cullMode = gfx.CULL_BACK;
// blending
this._blend = false;
this._blendEq = gfx.BLEND_FUNC_ADD;
this._blendAlphaEq = gfx.BLEND_FUNC_ADD;
this._blendSrc = gfx.BLEND_SRC_ALPHA;
this._blendDst = gfx.BLEND_ONE_MINUS_SRC_ALPHA;
this._blendSrcAlpha = gfx.BLEND_SRC_ALPHA;
this._blendDstAlpha = gfx.BLEND_ONE_MINUS_SRC_ALPHA;
this._blendColor = 0xffffffff;
// depth
this._depthTest = false;
this._depthWrite = false;
this._depthFunc = gfx.DS_FUNC_LESS,
// stencil
this._stencilTest = gfx.STENCIL_INHERIT;
// front
this._stencilFuncFront = gfx.DS_FUNC_ALWAYS;
this._stencilRefFront = 0;
this._stencilMaskFront = 0xff;
this._stencilFailOpFront = gfx.STENCIL_OP_KEEP;
this._stencilZFailOpFront = gfx.STENCIL_OP_KEEP;
this._stencilZPassOpFront = gfx.STENCIL_OP_KEEP;
this._stencilWriteMaskFront = 0xff;
// back
this._stencilFuncBack = gfx.DS_FUNC_ALWAYS;
this._stencilRefBack = 0;
this._stencilMaskBack = 0xff;
this._stencilFailOpBack = gfx.STENCIL_OP_KEEP;
this._stencilZFailOpBack = gfx.STENCIL_OP_KEEP;
this._stencilZPassOpBack = gfx.STENCIL_OP_KEEP;
this._stencilWriteMaskBack = 0xff;
}
setCullMode (cullMode = gfx.CULL_BACK) {
this._cullMode = cullMode;
}
setBlend (
enabled = false,
blendEq = gfx.BLEND_FUNC_ADD,
blendSrc = gfx.BLEND_SRC_ALPHA,
blendDst = gfx.BLEND_ONE_MINUS_SRC_ALPHA,
blendAlphaEq = gfx.BLEND_FUNC_ADD,
blendSrcAlpha = gfx.BLEND_SRC_ALPHA,
blendDstAlpha = gfx.BLEND_ONE_MINUS_SRC_ALPHA,
blendColor = 0xffffffff
) {
this._blend = enabled;
this._blendEq = blendEq;
this._blendSrc = blendSrc;
this._blendDst = blendDst;
this._blendAlphaEq = blendAlphaEq;
this._blendSrcAlpha = blendSrcAlpha;
this._blendDstAlpha = blendDstAlpha;
this._blendColor = blendColor;
}
setDepth (
depthTest = false,
depthWrite = false,
depthFunc = gfx.DS_FUNC_LESS
) {
this._depthTest = depthTest;
this._depthWrite = depthWrite;
this._depthFunc = depthFunc;
}
setStencilFront (
enabled = gfx.STENCIL_INHERIT,
stencilFunc = gfx.DS_FUNC_ALWAYS,
stencilRef = 0,
stencilMask = 0xff,
stencilFailOp = gfx.STENCIL_OP_KEEP,
stencilZFailOp = gfx.STENCIL_OP_KEEP,
stencilZPassOp = gfx.STENCIL_OP_KEEP,
stencilWriteMask = 0xff
) {
this._stencilTest = enabled;
this._stencilFuncFront = stencilFunc;
this._stencilRefFront = stencilRef;
this._stencilMaskFront = stencilMask;
this._stencilFailOpFront = stencilFailOp;
this._stencilZFailOpFront = stencilZFailOp;
this._stencilZPassOpFront = stencilZPassOp;
this._stencilWriteMaskFront = stencilWriteMask;
}
setStencilEnabled (stencilTest = gfx.STENCIL_INHERIT) {
this._stencilTest = stencilTest;
}
setStencilBack (
stencilTest = gfx.STENCIL_INHERIT,
stencilFunc = gfx.DS_FUNC_ALWAYS,
stencilRef = 0,
stencilMask = 0xff,
stencilFailOp = gfx.STENCIL_OP_KEEP,
stencilZFailOp = gfx.STENCIL_OP_KEEP,
stencilZPassOp = gfx.STENCIL_OP_KEEP,
stencilWriteMask = 0xff
) {
this._stencilTest = stencilTest;
this._stencilFuncBack = stencilFunc;
this._stencilRefBack = stencilRef;
this._stencilMaskBack = stencilMask;
this._stencilFailOpBack = stencilFailOp;
this._stencilZFailOpBack = stencilZFailOp;
this._stencilZPassOpBack = stencilZPassOp;
this._stencilWriteMaskBack = stencilWriteMask;
}
setStage (stage) {
this._stage = stage;
}
setProperties (properties) {
this._properties = properties;
}
getProperty (name) {
if (!this._properties[name]) {
return;
}
return this._properties[name].value;
}
setProperty (name, value, directly) {
let prop = this._properties[name];
if (!prop) {
return false;
}
prop.directly = directly;
if (Array.isArray(value)) {
let array = prop.value;
if (array.length !== value.length) {
cc.warnID(9105, this._name, name);
return;
}
for (let i = 0; i < value.length; i++) {
array[i] = value[i];
}
}
else {
if (value && !ArrayBuffer.isView(value)) {
if (prop.type === enums.PARAM_TEXTURE_2D) {
prop.value = value.getImpl();
}
else if (value instanceof ValueType) {
value.constructor.toArray(prop.value, value);
}
else {
if (typeof value === 'object') {
cc.warnID(9106, this._name, name);
}
prop.value = value;
}
}
else {
prop.value = value;
}
}
return true;
}
getDefine (name) {
return this._defines[name];
}
define (name, value, force) {
let oldValue = this._defines[name];
if (!force && oldValue === undefined) {
return false;
}
if (oldValue !== value) {
this._defines[name] = value;
this._programKey = null;
}
return true;
}
clone () {
let pass = new Pass(this._programName);
Object.assign(pass, this);
let newProperties = {};
let properties = this._properties;
for (let name in properties) {
let prop = properties[name];
let newProp = newProperties[name] = {};
let value = prop.value;
if (Array.isArray(value)) {
newProp.value = value.concat();
}
else if (ArrayBuffer.isView(value)) {
newProp.value = new value.__proto__.constructor(value);
}
else {
newProp.value = value;
}
for (let name in prop) {
if (name === 'value') continue;
newProp[name] = prop[name];
}
}
pass._properties = newProperties;
pass._defines = Object.assign({}, this._defines);
pass._propertyNames = this._propertyNames;
pass._defineNames = this._defineNames;
return pass;
}
}

View File

@@ -0,0 +1,262 @@
// Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
import gfx from '../gfx';
let _shdID = 0;
function _generateDefines(tmpDefines, defines) {
let results = [];
for (let i = 0; i < tmpDefines.length; i++) {
let name = tmpDefines[i].name;
let value = defines[name];
if (typeof value !== 'number') {
value = value ? 1 : 0;
}
results.push(`#define ${name} ${value}`);
}
return results.join('\n') + '\n';
}
function _replaceMacroNums(string, tmpDefines, defines) {
let tmp = string;
for (let i = 0; i < tmpDefines.length; i++) {
let name = tmpDefines[i].name;
let value = defines[name];
if (Number.isInteger(value)) {
let reg = new RegExp(name, 'g');
tmp = tmp.replace(reg, value);
}
}
return tmp;
}
function _unrollLoops(string) {
let pattern = /#pragma for (\w+) in range\(\s*(\d+)\s*,\s*(\d+)\s*\)([\s\S]+?)#pragma endFor/g;
function replace(match, index, begin, end, snippet) {
let unroll = '';
let parsedBegin = parseInt(begin);
let parsedEnd = parseInt(end);
if (parsedBegin.isNaN || parsedEnd.isNaN) {
console.error('Unroll For Loops Error: begin and end of range must be an int num.');
}
for (let i = parsedBegin; i < parsedEnd; ++i) {
unroll += snippet.replace(new RegExp(`{${index}}`, 'g'), i);
}
return unroll;
}
return string.replace(pattern, replace);
}
function _replaceHighp(string) {
return string.replace(/\bhighp\b/g, 'mediump');
}
export default class ProgramLib {
/**
* @param {gfx.Device} device
*/
constructor(device) {
this._device = device;
// register templates
this._templates = {};
this._cache = {};
this._checkPrecision();
}
clear () {
this._templates = {};
this._cache = {};
}
/**
* @param {string} name
* @param {string} vert
* @param {string} frag
* @param {Object[]} defines
*
* @example:
* // this object is auto-generated from your actual shaders
* let program = {
* name: 'foobar',
* vert: vertTmpl,
* frag: fragTmpl,
* defines: [
* { name: 'shadow', type: 'boolean' },
* { name: 'lightCount', type: 'number', min: 1, max: 4 }
* ],
* attributes: [{ name: 'a_position', type: 'vec3' }],
* uniforms: [{ name: 'color', type: 'vec4' }],
* extensions: ['GL_OES_standard_derivatives'],
* };
* programLib.define(program);
*/
define(prog) {
let { name, defines, glsl1 } = prog;
let { vert, frag } = glsl1 || prog;
if (this._templates[name]) {
// console.warn(`Failed to define shader ${name}: already exists.`);
return;
}
let id = ++_shdID;
// calculate option mask offset
let offset = 0;
for (let i = 0; i < defines.length; ++i) {
let def = defines[i];
let cnt = 1;
if (def.type === 'number') {
let range = def.range || [];
def.min = range[0] || 0;
def.max = range[1] || 4;
cnt = Math.ceil(Math.log2(def.max - def.min));
def._map = function (value) {
return (value - this.min) << this._offset;
}.bind(def);
} else { // boolean
def._map = function (value) {
if (value) {
return 1 << this._offset;
}
return 0;
}.bind(def);
}
def._offset = offset;
offset += cnt;
}
let uniforms = prog.uniforms || [];
if (prog.samplers) {
for (let i = 0; i < prog.samplers.length; i++) {
uniforms.push(prog.samplers[i])
}
}
if (prog.blocks) {
for (let i = 0; i < prog.blocks.length; i++) {
let defines = prog.blocks[i].defines;
let members = prog.blocks[i].members;
for (let j = 0; j < members.length; j++) {
uniforms.push({
defines,
name: members[j].name,
type: members[j].type,
})
}
}
}
// store it
this._templates[name] = {
id,
name,
vert,
frag,
defines,
attributes: prog.attributes,
uniforms,
extensions: prog.extensions
};
}
getTemplate(name) {
return this._templates[name];
}
/**
* Does this library has the specified program?
* @param {string} name
* @returns {boolean}
*/
hasProgram(name) {
return this._templates[name] !== undefined;
}
getKey(name, defines) {
let tmpl = this._templates[name];
let key = 0;
for (let i = 0; i < tmpl.defines.length; ++i) {
let tmplDefs = tmpl.defines[i];
let value = defines[tmplDefs.name];
if (value === undefined) {
continue;
}
key |= tmplDefs._map(value);
}
// return key << 8 | tmpl.id;
// key number maybe bigger than 32 bit, need use string to store value.
return tmpl.id + ':' + key;
}
getProgram(pass, defines, errPrefix) {
let key = pass._programKey = pass._programKey || this.getKey(pass._programName, defines);
let program = this._cache[key];
if (program) {
return program;
}
// get template
let tmpl = this._templates[pass._programName];
let customDef = _generateDefines(tmpl.defines, defines);
let vert = _replaceMacroNums(tmpl.vert, tmpl.defines, defines);
vert = customDef + _unrollLoops(vert);
if (!this._highpSupported) {
vert = _replaceHighp(vert);
}
let frag = _replaceMacroNums(tmpl.frag, tmpl.defines, defines);
frag = customDef + _unrollLoops(frag);
if (!this._highpSupported) {
frag = _replaceHighp(frag);
}
program = new gfx.Program(this._device, {
vert,
frag
});
let errors = program.link();
if (errors) {
let vertLines = vert.split('\n');
let fragLines = frag.split('\n');
let defineLength = tmpl.defines.length;
errors.forEach(err => {
let line = err.line - 1;
let originLine = err.line - defineLength;
let lines = err.type === 'vs' ? vertLines : fragLines;
// let source = ` ${lines[line-1]}\n>${lines[line]}\n ${lines[line+1]}`;
let source = lines[line];
let info = err.info || `Failed to compile ${err.type} ${err.fileID} (ln ${originLine}): \n ${err.message}: \n ${source}`;
cc.error(`${errPrefix} : ${info}`);
})
}
this._cache[key] = program;
return program;
}
_checkPrecision () {
let gl = this._device._gl;
let highpSupported = false;
if (gl.getShaderPrecisionFormat) {
let vertHighp = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_FLOAT);
let fragHighp = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT);
highpSupported = (vertHighp && vertHighp.precision > 0) &&
(fragHighp && fragHighp.precision > 0);
}
if (!highpSupported) {
cc.warnID(9102);
}
this._highpSupported = highpSupported;
}
}

View File

@@ -0,0 +1,24 @@
// Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
export default class Technique {
constructor(name, passes) {
this._name = name;
this._passes = passes;
}
get name () {
return this._name;
}
get passes() {
return this._passes;
}
clone () {
let passes = [];
for (let i = 0; i < this._passes.length; i++) {
passes.push(this._passes[i].clone());
}
return new Technique(this._name, passes);
}
}

View File

@@ -0,0 +1,80 @@
// Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
import { Vec3, Mat4, Vec4 } from '../../core/value-types';
import enums from '../enums';
let _m4_tmp = new Mat4();
let _genID = 0;
/**
* A representation of a single camera view
*/
export default class View {
/**
* Setup a default view
*/
constructor() {
this._id = _genID++;
// priority. the smaller one will be rendered first
this._priority = 0;
// viewport
this._rect = {
x: 0, y: 0, w: 1, h: 1
};
// TODO:
// this._scissor = {
// x: 0, y: 0, w: 1, h: 1
// };
// clear options
this._color = new Vec4(0.3, 0.3, 0.3, 1);
this._depth = 1;
this._stencil = 0;
this._clearFlags = enums.CLEAR_COLOR | enums.CLEAR_DEPTH;
this._clearModel = null;
// matrix
this._matView = cc.mat4();
this._matViewInv = cc.mat4();
this._matProj = cc.mat4();
this._matViewProj = cc.mat4();
this._matInvViewProj = cc.mat4();
// stages & framebuffer
this._stages = [];
this._cullingByID = false;
this._framebuffer = null;
this._shadowLight = null; // TODO: should not refer light in view.
this._cullingMask = 0xffffffff;
}
/**
* Get the view's forward direction
* @param {Vec3} out the receiving vector
* @returns {Vec3} the receiving vector
*/
getForward(out) {
let m = this._matView.m;
return Vec3.set(
out,
-m[2],
-m[6],
-m[10]
);
}
/**
* Get the view's observing location
* @param {Vec3} out the receiving vector
* @returns {Vec3} the receiving vector
*/
getPosition(out) {
Mat4.invert(_m4_tmp, this._matView);
return Mat4.getTranslation(out, _m4_tmp);
}
}

View File

@@ -0,0 +1,50 @@
// Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
import { typeMap } from './build/mappings'
export default {
// projection
PROJ_PERSPECTIVE: 0,
PROJ_ORTHO: 1,
// lights
LIGHT_DIRECTIONAL: 0,
LIGHT_POINT: 1,
LIGHT_SPOT: 2,
LIGHT_AMBIENT: 3,
// shadows
SHADOW_NONE: 0,
SHADOW_HARD: 1,
SHADOW_SOFT: 2,
// parameter type
PARAM_INT: typeMap.int,
PARAM_INT2: typeMap.ivec2,
PARAM_INT3: typeMap.ivec3,
PARAM_INT4: typeMap.ivec4,
PARAM_FLOAT: typeMap.float,
PARAM_FLOAT2: typeMap.vec2,
PARAM_FLOAT3: typeMap.vec3,
PARAM_FLOAT4: typeMap.vec4,
PARAM_MAT2: typeMap.mat2,
PARAM_MAT3: typeMap.mat3,
PARAM_MAT4: typeMap.mat4,
PARAM_TEXTURE_2D: typeMap.sampler2D,
PARAM_TEXTURE_CUBE: typeMap.samplerCube,
// clear flags
CLEAR_COLOR: 1,
CLEAR_DEPTH: 2,
CLEAR_STENCIL: 4,
CLEAR_SKYBOX: 8,
//
BUFFER_VIEW_INT8: 0,
BUFFER_VIEW_UINT8: 1,
BUFFER_VIEW_INT16: 2,
BUFFER_VIEW_UINT16: 3,
BUFFER_VIEW_INT32: 4,
BUFFER_VIEW_UINT32: 5,
BUFFER_VIEW_FLOAT32: 6,
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,363 @@
const GL_NEAREST = 9728; // gl.NEAREST
const GL_LINEAR = 9729; // gl.LINEAR
const GL_NEAREST_MIPMAP_NEAREST = 9984; // gl.NEAREST_MIPMAP_NEAREST
const GL_LINEAR_MIPMAP_NEAREST = 9985; // gl.LINEAR_MIPMAP_NEAREST
const GL_NEAREST_MIPMAP_LINEAR = 9986; // gl.NEAREST_MIPMAP_LINEAR
const GL_LINEAR_MIPMAP_LINEAR = 9987; // gl.LINEAR_MIPMAP_LINEAR
// const GL_BYTE = 5120; // gl.BYTE
const GL_UNSIGNED_BYTE = 5121; // gl.UNSIGNED_BYTE
// const GL_SHORT = 5122; // gl.SHORT
const GL_UNSIGNED_SHORT = 5123; // gl.UNSIGNED_SHORT
const GL_UNSIGNED_INT = 5125; // gl.UNSIGNED_INT
const GL_FLOAT = 5126; // gl.FLOAT
const GL_UNSIGNED_SHORT_5_6_5 = 33635; // gl.UNSIGNED_SHORT_5_6_5
const GL_UNSIGNED_SHORT_4_4_4_4 = 32819; // gl.UNSIGNED_SHORT_4_4_4_4
const GL_UNSIGNED_SHORT_5_5_5_1 = 32820; // gl.UNSIGNED_SHORT_5_5_5_1
const GL_HALF_FLOAT_OES = 36193; // gl.HALF_FLOAT_OES
const GL_DEPTH_COMPONENT = 6402; // gl.DEPTH_COMPONENT
const GL_ALPHA = 6406; // gl.ALPHA
const GL_RGB = 6407; // gl.RGB
const GL_RGBA = 6408; // gl.RGBA
const GL_LUMINANCE = 6409; // gl.LUMINANCE
const GL_LUMINANCE_ALPHA = 6410; // gl.LUMINANCE_ALPHA
const GL_COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0; // ext.COMPRESSED_RGB_S3TC_DXT1_EXT
const GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1; // ext.COMPRESSED_RGBA_S3TC_DXT1_EXT
const GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2; // ext.COMPRESSED_RGBA_S3TC_DXT3_EXT
const GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3; // ext.COMPRESSED_RGBA_S3TC_DXT5_EXT
const GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00; // ext.COMPRESSED_RGB_PVRTC_4BPPV1_IMG
const GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG = 0x8C01; // ext.COMPRESSED_RGB_PVRTC_2BPPV1_IMG
const GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02; // ext.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG
const GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG = 0x8C03; // ext.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG
const GL_COMPRESSED_RGB_ETC1_WEBGL = 0x8D64; // ext.COMPRESSED_RGB_ETC1_WEBGL
const GL_COMPRESSED_RGB8_ETC2 = 0x9274; // ext.COMPRESSED_RGB8_ETC2
const GL_COMPRESSED_RGBA8_ETC2_EAC = 0x9278; // ext.COMPRESSED_RGBA8_ETC2_EAC
const _filterGL = [
[ GL_NEAREST, GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR ],
[ GL_LINEAR, GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_LINEAR ],
];
const _textureFmtGL = [
// TEXTURE_FMT_RGB_DXT1: 0
{ format: GL_RGB, internalFormat: GL_COMPRESSED_RGB_S3TC_DXT1_EXT, pixelType: null },
// TEXTURE_FMT_RGBA_DXT1: 1
{ format: GL_RGBA, internalFormat: GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, pixelType: null },
// TEXTURE_FMT_RGBA_DXT3: 2
{ format: GL_RGBA, internalFormat: GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, pixelType: null },
// TEXTURE_FMT_RGBA_DXT5: 3
{ format: GL_RGBA, internalFormat: GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, pixelType: null },
// TEXTURE_FMT_RGB_ETC1: 4
{ format: GL_RGB, internalFormat: GL_COMPRESSED_RGB_ETC1_WEBGL, pixelType: null },
// TEXTURE_FMT_RGB_PVRTC_2BPPV1: 5
{ format: GL_RGB, internalFormat: GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG, pixelType: null },
// TEXTURE_FMT_RGBA_PVRTC_2BPPV1: 6
{ format: GL_RGBA, internalFormat: GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG, pixelType: null },
// TEXTURE_FMT_RGB_PVRTC_4BPPV1: 7
{ format: GL_RGB, internalFormat: GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG, pixelType: null },
// TEXTURE_FMT_RGBA_PVRTC_4BPPV1: 8
{ format: GL_RGBA, internalFormat: GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG, pixelType: null },
// TEXTURE_FMT_A8: 9
{ format: GL_ALPHA, internalFormat: GL_ALPHA, pixelType: GL_UNSIGNED_BYTE },
// TEXTURE_FMT_L8: 10
{ format: GL_LUMINANCE, internalFormat: GL_LUMINANCE, pixelType: GL_UNSIGNED_BYTE },
// TEXTURE_FMT_L8_A8: 11
{ format: GL_LUMINANCE_ALPHA, internalFormat: GL_LUMINANCE_ALPHA, pixelType: GL_UNSIGNED_BYTE },
// TEXTURE_FMT_R5_G6_B5: 12
{ format: GL_RGB, internalFormat: GL_RGB, pixelType: GL_UNSIGNED_SHORT_5_6_5 },
// TEXTURE_FMT_R5_G5_B5_A1: 13
{ format: GL_RGBA, internalFormat: GL_RGBA, pixelType: GL_UNSIGNED_SHORT_5_5_5_1 },
// TEXTURE_FMT_R4_G4_B4_A4: 14
{ format: GL_RGBA, internalFormat: GL_RGBA, pixelType: GL_UNSIGNED_SHORT_4_4_4_4 },
// TEXTURE_FMT_RGB8: 15
{ format: GL_RGB, internalFormat: GL_RGB, pixelType: GL_UNSIGNED_BYTE },
// TEXTURE_FMT_RGBA8: 16
{ format: GL_RGBA, internalFormat: GL_RGBA, pixelType: GL_UNSIGNED_BYTE },
// TEXTURE_FMT_RGB16F: 17
{ format: GL_RGB, internalFormat: GL_RGB, pixelType: GL_HALF_FLOAT_OES },
// TEXTURE_FMT_RGBA16F: 18
{ format: GL_RGBA, internalFormat: GL_RGBA, pixelType: GL_HALF_FLOAT_OES },
// TEXTURE_FMT_RGB32F: 19
{ format: GL_RGB, internalFormat: GL_RGB, pixelType: GL_FLOAT },
// TEXTURE_FMT_RGBA32F: 20
{ format: GL_RGBA, internalFormat: GL_RGBA, pixelType: GL_FLOAT },
// TEXTURE_FMT_R32F: 21
{ format: null, internalFormat: null, pixelType: null },
// TEXTURE_FMT_111110F: 22
{ format: null, internalFormat: null, pixelType: null },
// TEXTURE_FMT_SRGB: 23
{ format: null, internalFormat: null, pixelType: null },
// TEXTURE_FMT_SRGBA: 24
{ format: null, internalFormat: null, pixelType: null },
// TEXTURE_FMT_D16: 25
{ format: GL_DEPTH_COMPONENT, internalFormat: GL_DEPTH_COMPONENT, pixelType: GL_UNSIGNED_SHORT },
// TEXTURE_FMT_D32: 26
{ format: GL_DEPTH_COMPONENT, internalFormat: GL_DEPTH_COMPONENT, pixelType: GL_UNSIGNED_INT },
// TEXTURE_FMT_D24S8: 27
{ format: GL_DEPTH_COMPONENT, internalFormat: GL_DEPTH_COMPONENT, pixelType: GL_UNSIGNED_INT },
// TEXTURE_FMT_RGB_ETC2: 28
{ format: GL_RGB, internalFormat: GL_COMPRESSED_RGB8_ETC2, pixelType: null },
// TEXTURE_FMT_RGBA_ETC2: 29
{ format: GL_RGBA, internalFormat: GL_COMPRESSED_RGBA8_ETC2_EAC, pixelType: null },
];
/**
* enums
*/
export const enums = {
// buffer usage
USAGE_STATIC: 35044, // gl.STATIC_DRAW
USAGE_DYNAMIC: 35048, // gl.DYNAMIC_DRAW
USAGE_STREAM: 35040, // gl.STREAM_DRAW
// index buffer format
INDEX_FMT_UINT8: 5121, // gl.UNSIGNED_BYTE
INDEX_FMT_UINT16: 5123, // gl.UNSIGNED_SHORT
INDEX_FMT_UINT32: 5125, // gl.UNSIGNED_INT (OES_element_index_uint)
// vertex attribute semantic
ATTR_POSITION: 'a_position',
ATTR_NORMAL: 'a_normal',
ATTR_TANGENT: 'a_tangent',
ATTR_BITANGENT: 'a_bitangent',
ATTR_WEIGHTS: 'a_weights',
ATTR_JOINTS: 'a_joints',
ATTR_COLOR: 'a_color',
ATTR_COLOR0: 'a_color0',
ATTR_COLOR1: 'a_color1',
ATTR_UV: 'a_uv',
ATTR_UV0: 'a_uv0',
ATTR_UV1: 'a_uv1',
ATTR_UV2: 'a_uv2',
ATTR_UV3: 'a_uv3',
ATTR_UV4: 'a_uv4',
ATTR_UV5: 'a_uv5',
ATTR_UV6: 'a_uv6',
ATTR_UV7: 'a_uv7',
ATTR_TEX_COORD: 'a_texCoord',
ATTR_TEX_COORD1: 'a_texCoord1',
ATTR_TEX_COORD2: 'a_texCoord2',
ATTR_TEX_COORD3: 'a_texCoord3',
ATTR_TEX_COORD4: 'a_texCoord4',
ATTR_TEX_COORD5: 'a_texCoord5',
ATTR_TEX_COORD6: 'a_texCoord6',
ATTR_TEX_COORD7: 'a_texCoord7',
ATTR_TEX_COORD8: 'a_texCoord8',
// vertex attribute type
ATTR_TYPE_INT8: 5120, // gl.BYTE
ATTR_TYPE_UINT8: 5121, // gl.UNSIGNED_BYTE
ATTR_TYPE_INT16: 5122, // gl.SHORT
ATTR_TYPE_UINT16: 5123, // gl.UNSIGNED_SHORT
ATTR_TYPE_INT32: 5124, // gl.INT
ATTR_TYPE_UINT32: 5125, // gl.UNSIGNED_INT
ATTR_TYPE_FLOAT32: 5126, // gl.FLOAT
// texture filter
FILTER_NEAREST: 0,
FILTER_LINEAR: 1,
// texture wrap mode
WRAP_REPEAT: 10497, // gl.REPEAT
WRAP_CLAMP: 33071, // gl.CLAMP_TO_EDGE
WRAP_MIRROR: 33648, // gl.MIRRORED_REPEAT
// texture format
// compress formats
TEXTURE_FMT_RGB_DXT1: 0,
TEXTURE_FMT_RGBA_DXT1: 1,
TEXTURE_FMT_RGBA_DXT3: 2,
TEXTURE_FMT_RGBA_DXT5: 3,
TEXTURE_FMT_RGB_ETC1: 4,
TEXTURE_FMT_RGB_PVRTC_2BPPV1: 5,
TEXTURE_FMT_RGBA_PVRTC_2BPPV1: 6,
TEXTURE_FMT_RGB_PVRTC_4BPPV1: 7,
TEXTURE_FMT_RGBA_PVRTC_4BPPV1: 8,
// normal formats
TEXTURE_FMT_A8: 9,
TEXTURE_FMT_L8: 10,
TEXTURE_FMT_L8_A8: 11,
TEXTURE_FMT_R5_G6_B5: 12,
TEXTURE_FMT_R5_G5_B5_A1: 13,
TEXTURE_FMT_R4_G4_B4_A4: 14,
TEXTURE_FMT_RGB8: 15,
TEXTURE_FMT_RGBA8: 16,
TEXTURE_FMT_RGB16F: 17,
TEXTURE_FMT_RGBA16F: 18,
TEXTURE_FMT_RGB32F: 19,
TEXTURE_FMT_RGBA32F: 20,
TEXTURE_FMT_R32F: 21,
TEXTURE_FMT_111110F: 22,
TEXTURE_FMT_SRGB: 23,
TEXTURE_FMT_SRGBA: 24,
// depth formats
TEXTURE_FMT_D16: 25,
TEXTURE_FMT_D32: 26,
TEXTURE_FMT_D24S8: 27,
// etc2 format
TEXTURE_FMT_RGB_ETC2: 28,
TEXTURE_FMT_RGBA_ETC2: 29,
// depth and stencil function
DS_FUNC_NEVER: 512, // gl.NEVER
DS_FUNC_LESS: 513, // gl.LESS
DS_FUNC_EQUAL: 514, // gl.EQUAL
DS_FUNC_LEQUAL: 515, // gl.LEQUAL
DS_FUNC_GREATER: 516, // gl.GREATER
DS_FUNC_NOTEQUAL: 517, // gl.NOTEQUAL
DS_FUNC_GEQUAL: 518, // gl.GEQUAL
DS_FUNC_ALWAYS: 519, // gl.ALWAYS
// render-buffer format
RB_FMT_RGBA4: 32854, // gl.RGBA4
RB_FMT_RGB5_A1: 32855, // gl.RGB5_A1
RB_FMT_RGB565: 36194, // gl.RGB565
RB_FMT_D16: 33189, // gl.DEPTH_COMPONENT16
RB_FMT_S8: 36168, // gl.STENCIL_INDEX8
RB_FMT_D24S8: 34041, // gl.DEPTH_STENCIL
// blend-equation
BLEND_FUNC_ADD: 32774, // gl.FUNC_ADD
BLEND_FUNC_SUBTRACT: 32778, // gl.FUNC_SUBTRACT
BLEND_FUNC_REVERSE_SUBTRACT: 32779, // gl.FUNC_REVERSE_SUBTRACT
// blend
BLEND_ZERO: 0, // gl.ZERO
BLEND_ONE: 1, // gl.ONE
BLEND_SRC_COLOR: 768, // gl.SRC_COLOR
BLEND_ONE_MINUS_SRC_COLOR: 769, // gl.ONE_MINUS_SRC_COLOR
BLEND_DST_COLOR: 774, // gl.DST_COLOR
BLEND_ONE_MINUS_DST_COLOR: 775, // gl.ONE_MINUS_DST_COLOR
BLEND_SRC_ALPHA: 770, // gl.SRC_ALPHA
BLEND_ONE_MINUS_SRC_ALPHA: 771, // gl.ONE_MINUS_SRC_ALPHA
BLEND_DST_ALPHA: 772, // gl.DST_ALPHA
BLEND_ONE_MINUS_DST_ALPHA: 773, // gl.ONE_MINUS_DST_ALPHA
BLEND_CONSTANT_COLOR: 32769, // gl.CONSTANT_COLOR
BLEND_ONE_MINUS_CONSTANT_COLOR: 32770, // gl.ONE_MINUS_CONSTANT_COLOR
BLEND_CONSTANT_ALPHA: 32771, // gl.CONSTANT_ALPHA
BLEND_ONE_MINUS_CONSTANT_ALPHA: 32772, // gl.ONE_MINUS_CONSTANT_ALPHA
BLEND_SRC_ALPHA_SATURATE: 776, // gl.SRC_ALPHA_SATURATE
// stencil operation
STENCIL_DISABLE: 0, // disable stencil
STENCIL_ENABLE: 1, // enable stencil
STENCIL_INHERIT: 2, // inherit stencil states
STENCIL_OP_KEEP: 7680, // gl.KEEP
STENCIL_OP_ZERO: 0, // gl.ZERO
STENCIL_OP_REPLACE: 7681, // gl.REPLACE
STENCIL_OP_INCR: 7682, // gl.INCR
STENCIL_OP_INCR_WRAP: 34055, // gl.INCR_WRAP
STENCIL_OP_DECR: 7683, // gl.DECR
STENCIL_OP_DECR_WRAP: 34056, // gl.DECR_WRAP
STENCIL_OP_INVERT: 5386, // gl.INVERT
// cull
CULL_NONE: 0,
CULL_FRONT: 1028,
CULL_BACK: 1029,
CULL_FRONT_AND_BACK: 1032,
// primitive type
PT_POINTS: 0, // gl.POINTS
PT_LINES: 1, // gl.LINES
PT_LINE_LOOP: 2, // gl.LINE_LOOP
PT_LINE_STRIP: 3, // gl.LINE_STRIP
PT_TRIANGLES: 4, // gl.TRIANGLES
PT_TRIANGLE_STRIP: 5, // gl.TRIANGLE_STRIP
PT_TRIANGLE_FAN: 6, // gl.TRIANGLE_FAN
};
/**
* @method attrTypeBytes
* @param {ATTR_TYPE_*} attrType
*/
export function attrTypeBytes(attrType) {
if (attrType === enums.ATTR_TYPE_INT8) {
return 1;
} else if (attrType === enums.ATTR_TYPE_UINT8) {
return 1;
} else if (attrType === enums.ATTR_TYPE_INT16) {
return 2;
} else if (attrType === enums.ATTR_TYPE_UINT16) {
return 2;
} else if (attrType === enums.ATTR_TYPE_INT32) {
return 4;
} else if (attrType === enums.ATTR_TYPE_UINT32) {
return 4;
} else if (attrType === enums.ATTR_TYPE_FLOAT32) {
return 4;
}
console.warn(`Unknown ATTR_TYPE: ${attrType}`);
return 0;
}
/**
* @method glFilter
* @param {WebGLContext} gl
* @param {FILTER_*} filter
* @param {FILTER_*} mipFilter
*/
export function glFilter(gl, filter, mipFilter = -1) {
let result = _filterGL[filter][mipFilter+1];
if (result === undefined) {
console.warn(`Unknown FILTER: ${filter}`);
return mipFilter === -1 ? gl.LINEAR : gl.LINEAR_MIPMAP_LINEAR;
}
return result;
}
/**
* @method glTextureFmt
* @param {TEXTURE_FMT_*} fmt
*/
export function glTextureFmt(fmt) {
let result = _textureFmtGL[fmt];
if (result === undefined) {
console.warn(`Unknown TEXTURE_FMT: ${fmt}`);
return _textureFmtGL[enums.TEXTURE_FMT_RGBA8];
}
return result;
}

View File

@@ -0,0 +1,45 @@
export default class FrameBuffer {
/**
* @constructor
* @param {Device} device
* @param {Number} width
* @param {Number} height
* @param {Object} options
* @param {Array} options.colors
* @param {RenderBuffer|Texture2D|TextureCube} options.depth
* @param {RenderBuffer|Texture2D|TextureCube} options.stencil
* @param {RenderBuffer|Texture2D|TextureCube} options.depthStencil
*/
constructor(device, width, height, options) {
this._device = device;
this._width = width;
this._height = height;
this._colors = options.colors || [];
this._depth = options.depth || null;
this._stencil = options.stencil || null;
this._depthStencil = options.depthStencil || null;
this._glID = device._gl.createFramebuffer();
}
/**
* @method destroy
*/
destroy() {
if (this._glID === null) {
console.error('The frame-buffer already destroyed');
return;
}
const gl = this._device._gl;
gl.deleteFramebuffer(this._glID);
this._glID = null;
}
getHandle() {
return this._glID;
}
}

View File

@@ -0,0 +1,104 @@
import { enums } from './enums';
const BYTES_PER_INDEX = {
[enums.INDEX_FMT_UINT8]: 1,
[enums.INDEX_FMT_UINT16]: 2,
[enums.INDEX_FMT_UINT32]: 4,
}
class IndexBuffer {
/**
* @constructor
* @param {Device} device
* @param {INDEX_FMT_*} format
* @param {USAGE_*} usage
* @param {ArrayBuffer | Uint8Array} data
*/
constructor(device, format, usage, data) {
this._device = device;
this._format = format;
this._usage = usage;
this._bytesPerIndex = BYTES_PER_INDEX[format];
this._bytes = data.byteLength;
this._numIndices = this._bytes / this._bytesPerIndex;
this._needExpandDataStore = true;
// update
this._glID = device._gl.createBuffer();
this.update(0, data);
// stats
device._stats.ib += this._bytes;
}
/**
* @method destroy
*/
destroy() {
if (this._glID === -1) {
console.error('The buffer already destroyed');
return;
}
let gl = this._device._gl;
gl.deleteBuffer(this._glID);
this._device._stats.ib -= this.bytes;
this._glID = -1;
}
/**
* @method update
* @param {Number} byteOffset
* @param {ArrayBuffer} data
*/
update(byteOffset, data) {
if (this._glID === -1) {
console.error('The buffer is destroyed');
return;
}
if (data.byteLength === 0) return;
// Need to create new buffer object when bytes exceed
if (byteOffset + data.byteLength > this._bytes) {
if (byteOffset) {
// Lost data between [0, byteOffset] which is need for new buffer
console.error('Failed to update data, bytes exceed.');
return;
}
else {
this._needExpandDataStore = true;
this._bytes = byteOffset + data.byteLength;
this._numIndices = this._bytes / this._bytesPerIndex;
}
}
/** @type{WebGLRenderingContext} */
let gl = this._device._gl;
let glUsage = this._usage;
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._glID);
if (this._needExpandDataStore) {
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, glUsage);
this._needExpandDataStore = false;
}
else {
gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, byteOffset, data);
}
this._device._restoreIndexBuffer();
}
get count () {
return this._numIndices;
}
setUsage (usage) {
this._usage = usage;
}
}
IndexBuffer.BYTES_PER_INDEX = BYTES_PER_INDEX;
export default IndexBuffer;

View File

@@ -0,0 +1,46 @@
import {
enums,
attrTypeBytes,
glFilter,
glTextureFmt,
} from './enums';
let gfx = null;
if (CC_JSB && CC_NATIVERENDERER) {
gfx = window.gfx;
} else {
let VertexFormat = require('./vertex-format');
let IndexBuffer = require('./index-buffer');
let VertexBuffer = require('./vertex-buffer');
let Program = require('./program');
let Texture = require('./texture');
let Texture2D = require('./texture-2d');
let TextureCube = require('./texture-cube');
let RenderBuffer = require('./render-buffer');
let FrameBuffer = require('./frame-buffer');
let Device = require('./device');
gfx = {
// classes
VertexFormat,
IndexBuffer,
VertexBuffer,
Program,
Texture,
Texture2D,
TextureCube,
RenderBuffer,
FrameBuffer,
Device,
// functions
attrTypeBytes,
glFilter,
glTextureFmt,
};
Object.assign(gfx, enums);
}
export default gfx;
cc.gfx = gfx;

View File

@@ -0,0 +1,3 @@
export function isPow2(v) {
return !(v & (v - 1)) && (!!v);
}

View File

@@ -0,0 +1,172 @@
let _genID = 0;
function _parseError(out, type, errorLog) {
if(!errorLog){
return;
}
errorLog.split('\n').forEach(msg => {
if (msg.length < 5) {
return;
}
let parts = /^ERROR:\s+(\d+):(\d+):\s*(.*)$/.exec(msg);
if (parts) {
out.push({
type: type,
fileID: parts[1] | 0,
line: parts[2] | 0,
message: parts[3].trim()
})
} else if (msg.length > 0) {
out.push({
type: type,
fileID: -1,
line: 0,
message: msg
});
}
});
}
export default class Program {
/**
* @param {ef.GraphicsDevice} device - graphic device
* @param {object} options - shader definition
* @param {string} options.vert - vertex shader source code
* @param {string} options.frag - fragment shader shader source code
* @example
* let prog = new Program(device, {
* vert: `
* attribute vec3 a_position;
* void main() {
* gl_Position = vec4( a_position, 1.0 );
* }
* `,
* frag: `
* precision mediump float;
* void main() {
* gl_FragColor = vec4( 1.0, 1.0, 1.0, 1.0 );
* }
* `
* });
*/
constructor(device, options) {
this._device = device;
// stores gl information: { location, type }
this._attributes = [];
this._uniforms = [];
this._samplers = [];
this._errors = [];
this._linked = false;
this._vertSource = options.vert;
this._fragSource = options.frag;
this._glID = null;
this._id = _genID++;
}
get id() {
return this._id;
}
link() {
if (this._linked) {
return;
}
let gl = this._device._gl;
let vertShader = _createShader(gl, gl.VERTEX_SHADER, this._vertSource);
let fragShader = _createShader(gl, gl.FRAGMENT_SHADER, this._fragSource);
let program = gl.createProgram();
gl.attachShader(program, vertShader);
gl.attachShader(program, fragShader);
gl.linkProgram(program);
let failed = false;
let errors = this._errors;
if (!gl.getShaderParameter(vertShader, gl.COMPILE_STATUS)) {
_parseError(errors, 'vs', gl.getShaderInfoLog(vertShader));
failed = true;
}
if (!gl.getShaderParameter(fragShader, gl.COMPILE_STATUS)) {
_parseError(errors, 'fs', gl.getShaderInfoLog(fragShader));
failed = true;
}
gl.deleteShader(vertShader);
gl.deleteShader(fragShader);
if (failed) {
return errors;
}
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
errors.push({info: `Failed to link shader program: ${gl.getProgramInfoLog(program)}`});
return errors;
}
this._glID = program;
// parse attribute
let numAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
for (let i = 0; i < numAttributes; ++i) {
let info = gl.getActiveAttrib(program, i);
let location = gl.getAttribLocation(program, info.name);
this._attributes.push({
name: info.name,
location: location,
type: info.type,
});
}
// parse uniform
let numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
for (let i = 0; i < numUniforms; ++i) {
let info = gl.getActiveUniform(program, i);
let name = info.name;
let location = gl.getUniformLocation(program, name);
let isArray = name.substr(name.length - 3) === '[0]';
if (isArray) {
name = name.substr(0, name.length - 3);
}
let uniform = {
name: name,
location: location,
type: info.type,
size: isArray ? info.size : undefined, // used when uniform is an array
};
this._uniforms.push(uniform);
}
this._linked = true;
}
destroy() {
let gl = this._device._gl;
gl.deleteProgram(this._glID);
this._linked = false;
this._glID = null;
this._attributes = [];
this._uniforms = [];
this._samplers = [];
}
}
// ====================
// internal
// ====================
function _createShader(gl, type, src) {
let shader = gl.createShader(type);
gl.shaderSource(shader, src);
gl.compileShader(shader);
return shader;
}

View File

@@ -0,0 +1,43 @@
export default class RenderBuffer {
/**
* @constructor
* @param {Device} device
* @param {RB_FMT_*} format
* @param {Number} width
* @param {Number} height
*/
constructor(device, format, width, height) {
this._device = device;
this._format = format;
this._glID = device._gl.createRenderbuffer();
this.update(width, height);
}
update (width, height) {
this._width = width;
this._height = height;
const gl = this._device._gl;
gl.bindRenderbuffer(gl.RENDERBUFFER, this._glID);
gl.renderbufferStorage(gl.RENDERBUFFER, this._format, width, height);
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
}
/**
* @method destroy
*/
destroy() {
if (this._glID === null) {
console.error('The render-buffer already destroyed');
return;
}
const gl = this._device._gl;
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
gl.deleteRenderbuffer(this._glID);
this._glID = null;
}
}

View File

@@ -0,0 +1,133 @@
import { enums } from './enums';
const _default = {
// blend
blend: false,
blendSep: false,
blendColor: 0xffffffff,
blendEq: enums.BLEND_FUNC_ADD,
blendAlphaEq: enums.BLEND_FUNC_ADD,
blendSrc: enums.BLEND_ONE,
blendDst: enums.BLEND_ZERO,
blendSrcAlpha: enums.BLEND_ONE,
blendDstAlpha: enums.BLEND_ZERO,
// depth
depthTest: false,
depthWrite: false,
depthFunc: enums.DS_FUNC_LESS,
// stencil
stencilTest: false,
stencilSep: false,
stencilFuncFront: enums.DS_FUNC_ALWAYS,
stencilRefFront: 0,
stencilMaskFront: 0xff,
stencilFailOpFront: enums.STENCIL_OP_KEEP,
stencilZFailOpFront: enums.STENCIL_OP_KEEP,
stencilZPassOpFront: enums.STENCIL_OP_KEEP,
stencilWriteMaskFront: 0xff,
stencilFuncBack: enums.DS_FUNC_ALWAYS,
stencilRefBack: 0,
stencilMaskBack: 0xff,
stencilFailOpBack: enums.STENCIL_OP_KEEP,
stencilZFailOpBack: enums.STENCIL_OP_KEEP,
stencilZPassOpBack: enums.STENCIL_OP_KEEP,
stencilWriteMaskBack: 0xff,
// cull-mode
cullMode: enums.CULL_BACK,
// primitive-type
primitiveType: enums.PT_TRIANGLES,
// bindings
maxStream: -1,
vertexBuffers: [],
vertexBufferOffsets: [],
indexBuffer: null,
maxTextureSlot: -1,
textureUnits: [],
program: null,
};
export default class State {
constructor(device) {
// bindings
this.vertexBuffers = new Array(device._caps.maxVertexStreams);
this.vertexBufferOffsets = new Array(device._caps.maxVertexStreams);
this.textureUnits = new Array(device._caps.maxTextureUnits);
this.set(_default);
}
static initDefault(device) {
_default.vertexBuffers = new Array(device._caps.maxVertexStreams);
_default.vertexBufferOffsets = new Array(device._caps.maxVertexStreams);
_default.textureUnits = new Array(device._caps.maxTextureUnits);
}
reset () {
this.set(_default);
}
set (cpy) {
// blending
this.blend = cpy.blend;
this.blendSep = cpy.blendSep;
this.blendColor = cpy.blendColor;
this.blendEq = cpy.blendEq;
this.blendAlphaEq = cpy.blendAlphaEq;
this.blendSrc = cpy.blendSrc;
this.blendDst = cpy.blendDst;
this.blendSrcAlpha = cpy.blendSrcAlpha;
this.blendDstAlpha = cpy.blendDstAlpha;
// depth
this.depthTest = cpy.depthTest;
this.depthWrite = cpy.depthWrite;
this.depthFunc = cpy.depthFunc;
// stencil
this.stencilTest = cpy.stencilTest;
this.stencilSep = cpy.stencilSep;
this.stencilFuncFront = cpy.stencilFuncFront;
this.stencilRefFront = cpy.stencilRefFront;
this.stencilMaskFront = cpy.stencilMaskFront;
this.stencilFailOpFront = cpy.stencilFailOpFront;
this.stencilZFailOpFront = cpy.stencilZFailOpFront;
this.stencilZPassOpFront = cpy.stencilZPassOpFront;
this.stencilWriteMaskFront = cpy.stencilWriteMaskFront;
this.stencilFuncBack = cpy.stencilFuncBack;
this.stencilRefBack = cpy.stencilRefBack;
this.stencilMaskBack = cpy.stencilMaskBack;
this.stencilFailOpBack = cpy.stencilFailOpBack;
this.stencilZFailOpBack = cpy.stencilZFailOpBack;
this.stencilZPassOpBack = cpy.stencilZPassOpBack;
this.stencilWriteMaskBack = cpy.stencilWriteMaskBack;
// cull-mode
this.cullMode = cpy.cullMode;
// primitive-type
this.primitiveType = cpy.primitiveType;
// buffer bindings
this.maxStream = cpy.maxStream;
for (let i = 0; i < cpy.vertexBuffers.length; ++i) {
this.vertexBuffers[i] = cpy.vertexBuffers[i];
}
for (let i = 0; i < cpy.vertexBufferOffsets.length; ++i) {
this.vertexBufferOffsets[i] = cpy.vertexBufferOffsets[i];
}
this.indexBuffer = cpy.indexBuffer;
// texture bindings
this.maxTextureSlot = cpy.maxTextureSlot;
for (let i = 0; i < cpy.textureUnits.length; ++i) {
this.textureUnits[i] = cpy.textureUnits[i];
}
this.program = cpy.program;
}
}

View File

@@ -0,0 +1,330 @@
// @ts-check
import Texture from './texture';
import { enums, glFilter, glTextureFmt } from './enums';
import { isPow2 } from './misc';
/**
* @typedef {HTMLImageElement | HTMLCanvasElement} HTMLImageSource
* @typedef {HTMLImageSource | ArrayBufferView} ImageSource
* @typedef {{width?: number, height?: number, minFilter?: number, magFilter?: number, mipFilter?: number, wrapS?: number, wrapT?: number, format?: number, genMipmaps?: boolean, images?: ImageSource[], image?: ImageSource, flipY?: boolean, premultiplyAlpha?: boolean, anisotropy?: number}} TextureUpdateOpts
* @typedef {import("../gfx/device").default} Device
*/
export default class Texture2D extends Texture {
/**
* @constructor
* @param {Device} device
* @param {TextureUpdateOpts} options
*/
constructor(device, options) {
super(device);
let gl = this._device._gl;
this._target = gl.TEXTURE_2D;
this._glID = gl.createTexture();
// always alloc texture in GPU when we create it.
options.images = options.images || [null];
this.update(options);
}
/**
* @method update
* @param {TextureUpdateOpts} options
*/
update(options) {
let gl = this._device._gl;
let genMipmaps = this._genMipmap;
if (options) {
if (options.width !== undefined) {
this._width = options.width;
}
if (options.height !== undefined) {
this._height = options.height;
}
if (options.anisotropy !== undefined) {
this._anisotropy = options.anisotropy;
}
if (options.minFilter !== undefined) {
this._minFilter = options.minFilter;
}
if (options.magFilter !== undefined) {
this._magFilter = options.magFilter;
}
if (options.mipFilter !== undefined) {
this._mipFilter = options.mipFilter;
}
if (options.wrapS !== undefined) {
this._wrapS = options.wrapS;
}
if (options.wrapT !== undefined) {
this._wrapT = options.wrapT;
}
if (options.format !== undefined) {
this._format = options.format;
this._compressed =
(this._format >= enums.TEXTURE_FMT_RGB_DXT1 && this._format <= enums.TEXTURE_FMT_RGBA_PVRTC_4BPPV1) ||
(this._format >= enums.TEXTURE_FMT_RGB_ETC2 && this._format <= enums.TEXTURE_FMT_RGBA_ETC2)
;
}
// check if generate mipmap
if (options.genMipmaps !== undefined) {
this._genMipmap = options.genMipmaps;
genMipmaps = options.genMipmaps;
}
let maxSize = this._device.caps.maxTextureSize || Number.MAX_VALUE;
let textureMaxSize = Math.max(options.width || 0, options.height || 0);
if (maxSize < textureMaxSize)
console.warn(`The current texture size ${textureMaxSize} exceeds the maximum size [${maxSize}] supported on the device.`);
if (options.images !== undefined) {
if (options.images.length > 1) {
genMipmaps = false;
let maxLength = options.width > options.height ? options.width : options.height;
if (maxLength >> (options.images.length - 1) !== 1) {
console.error('texture-2d mipmap is invalid, should have a 1x1 mipmap.');
}
}
}
}
// NOTE: get pot after this._width, this._height has been assigned.
let pot = isPow2(this._width) && isPow2(this._height);
if (!pot) {
genMipmaps = false;
}
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this._glID);
if (options.images !== undefined && options.images.length > 0) {
this._setMipmap(options.images, options.flipY, options.premultiplyAlpha);
if (options.images.length > 1) this._genMipmap = true;
}
if (genMipmaps) {
gl.hint(gl.GENERATE_MIPMAP_HINT, gl.NICEST);
gl.generateMipmap(gl.TEXTURE_2D);
this._genMipmap = true;
}
this._setTexInfo();
this._device._restoreTexture(0);
}
/**
* @method updateSubImage
* @param {Object} options
* @param {Number} options.x
* @param {Number} options.y
* @param {Number} options.width
* @param {Number} options.height
* @param {Number} options.level
* @param {HTMLCanvasElement | HTMLImageElement | HTMLVideoElement | ArrayBufferView} options.image
* @param {Boolean} options.flipY
* @param {Boolean} options.premultiplyAlpha
*/
updateSubImage(options) {
let gl = this._device._gl;
let glFmt = glTextureFmt(this._format);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this._glID);
this._setSubImage(glFmt, options);
this._device._restoreTexture(0);
}
/**
* @method updateImage
* @param {Object} options
* @param {Number} options.width
* @param {Number} options.height
* @param {Number} options.level
* @param {HTMLCanvasElement | HTMLImageElement | HTMLVideoElement | ArrayBufferView} options.image
* @param {Boolean} options.flipY
* @param {Boolean} options.premultiplyAlpha
*/
updateImage(options) {
let gl = this._device._gl;
let glFmt = glTextureFmt(this._format);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this._glID);
this._setImage(glFmt, options);
this._device._restoreTexture(0);
}
_setSubImage(glFmt, options) {
let gl = this._device._gl;
let flipY = options.flipY;
let premultiplyAlpha = options.premultiplyAlpha;
let img = options.image;
if (img && !ArrayBuffer.isView(img) && !(img instanceof ArrayBuffer)) {
if (flipY === undefined) {
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
} else {
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
}
if (premultiplyAlpha === undefined) {
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
} else {
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, premultiplyAlpha);
}
gl.texSubImage2D(gl.TEXTURE_2D, options.level, options.x, options.y, glFmt.format, glFmt.pixelType, img);
} else {
if (flipY === undefined) {
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
} else {
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
}
if (premultiplyAlpha === undefined) {
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
} else {
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, premultiplyAlpha);
}
if (this._compressed) {
gl.compressedTexSubImage2D(gl.TEXTURE_2D,
options.level,
options.x,
options.y,
options.width,
options.height,
glFmt.format,
img
);
} else {
gl.texSubImage2D(
gl.TEXTURE_2D,
options.level,
options.x,
options.y,
options.width,
options.height,
glFmt.format,
glFmt.pixelType,
img
);
}
}
}
_setImage(glFmt, options) {
let gl = this._device._gl;
let flipY = options.flipY;
let premultiplyAlpha = options.premultiplyAlpha;
let img = options.image;
if (img && !ArrayBuffer.isView(img) && !(img instanceof ArrayBuffer)) {
if (flipY === undefined) {
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
} else {
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
}
if (premultiplyAlpha === undefined) {
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
} else {
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, premultiplyAlpha);
}
gl.texImage2D(
gl.TEXTURE_2D,
options.level,
glFmt.internalFormat,
glFmt.format,
glFmt.pixelType,
img
);
} else {
if (flipY === undefined) {
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
} else {
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
}
if (premultiplyAlpha === undefined) {
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
} else {
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, premultiplyAlpha);
}
if (this._compressed) {
gl.compressedTexImage2D(
gl.TEXTURE_2D,
options.level,
glFmt.internalFormat,
options.width,
options.height,
0,
img
);
} else {
gl.texImage2D(
gl.TEXTURE_2D,
options.level,
glFmt.internalFormat,
options.width,
options.height,
0,
glFmt.format,
glFmt.pixelType,
img
);
}
}
}
_setMipmap(images, flipY, premultiplyAlpha) {
let glFmt = glTextureFmt(this._format);
let options = {
width: this._width,
height: this._height,
flipY: flipY,
premultiplyAlpha: premultiplyAlpha,
level: 0,
image: null
};
for (let i = 0; i < images.length; ++i) {
options.level = i;
options.width = this._width >> i;
options.height = this._height >> i;
options.image = images[i];
this._setImage(glFmt, options);
}
}
_setTexInfo() {
let gl = this._device._gl;
let pot = isPow2(this._width) && isPow2(this._height);
// WebGL1 doesn't support all wrap modes with NPOT textures
if (!pot && (this._wrapS !== enums.WRAP_CLAMP || this._wrapT !== enums.WRAP_CLAMP)) {
console.warn('WebGL1 doesn\'t support all wrap modes with NPOT textures');
this._wrapS = enums.WRAP_CLAMP;
this._wrapT = enums.WRAP_CLAMP;
}
let mipFilter = this._genMipmap ? this._mipFilter : -1;
if (!pot && mipFilter !== -1) {
console.warn('NPOT textures do not support mipmap filter');
mipFilter = -1;
}
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, glFilter(gl, this._minFilter, mipFilter));
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, glFilter(gl, this._magFilter, -1));
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, this._wrapS);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, this._wrapT);
let ext = this._device.ext('EXT_texture_filter_anisotropic');
if (ext) {
gl.texParameteri(gl.TEXTURE_2D, ext.TEXTURE_MAX_ANISOTROPY_EXT, this._anisotropy);
}
}
}

View File

@@ -0,0 +1,338 @@
import Texture from './texture';
import { enums, glFilter, glTextureFmt } from './enums';
import { isPow2 } from './misc';
export default class TextureCube extends Texture {
/**
* @constructor
* @param {Device} device
* @param {Object} options
* @param {Array} options.images
* @param {Boolean} options.genMipmaps
* @param {Number} options.width
* @param {Number} options.height
* @param {TEXTURE_FMT_*} options.format
* @param {Number} options.anisotropy
* @param {FILTER_*} options.minFilter
* @param {FILTER_*} options.magFilter
* @param {FILTER_*} options.mipFilter
* @param {WRAP_*} options.wrapS
* @param {WRAP_*} options.wrapT
* @param {WRAP_*} options.wrapR
* @param {Boolean} options.flipY
* @param {Boolean} options.premultiplyAlpha
*/
constructor(device, options) {
super(device);
let gl = this._device._gl;
this._target = gl.TEXTURE_CUBE_MAP;
this._glID = gl.createTexture();
this.update(options);
}
/**
* @method update
* @param {Object} options
* @param {Array} options.images
* @param {Boolean} options.genMipmaps
* @param {Number} options.width
* @param {Number} options.height
* @param {TEXTURE_FMT_*} options.format
* @param {Number} options.anisotropy
* @param {FILTER_*} options.minFilter
* @param {FILTER_*} options.magFilter
* @param {FILTER_*} options.mipFilter
* @param {WRAP_*} options.wrapS
* @param {WRAP_*} options.wrapT
* @param {WRAP_*} options.wrapR
* @param {Boolean} options.flipY
* @param {Boolean} options.premultiplyAlpha
*/
update(options) {
let gl = this._device._gl;
let genMipmaps = this._genMipmaps;
if (options) {
if (options.width !== undefined) {
this._width = options.width;
}
if (options.height !== undefined) {
this._height = options.height;
}
if (options.anisotropy !== undefined) {
this._anisotropy = options.anisotropy;
}
if (options.minFilter !== undefined) {
this._minFilter = options.minFilter;
}
if (options.magFilter !== undefined) {
this._magFilter = options.magFilter;
}
if (options.mipFilter !== undefined) {
this._mipFilter = options.mipFilter;
}
if (options.wrapS !== undefined) {
this._wrapS = options.wrapS;
}
if (options.wrapT !== undefined) {
this._wrapT = options.wrapT;
}
// wrapR available in webgl2
// if (options.wrapR !== undefined) {
// this._wrapR = options.wrapR;
// }
if (options.format !== undefined) {
this._format = options.format;
this._compressed =
(this._format >= enums.TEXTURE_FMT_RGB_DXT1 && this._format <= enums.TEXTURE_FMT_RGBA_PVRTC_4BPPV1) ||
(this._format >= enums.TEXTURE_FMT_RGB_ETC2 && this._format <= enums.TEXTURE_FMT_RGBA_ETC2)
;
}
// check if generate mipmap
if (options.genMipmaps !== undefined) {
this._genMipmaps = options.genMipmaps;
genMipmaps = options.genMipmaps;
}
if (options.images !== undefined) {
if (options.images.length > 1) {
genMipmaps = false;
if (options.width !== options.height) {
console.warn('texture-cube width and height should be identical.');
}
if (options.width >> (options.images.length - 1) !== 1) {
console.error('texture-cube mipmap is invalid. please set mipmap as 1x1, 2x2, 4x4 ... nxn');
}
}
}
}
// NOTE: get pot after this._width, this._height has been assigned.
let pot = isPow2(this._width) && isPow2(this._height);
if (!pot) {
genMipmaps = false;
}
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_CUBE_MAP, this._glID);
if (options.images !== undefined && options.images.length > 0) {
this._setMipmap(options.images, options.flipY, options.premultiplyAlpha);
if (options.images.length > 1) this._genMipmaps = true;
}
if (genMipmaps) {
gl.hint(gl.GENERATE_MIPMAP_HINT, gl.NICEST);
gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
this._genMipmaps = true;
}
this._setTexInfo();
this._device._restoreTexture(0);
}
/**
* @method updateSubImage
* @param {Object} options
* @param {Number} options.x
* @param {Number} options.y
* @param {Number} options.width
* @param {Number} options.height
* @param {Number} options.level
* @param {Number} options.faceIndex
* @param {HTMLCanvasElement | HTMLImageElement | HTMLVideoElement | ArrayBufferView} options.image
* @param {Boolean} options.flipY
* @param {Boolean} options.premultiplyAlpha
*/
updateSubImage(options) {
let gl = this._device._gl;
let glFmt = glTextureFmt(this._format);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_CUBE_MAP, this._glID);
this._setSubImage(glFmt, options);
this._device._restoreTexture(0);
}
/**
* @method updateImage
* @param {Object} options
* @param {Number} options.width
* @param {Number} options.height
* @param {Number} options.level
* @param {Number} options.faceIndex
* @param {HTMLCanvasElement | HTMLImageElement | HTMLVideoElement | ArrayBufferView} options.image
* @param {Boolean} options.flipY
* @param {Boolean} options.premultiplyAlpha
*/
updateImage(options) {
let gl = this._device._gl;
let glFmt = glTextureFmt(this._format);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_CUBE_MAP, this._glID);
this._setImage(glFmt, options);
this._device._restoreTexture(0);
}
_setSubImage(glFmt, options) {
let gl = this._device._gl;
let flipY = options.flipY;
let premultiplyAlpha = options.premultiplyAlpha;
let faceIndex = options.faceIndex;
let img = options.image;
if (flipY === undefined) {
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
} else {
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
}
if (premultiplyAlpha === undefined) {
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
} else {
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, premultiplyAlpha);
}
if (img && !ArrayBuffer.isView(img) && !(img instanceof ArrayBuffer)) {
gl.texSubImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, options.level, options.x, options.y, glFmt.format, glFmt.pixelType, img);
} else {
if (this._compressed) {
gl.compressedTexSubImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex,
options.level,
options.x,
options.y,
options.width,
options.height,
glFmt.format,
img
);
} else {
gl.texSubImage2D(
gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex,
options.level,
options.x,
options.y,
options.width,
options.height,
glFmt.format,
glFmt.pixelType,
img
);
}
}
}
_setImage(glFmt, options) {
let gl = this._device._gl;
let flipY = options.flipY;
let premultiplyAlpha = options.premultiplyAlpha;
let faceIndex = options.faceIndex;
let img = options.image;
if (flipY === undefined) {
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
} else {
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
}
if (premultiplyAlpha === undefined) {
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
} else {
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, premultiplyAlpha);
}
if (img && !ArrayBuffer.isView(img) && !(img instanceof ArrayBuffer)) {
gl.texImage2D(
gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex,
options.level,
glFmt.internalFormat,
glFmt.format,
glFmt.pixelType,
img
);
} else {
if (this._compressed) {
gl.compressedTexImage2D(
gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex,
options.level,
glFmt.internalFormat,
options.width,
options.height,
0,
img
);
} else {
gl.texImage2D(
gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex,
options.level,
glFmt.internalFormat,
options.width,
options.height,
0,
glFmt.format,
glFmt.pixelType,
img
);
}
}
}
// levelImages = [imagePosX, imageNegX, imagePosY, imageNegY, imagePosZ, imageNegz]
// images = [levelImages0, levelImages1, ...]
_setMipmap(images, flipY, premultiplyAlpha) {
let glFmt = glTextureFmt(this._format);
let options = {
width: this._width,
height: this._height,
faceIndex: 0,
flipY: flipY,
premultiplyAlpha: premultiplyAlpha,
level: 0,
image: null
};
for (let i = 0; i < images.length; ++i) {
let levelImages = images[i];
options.level = i;
options.width = this._width >> i;
options.height = this._height >> i;
for (let face = 0; face < 6; ++face) {
options.faceIndex = face;
options.image = levelImages[face];
this._setImage(glFmt, options);
}
}
}
_setTexInfo() {
let gl = this._device._gl;
let pot = isPow2(this._width) && isPow2(this._height);
// WebGL1 doesn't support all wrap modes with NPOT textures
if (!pot && (this._wrapS !== enums.WRAP_CLAMP || this._wrapT !== enums.WRAP_CLAMP)) {
console.warn('WebGL1 doesn\'t support all wrap modes with NPOT textures');
this._wrapS = enums.WRAP_CLAMP;
this._wrapT = enums.WRAP_CLAMP;
}
let mipFilter = this._genMipmaps ? this._mipFilter : -1;
if (!pot && mipFilter !== -1) {
console.warn('NPOT textures do not support mipmap filter');
mipFilter = -1;
}
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, glFilter(gl, this._minFilter, mipFilter));
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, glFilter(gl, this._magFilter, -1));
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, this._wrapS);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, this._wrapT);
// wrapR available in webgl2
// gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_R, this._wrapR);
let ext = this._device.ext('EXT_texture_filter_anisotropic');
if (ext) {
gl.texParameteri(gl.TEXTURE_CUBE_MAP, ext.TEXTURE_MAX_ANISOTROPY_EXT, this._anisotropy);
}
}
}

View File

@@ -0,0 +1,56 @@
import { enums } from './enums';
/**
* @type {WebGLTexture}
*/
const _nullWebGLTexture = null;
let _textureID = 0;
/**
* @typedef {import("../gfx/device").default} Device
*/
export default class Texture {
/**
* @param {Device} device
*/
constructor(device) {
this._device = device;
this._width = 4;
this._height = 4;
this._genMipmaps = false;
this._compressed = false;
this._anisotropy = 1;
this._minFilter = enums.FILTER_LINEAR;
this._magFilter = enums.FILTER_LINEAR;
this._mipFilter = enums.FILTER_LINEAR;
this._wrapS = enums.WRAP_REPEAT;
this._wrapT = enums.WRAP_REPEAT;
// wrapR available in webgl2
// this._wrapR = enums.WRAP_REPEAT;
this._format = enums.TEXTURE_FMT_RGBA8;
this._target = -1;
this._id = _textureID++;
}
/**
* @method destroy
*/
destroy() {
if (this._glID === _nullWebGLTexture) {
console.error('The texture already destroyed');
return;
}
let gl = this._device._gl;
gl.deleteTexture(this._glID);
this._device._stats.tex -= this.bytes;
this._glID = _nullWebGLTexture;
}
}

View File

@@ -0,0 +1,99 @@
import { enums } from './enums';
class VertexBuffer {
/**
* @constructor
* @param {Device} device
* @param {VertexFormat} format
* @param {USAGE_*} usage
* @param {ArrayBuffer | Uint8Array} data
*/
constructor(device, format, usage, data) {
this._device = device;
this._format = format;
this._usage = usage;
this._bytesPerVertex = this._format._bytes;
this._bytes = data.byteLength;
this._numVertices = this._bytes / this._bytesPerVertex;
this._needExpandDataStore = true;
// update
this._glID = device._gl.createBuffer();
this.update(0, data);
// stats
device._stats.vb += this._bytes;
}
/**
* @method destroy
*/
destroy() {
if (this._glID === -1) {
console.error('The buffer already destroyed');
return;
}
let gl = this._device._gl;
gl.deleteBuffer(this._glID);
this._device._stats.vb -= this.bytes;
this._glID = -1;
}
/**
* @method update
* @param {Number} byteOffset
* @param {ArrayBuffer} data
*/
update(byteOffset, data) {
if (this._glID === -1) {
console.error('The buffer is destroyed');
return;
}
if (data.byteLength === 0) return;
// Need to create new buffer object when bytes exceed
if (byteOffset + data.byteLength > this._bytes) {
if (byteOffset) {
// Lost data between [0, byteOffset] which is need for new buffer
console.error('Failed to update data, bytes exceed.');
return;
}
else {
this._needExpandDataStore = true;
this._bytes = byteOffset + data.byteLength;
this._numVertices = this._bytes / this._bytesPerVertex;
}
}
let gl = this._device._gl;
let glUsage = this._usage;
gl.bindBuffer(gl.ARRAY_BUFFER, this._glID);
if (this._needExpandDataStore) {
gl.bufferData(gl.ARRAY_BUFFER, data, glUsage);
this._needExpandDataStore = false;
}
else {
gl.bufferSubData(gl.ARRAY_BUFFER, byteOffset, data);
}
gl.bindBuffer(gl.ARRAY_BUFFER, null);
}
get count () {
return this._numVertices;
}
getFormat (name) {
return this._format.element(name);
}
setUsage (usage) {
this._usage = usage;
}
}
export default VertexBuffer;

View File

@@ -0,0 +1,70 @@
import { attrTypeBytes } from './enums';
import murmurhash2 from '../murmurhash2_gc';
// ====================
// exports
// ====================
export default class VertexFormat {
/**
* @constructor
* @param {Array} infos
*
* @example
* let vertexFmt = new VertexFormat([
* { name: gfx.ATTR_POSITION, type: gfx.ATTR_TYPE_FLOAT32, num: 3 },
* { name: gfx.ATTR_UV0, type: gfx.ATTR_TYPE_FLOAT32, num: 2 },
* { name: gfx.ATTR_COLOR, type: gfx.ATTR_TYPE_FLOAT32, num: 4, normalize: true },
* ])
*/
constructor(infos) {
this._attr2el = {};
this._elements = [];
this._bytes = 0;
let hash = "";
for (let i = 0, len = infos.length; i < len; ++i) {
let info = infos[i];
let el = {
name: info.name,
offset: this._bytes,
stride: 0,
stream: -1,
type: info.type,
num: info.num,
normalize: (info.normalize === undefined) ? false : info.normalize,
bytes: info.num * attrTypeBytes(info.type),
};
this._attr2el[el.name] = el;
this._elements.push(el);
this._bytes += el.bytes;
hash += `${el.name}:${el.num}:${el.type}:${el.normalize}`;
}
for (let i = 0, len = this._elements.length; i < len; ++i) {
let el = this._elements[i];
el.stride = this._bytes;
}
this._hash = murmurhash2(hash, 666);
}
/**
* @method element
* @param {string} attrName
*/
element(attrName) {
return this._attr2el[attrName];
}
/**
* @method getHash
*/
getHash () {
return this._hash;
}
}

View File

@@ -0,0 +1,17 @@
export default class CircularPool {
constructor(fn, size) {
this._cursor = 0;
this._data = new Array(size);
for (let i = 0; i < size; ++i) {
this._data[i] = fn();
}
}
request() {
let item = this._data[this._cursor];
this._cursor = (this._cursor + 1) % this._data.length;
return item;
}
}

View File

@@ -0,0 +1,73 @@
import sort from './timsort';
export default class FixedArray {
constructor(size) {
this._count = 0;
this._data = new Array(size);
}
_resize(size) {
if (size > this._data.length) {
for (let i = this._data.length; i < size; ++i) {
this._data[i] = undefined;
}
}
}
get length() {
return this._count;
}
get data() {
return this._data;
}
reset() {
for (let i = 0; i < this._count; ++i) {
this._data[i] = undefined;
}
this._count = 0;
}
push(val) {
if (this._count >= this._data.length) {
this._resize(this._data.length * 2);
}
this._data[this._count] = val;
++this._count;
}
pop() {
--this._count;
if (this._count < 0) {
this._count = 0;
}
let ret = this._data[this._count];
this._data[this._count] = undefined;
return ret;
}
fastRemove(idx) {
if (idx >= this._count || idx < 0) {
return;
}
let last = this._count - 1;
this._data[idx] = this._data[last];
this._data[last] = undefined;
this._count -= 1;
}
indexOf(val) {
return this._data.indexOf(val);
}
sort(cmp) {
return sort(this._data, 0, this._count, cmp);
}
}

View File

@@ -0,0 +1,6 @@
export { default as CircularPool } from './circular-pool';
export { default as FixedArray } from './fixed-array';
export { default as LinkedArray } from './linked-array';
export { default as Pool } from './pool';
export { default as RecyclePool } from './recycle-pool';
export { default as TypedArrayPool } from './typed-array-pool';

View File

@@ -0,0 +1,82 @@
import Pool from './pool';
// NOTE: you must have `_prev` and `_next` field in the object returns by `fn`
export default class LinkedArray {
constructor(fn, size) {
this._fn = fn;
this._count = 0;
this._head = null;
this._tail = null;
this._pool = new Pool(fn, size);
}
get head() {
return this._head;
}
get tail() {
return this._tail;
}
get length() {
return this._count;
}
add() {
let node = this._pool.alloc();
if (!this._tail) {
this._head = node;
} else {
this._tail._next = node;
node._prev = this._tail;
}
this._tail = node;
this._count += 1;
return node;
}
remove(node) {
if (node._prev) {
node._prev._next = node._next;
} else {
this._head = node._next;
}
if (node._next) {
node._next._prev = node._prev;
} else {
this._tail = node._prev;
}
node._next = null;
node._prev = null;
this._pool.free(node);
this._count -= 1;
}
forEach(fn, binder) {
let cursor = this._head;
if (!cursor) {
return;
}
if (binder) {
fn = fn.bind(binder);
}
let idx = 0;
let next = cursor;
while (cursor) {
next = cursor._next;
fn(cursor, idx, this);
cursor = next;
++idx;
}
}
}

View File

@@ -0,0 +1,187 @@
export class OptimizedArray {
/**
* Initialize this array with specified capacity.
* @param {Number} [size] The size.
*/
constructor(size = 0) {
this._size = size;
this._data = new Array(size);
}
/**
* Size of this array.
* @return {Number}
*/
get size() {
return this._size;
}
/**
* The underlying Array of this array.
* @return {Array}
*/
get data() {
return this._data;
}
/**
* Capacity of this array.
*/
get capacity() {
return this._data.length;
}
/**
* Push a value to back of this array.
* @param {any} value
*/
push(value) {
if (this._size > this._data.length)
this._extends(this._data.length);
this._data[this._size] = value;
++this._size;
}
/**
* Remove the last element and return it, if exists.
*/
pop() {
if (this._size == 0)
return;
--this._size;
let ret = this._data[this._size];
this._data[this._size] = undefined;
return ret;
}
/**
* Remove all elements.
*/
clear() {
for (let i = 0; i < this._data.length; ++i)
this._data[i] = undefined;
this._size = 0;
}
/**
* @ignore
* @param {Number} size
*/
_extends(size) {
let finalSize = this._data.length + size;
for (let i = this._data.length; i < finalSize; ++i)
this._data[i] = undefined;
}
}
export class OptimizedValueArray {
/**
* Initialize this array with specified capacity.
* @param {any} ctor The constructor to create the value.
* @param {Number} [size] The size.
*/
constructor(ctor, dtor, size = 0) {
this._size = size;
this._data = new Array();
this._ctor = ctor;
this._dtor = dtor;
this._extends(size);
}
/**
* Size of this array.
* @return {Number}
*/
get size() {
return this._size;
}
/**
* The underlying Array of this array.
* @return {Array}
*/
get data() {
return this._data;
}
/**
* Capacity of this array.
*/
get capacity() {
return this._data.length;
}
/**
* Push a value to back of this array.
*/
push() {
if (this._size >= this._data.length)
this._extends(this._data.length + 1);
let retval = this._data[this._size];
++this._size;
return retval;
}
/**
* Remove the last element, if exists.
* Since that element is not erased, so we cannot return it.
*/
pop() {
if (this._size == 0)
return;
--this._size;
this._dtor(this._data[this._size]);
}
/**
* Remove all elements.
*/
clear() {
this._size = 0;
for (let i = 0; i < this._data.length; ++i)
this._dtor(this._data[i]);
}
splice(from, number) {
if (number == 0)
return;
if (from >= this.size)
return; // throw
number = Math.min(this.size - from, number);
let originalSize = this._size;
this._size -= number;
for (let i = 0, moveStart = from + number, moveNumber = originalSize - moveStart; i < moveNumber; ++i) {
let temp = this._data[from + i];
this._data[from + i] = this._data[moveStart + i];
this._data[moveStart + i] = temp;
}
for (let i = this._size; i != originalSize; ++i)
this._dtor(this._data[i]);
}
forEach(fx) {
for (let i = 0; i < this.size; ++i)
fx(this.data[i], i, this);
}
map(fx) {
let result = new Array();
for (let i = 0; i < this.size; ++i)
result.push(fx(this.data[i], i, this));
return result;
}
/**
* @ignore
* @param {Number} size
*/
_extends(size) {
let finalSize = this._data.length + size;
for (let i = this._data.length; i < finalSize; ++i)
this._data[i] = this._ctor();
}
}

View File

@@ -0,0 +1,59 @@
export default class Pool {
constructor(fn, size) {
this._fn = fn;
this._idx = size - 1;
this._frees = new Array(size);
for (let i = 0; i < size; ++i) {
this._frees[i] = fn();
}
}
_expand(size) {
let old = this._frees;
this._frees = new Array(size);
let len = size - old.length;
for (let i = 0; i < len; ++i) {
this._frees[i] = this._fn();
}
for (let i = len, j = 0; i < size; ++i, ++j) {
this._frees[i] = old[j];
}
this._idx += len;
}
alloc() {
// create some more space (expand by 20%, minimum 1)
if (this._idx < 0) {
this._expand(Math.round(this._frees.length * 1.2) + 1);
}
let ret = this._frees[this._idx];
this._frees[this._idx] = null;
--this._idx;
return ret;
}
free(obj) {
++this._idx;
this._frees[this._idx] = obj;
}
/**
* 清除对象池。
* @param fn 清除回调,对每个释放的对象调用一次。
*/
clear (fn) {
for (let i = 0; i <= this._idx; i++) {
if (fn) {
fn(this._frees[i]);
}
}
this._frees.length = 0;
this._idx = -1;
}
}

View File

@@ -0,0 +1,63 @@
import sort from './timsort';
/**
* Recycle Pool
* @class RecyclePool
*/
export default class RecyclePool {
constructor(fn, size) {
this._fn = fn;
this._count = 0;
this._data = new Array(size);
for (let i = 0; i < size; ++i) {
this._data[i] = fn();
}
}
get length() {
return this._count;
}
get data() {
return this._data;
}
reset() {
this._count = 0;
}
resize(size) {
if (size > this._data.length) {
for (let i = this._data.length; i < size; ++i) {
this._data[i] = this._fn();
}
}
}
add() {
if (this._count >= this._data.length) {
this.resize(this._data.length * 2);
}
return this._data[this._count++];
}
remove(idx) {
if (idx >= this._count) {
return;
}
let last = this._count - 1;
let tmp = this._data[idx];
this._data[idx] = this._data[last];
this._data[last] = tmp;
this._count -= 1;
}
sort(cmp) {
return sort(this._data, 0, this._count, cmp);
}
}
cc.RecyclePool = RecyclePool;

View File

@@ -0,0 +1,166 @@
// reference: https://github.com/v8/v8/blob/master/src/js/array.js
function _compare(a, b) {
return a - b;
}
function _sort(array, from, to, comparefn) {
function _insertionSort(a, from, to) {
for (let i = from + 1; i < to; i++) {
let element = a[i];
let j;
for (j = i - 1; j >= from; j--) {
let tmp = a[j];
let order = comparefn(tmp, element);
if (order > 0) {
a[j + 1] = tmp;
} else {
break;
}
}
a[j + 1] = element;
}
}
// function _getThirdIndex(a, from, to) {
// let t_array = new Array();
// // Use both 'from' and 'to' to determine the pivot candidates.
// let increment = 200 + ((to - from) & 15);
// let j = 0;
// from += 1;
// to -= 1;
// for (let i = from; i < to; i += increment) {
// t_array[j] = [i, a[i]];
// j++;
// }
// t_array.sort(function (a, b) {
// return comparefn(a[1], b[1]);
// });
// let third_index = t_array[t_array.length >> 1][0];
// return third_index;
// }
function _quickSort(a, from, to) {
let third_index = 0;
while (true) {
// Insertion sort is faster for short arrays.
if (to - from <= 10) {
_insertionSort(a, from, to);
return;
}
// if (to - from > 1000) {
// third_index = _getThirdIndex(a, from, to);
// } else {
third_index = from + ((to - from) >> 1);
// }
// Find a pivot as the median of first, last and middle element.
let v0 = a[from];
let v1 = a[to - 1];
let v2 = a[third_index];
let c01 = comparefn(v0, v1);
if (c01 > 0) {
// v1 < v0, so swap them.
let tmp = v0;
v0 = v1;
v1 = tmp;
} // v0 <= v1.
let c02 = comparefn(v0, v2);
if (c02 >= 0) {
// v2 <= v0 <= v1.
let tmp = v0;
v0 = v2;
v2 = v1;
v1 = tmp;
} else {
// v0 <= v1 && v0 < v2
let c12 = comparefn(v1, v2);
if (c12 > 0) {
// v0 <= v2 < v1
let tmp = v1;
v1 = v2;
v2 = tmp;
}
}
// v0 <= v1 <= v2
a[from] = v0;
a[to - 1] = v2;
let pivot = v1;
let low_end = from + 1; // Upper bound of elements lower than pivot.
let high_start = to - 1; // Lower bound of elements greater than pivot.
a[third_index] = a[low_end];
a[low_end] = pivot;
// From low_end to i are elements equal to pivot.
// From i to high_start are elements that haven't been compared yet.
partition: for (let i = low_end + 1; i < high_start; i++) {
let element = a[i];
let order = comparefn(element, pivot);
if (order < 0) {
a[i] = a[low_end];
a[low_end] = element;
low_end++;
} else if (order > 0) {
do {
high_start--;
if (high_start == i) break partition;
let top_elem = a[high_start];
order = comparefn(top_elem, pivot);
} while (order > 0);
a[i] = a[high_start];
a[high_start] = element;
if (order < 0) {
element = a[i];
a[i] = a[low_end];
a[low_end] = element;
low_end++;
}
}
}
if (to - high_start < low_end - from) {
_quickSort(a, high_start, to);
to = low_end;
} else {
_quickSort(a, from, low_end);
from = high_start;
}
}
}
if (to - from < 2) {
return array;
}
_quickSort(array, from, to);
return array;
}
/**
* Calls the _quickSort function with it's initial values.
*
* @public
* @param {array} array The input array which should be sorted
* @param {Number} from
* @param {Number} to
* @param {Function} cmp
* @returns {array} array Sorted array
*/
export default function (array, from, to, cmp) {
if (from === undefined) {
from = 0;
}
if (to === undefined) {
to = array.length;
}
if (cmp === undefined) {
cmp = _compare;
}
return _sort(array, from, to, cmp);
}

View File

@@ -0,0 +1,967 @@
// reference: https://github.com/mziccard/node-timsort
/**
* Default minimum size of a run.
*/
const DEFAULT_MIN_MERGE = 32;
/**
* Minimum ordered subsequece required to do galloping.
*/
const DEFAULT_MIN_GALLOPING = 7;
/**
* Default tmp storage length. Can increase depending on the size of the
* smallest run to merge.
*/
const DEFAULT_TMP_STORAGE_LENGTH = 256;
/**
* Pre-computed powers of 10 for efficient lexicographic comparison of
* small integers.
*/
const POWERS_OF_TEN = [1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9]
/**
* Estimate the logarithm base 10 of a small integer.
*
* @param {number} x - The integer to estimate the logarithm of.
* @return {number} - The estimated logarithm of the integer.
*/
function log10(x) {
if (x < 1e5) {
if (x < 1e2) {
return x < 1e1 ? 0 : 1;
}
if (x < 1e4) {
return x < 1e3 ? 2 : 3;
}
return 4;
}
if (x < 1e7) {
return x < 1e6 ? 5 : 6;
}
if (x < 1e9) {
return x < 1e8 ? 7 : 8;
}
return 9;
}
/**
* Default alphabetical comparison of items.
*
* @param {string|object|number} a - First element to compare.
* @param {string|object|number} b - Second element to compare.
* @return {number} - A positive number if a.toString() > b.toString(), a
* negative number if .toString() < b.toString(), 0 otherwise.
*/
function alphabeticalCompare(a, b) {
if (a === b) {
return 0;
}
if (~~a === a && ~~b === b) {
if (a === 0 || b === 0) {
return a < b ? -1 : 1;
}
if (a < 0 || b < 0) {
if (b >= 0) {
return -1;
}
if (a >= 0) {
return 1;
}
a = -a;
b = -b;
}
const al = log10(a);
const bl = log10(b);
let t = 0;
if (al < bl) {
a *= POWERS_OF_TEN[bl - al - 1];
b /= 10;
t = -1;
} else if (al > bl) {
b *= POWERS_OF_TEN[al - bl - 1];
a /= 10;
t = 1;
}
if (a === b) {
return t;
}
return a < b ? -1 : 1;
}
let aStr = String(a);
let bStr = String(b);
if (aStr === bStr) {
return 0;
}
return aStr < bStr ? -1 : 1;
}
/**
* Compute minimum run length for TimSort
*
* @param {number} n - The size of the array to sort.
*/
function minRunLength(n) {
let r = 0;
while (n >= DEFAULT_MIN_MERGE) {
r |= (n & 1);
n >>= 1;
}
return n + r;
}
/**
* Counts the length of a monotonically ascending or strictly monotonically
* descending sequence (run) starting at array[lo] in the range [lo, hi). If
* the run is descending it is made ascending.
*
* @param {array} array - The array to reverse.
* @param {number} lo - First element in the range (inclusive).
* @param {number} hi - Last element in the range.
* @param {function} compare - Item comparison function.
* @return {number} - The length of the run.
*/
function makeAscendingRun(array, lo, hi, compare) {
let runHi = lo + 1;
if (runHi === hi) {
return 1;
}
// Descending
if (compare(array[runHi++], array[lo]) < 0) {
while (runHi < hi && compare(array[runHi], array[runHi - 1]) < 0) {
runHi++;
}
reverseRun(array, lo, runHi);
// Ascending
} else {
while (runHi < hi && compare(array[runHi], array[runHi - 1]) >= 0) {
runHi++;
}
}
return runHi - lo;
}
/**
* Reverse an array in the range [lo, hi).
*
* @param {array} array - The array to reverse.
* @param {number} lo - First element in the range (inclusive).
* @param {number} hi - Last element in the range.
*/
function reverseRun(array, lo, hi) {
hi--;
while (lo < hi) {
let t = array[lo];
array[lo++] = array[hi];
array[hi--] = t;
}
}
/**
* Perform the binary sort of the array in the range [lo, hi) where start is
* the first element possibly out of order.
*
* @param {array} array - The array to sort.
* @param {number} lo - First element in the range (inclusive).
* @param {number} hi - Last element in the range.
* @param {number} start - First element possibly out of order.
* @param {function} compare - Item comparison function.
*/
function binaryInsertionSort(array, lo, hi, start, compare) {
if (start === lo) {
start++;
}
for (; start < hi; start++) {
let pivot = array[start];
// Ranges of the array where pivot belongs
let left = lo;
let right = start;
/*
* pivot >= array[i] for i in [lo, left)
* pivot < array[i] for i in in [right, start)
*/
while (left < right) {
let mid = (left + right) >>> 1;
if (compare(pivot, array[mid]) < 0) {
right = mid;
} else {
left = mid + 1;
}
}
/*
* Move elements right to make room for the pivot. If there are elements
* equal to pivot, left points to the first slot after them: this is also
* a reason for which TimSort is stable
*/
let n = start - left;
// Switch is just an optimization for small arrays
switch (n) {
case 3:
array[left + 3] = array[left + 2];
/* falls through */
case 2:
array[left + 2] = array[left + 1];
/* falls through */
case 1:
array[left + 1] = array[left];
break;
default:
while (n > 0) {
array[left + n] = array[left + n - 1];
n--;
}
}
array[left] = pivot;
}
}
/**
* Find the position at which to insert a value in a sorted range. If the range
* contains elements equal to the value the leftmost element index is returned
* (for stability).
*
* @param {number} value - Value to insert.
* @param {array} array - The array in which to insert value.
* @param {number} start - First element in the range.
* @param {number} length - Length of the range.
* @param {number} hint - The index at which to begin the search.
* @param {function} compare - Item comparison function.
* @return {number} - The index where to insert value.
*/
function gallopLeft(value, array, start, length, hint, compare) {
let lastOffset = 0;
let maxOffset = 0;
let offset = 1;
if (compare(value, array[start + hint]) > 0) {
maxOffset = length - hint;
while (offset < maxOffset && compare(value, array[start + hint + offset]) > 0) {
lastOffset = offset;
offset = (offset << 1) + 1;
if (offset <= 0) {
offset = maxOffset;
}
}
if (offset > maxOffset) {
offset = maxOffset;
}
// Make offsets relative to start
lastOffset += hint;
offset += hint;
// value <= array[start + hint]
} else {
maxOffset = hint + 1;
while (offset < maxOffset && compare(value, array[start + hint - offset]) <= 0) {
lastOffset = offset;
offset = (offset << 1) + 1;
if (offset <= 0) {
offset = maxOffset;
}
}
if (offset > maxOffset) {
offset = maxOffset;
}
// Make offsets relative to start
let tmp = lastOffset;
lastOffset = hint - offset;
offset = hint - tmp;
}
/*
* Now array[start+lastOffset] < value <= array[start+offset], so value
* belongs somewhere in the range (start + lastOffset, start + offset]. Do a
* binary search, with invariant array[start + lastOffset - 1] < value <=
* array[start + offset].
*/
lastOffset++;
while (lastOffset < offset) {
let m = lastOffset + ((offset - lastOffset) >>> 1);
if (compare(value, array[start + m]) > 0) {
lastOffset = m + 1;
} else {
offset = m;
}
}
return offset;
}
/**
* Find the position at which to insert a value in a sorted range. If the range
* contains elements equal to the value the rightmost element index is returned
* (for stability).
*
* @param {number} value - Value to insert.
* @param {array} array - The array in which to insert value.
* @param {number} start - First element in the range.
* @param {number} length - Length of the range.
* @param {number} hint - The index at which to begin the search.
* @param {function} compare - Item comparison function.
* @return {number} - The index where to insert value.
*/
function gallopRight(value, array, start, length, hint, compare) {
let lastOffset = 0;
let maxOffset = 0;
let offset = 1;
if (compare(value, array[start + hint]) < 0) {
maxOffset = hint + 1;
while (offset < maxOffset && compare(value, array[start + hint - offset]) < 0) {
lastOffset = offset;
offset = (offset << 1) + 1;
if (offset <= 0) {
offset = maxOffset;
}
}
if (offset > maxOffset) {
offset = maxOffset;
}
// Make offsets relative to start
let tmp = lastOffset;
lastOffset = hint - offset;
offset = hint - tmp;
// value >= array[start + hint]
} else {
maxOffset = length - hint;
while (offset < maxOffset && compare(value, array[start + hint + offset]) >= 0) {
lastOffset = offset;
offset = (offset << 1) + 1;
if (offset <= 0) {
offset = maxOffset;
}
}
if (offset > maxOffset) {
offset = maxOffset;
}
// Make offsets relative to start
lastOffset += hint;
offset += hint;
}
/*
* Now array[start+lastOffset] < value <= array[start+offset], so value
* belongs somewhere in the range (start + lastOffset, start + offset]. Do a
* binary search, with invariant array[start + lastOffset - 1] < value <=
* array[start + offset].
*/
lastOffset++;
while (lastOffset < offset) {
let m = lastOffset + ((offset - lastOffset) >>> 1);
if (compare(value, array[start + m]) < 0) {
offset = m;
} else {
lastOffset = m + 1;
}
}
return offset;
}
class TimSort {
constructor(array, compare) {
this.array = array;
this.compare = compare;
this.minGallop = DEFAULT_MIN_GALLOPING;
this.length = array.length;
this.tmpStorageLength = DEFAULT_TMP_STORAGE_LENGTH;
if (this.length < 2 * DEFAULT_TMP_STORAGE_LENGTH) {
this.tmpStorageLength = this.length >>> 1;
}
this.tmp = new Array(this.tmpStorageLength);
this.stackLength =
(this.length < 120 ? 5 :
this.length < 1542 ? 10 :
this.length < 119151 ? 19 : 40);
this.runStart = new Array(this.stackLength);
this.runLength = new Array(this.stackLength);
this.stackSize = 0;
}
/**
* Push a new run on TimSort's stack.
*
* @param {number} runStart - Start index of the run in the original array.
* @param {number} runLength - Length of the run;
*/
pushRun(runStart, runLength) {
this.runStart[this.stackSize] = runStart;
this.runLength[this.stackSize] = runLength;
this.stackSize += 1;
}
/**
* Merge runs on TimSort's stack so that the following holds for all i:
* 1) runLength[i - 3] > runLength[i - 2] + runLength[i - 1]
* 2) runLength[i - 2] > runLength[i - 1]
*/
mergeRuns() {
while (this.stackSize > 1) {
let n = this.stackSize - 2;
if ((n >= 1 &&
this.runLength[n - 1] <= this.runLength[n] + this.runLength[n + 1]) ||
(n >= 2 &&
this.runLength[n - 2] <= this.runLength[n] + this.runLength[n - 1])) {
if (this.runLength[n - 1] < this.runLength[n + 1]) {
n--;
}
} else if (this.runLength[n] > this.runLength[n + 1]) {
break;
}
this.mergeAt(n);
}
}
/**
* Merge all runs on TimSort's stack until only one remains.
*/
forceMergeRuns() {
while (this.stackSize > 1) {
let n = this.stackSize - 2;
if (n > 0 && this.runLength[n - 1] < this.runLength[n + 1]) {
n--;
}
this.mergeAt(n);
}
}
/**
* Merge the runs on the stack at positions i and i+1. Must be always be called
* with i=stackSize-2 or i=stackSize-3 (that is, we merge on top of the stack).
*
* @param {number} i - Index of the run to merge in TimSort's stack.
*/
mergeAt(i) {
let compare = this.compare;
let array = this.array;
let start1 = this.runStart[i];
let length1 = this.runLength[i];
let start2 = this.runStart[i + 1];
let length2 = this.runLength[i + 1];
this.runLength[i] = length1 + length2;
if (i === this.stackSize - 3) {
this.runStart[i + 1] = this.runStart[i + 2];
this.runLength[i + 1] = this.runLength[i + 2];
}
this.stackSize--;
/*
* Find where the first element in the second run goes in run1. Previous
* elements in run1 are already in place
*/
let k = gallopRight(array[start2], array, start1, length1, 0, compare);
start1 += k;
length1 -= k;
if (length1 === 0) {
return;
}
/*
* Find where the last element in the first run goes in run2. Next elements
* in run2 are already in place
*/
length2 = gallopLeft(array[start1 + length1 - 1], array, start2, length2, length2 - 1, compare);
if (length2 === 0) {
return;
}
/*
* Merge remaining runs. A tmp array with length = min(length1, length2) is
* used
*/
if (length1 <= length2) {
this.mergeLow(start1, length1, start2, length2);
} else {
this.mergeHigh(start1, length1, start2, length2);
}
}
/**
* Merge two adjacent runs in a stable way. The runs must be such that the
* first element of run1 is bigger than the first element in run2 and the
* last element of run1 is greater than all the elements in run2.
* The method should be called when run1.length <= run2.length as it uses
* TimSort temporary array to store run1. Use mergeHigh if run1.length >
* run2.length.
*
* @param {number} start1 - First element in run1.
* @param {number} length1 - Length of run1.
* @param {number} start2 - First element in run2.
* @param {number} length2 - Length of run2.
*/
mergeLow(start1, length1, start2, length2) {
let compare = this.compare;
let array = this.array;
let tmp = this.tmp;
let i = 0;
for (i = 0; i < length1; i++) {
tmp[i] = array[start1 + i];
}
let cursor1 = 0;
let cursor2 = start2;
let dest = start1;
array[dest++] = array[cursor2++];
if (--length2 === 0) {
for (i = 0; i < length1; i++) {
array[dest + i] = tmp[cursor1 + i];
}
return;
}
if (length1 === 1) {
for (i = 0; i < length2; i++) {
array[dest + i] = array[cursor2 + i];
}
array[dest + length2] = tmp[cursor1];
return;
}
let minGallop = this.minGallop;
while (true) {
let count1 = 0;
let count2 = 0;
let exit = false;
do {
if (compare(array[cursor2], tmp[cursor1]) < 0) {
array[dest++] = array[cursor2++];
count2++;
count1 = 0;
if (--length2 === 0) {
exit = true;
break;
}
} else {
array[dest++] = tmp[cursor1++];
count1++;
count2 = 0;
if (--length1 === 1) {
exit = true;
break;
}
}
} while ((count1 | count2) < minGallop);
if (exit) {
break;
}
do {
count1 = gallopRight(array[cursor2], tmp, cursor1, length1, 0, compare);
if (count1 !== 0) {
for (i = 0; i < count1; i++) {
array[dest + i] = tmp[cursor1 + i];
}
dest += count1;
cursor1 += count1;
length1 -= count1;
if (length1 <= 1) {
exit = true;
break;
}
}
array[dest++] = array[cursor2++];
if (--length2 === 0) {
exit = true;
break;
}
count2 = gallopLeft(tmp[cursor1], array, cursor2, length2, 0, compare);
if (count2 !== 0) {
for (i = 0; i < count2; i++) {
array[dest + i] = array[cursor2 + i];
}
dest += count2;
cursor2 += count2;
length2 -= count2;
if (length2 === 0) {
exit = true;
break;
}
}
array[dest++] = tmp[cursor1++];
if (--length1 === 1) {
exit = true;
break;
}
minGallop--;
} while (count1 >= DEFAULT_MIN_GALLOPING || count2 >= DEFAULT_MIN_GALLOPING);
if (exit) {
break;
}
if (minGallop < 0) {
minGallop = 0;
}
minGallop += 2;
}
this.minGallop = minGallop;
if (minGallop < 1) {
this.minGallop = 1;
}
if (length1 === 1) {
for (i = 0; i < length2; i++) {
array[dest + i] = array[cursor2 + i];
}
array[dest + length2] = tmp[cursor1];
} else if (length1 === 0) {
throw new Error('mergeLow preconditions were not respected');
} else {
for (i = 0; i < length1; i++) {
array[dest + i] = tmp[cursor1 + i];
}
}
}
/**
* Merge two adjacent runs in a stable way. The runs must be such that the
* first element of run1 is bigger than the first element in run2 and the
* last element of run1 is greater than all the elements in run2.
* The method should be called when run1.length > run2.length as it uses
* TimSort temporary array to store run2. Use mergeLow if run1.length <=
* run2.length.
*
* @param {number} start1 - First element in run1.
* @param {number} length1 - Length of run1.
* @param {number} start2 - First element in run2.
* @param {number} length2 - Length of run2.
*/
mergeHigh(start1, length1, start2, length2) {
let compare = this.compare;
let array = this.array;
let tmp = this.tmp;
let i = 0;
for (i = 0; i < length2; i++) {
tmp[i] = array[start2 + i];
}
let cursor1 = start1 + length1 - 1;
let cursor2 = length2 - 1;
let dest = start2 + length2 - 1;
let customCursor = 0;
let customDest = 0;
array[dest--] = array[cursor1--];
if (--length1 === 0) {
customCursor = dest - (length2 - 1);
for (i = 0; i < length2; i++) {
array[customCursor + i] = tmp[i];
}
return;
}
if (length2 === 1) {
dest -= length1;
cursor1 -= length1;
customDest = dest + 1;
customCursor = cursor1 + 1;
for (i = length1 - 1; i >= 0; i--) {
array[customDest + i] = array[customCursor + i];
}
array[dest] = tmp[cursor2];
return;
}
let minGallop = this.minGallop;
while (true) {
let count1 = 0;
let count2 = 0;
let exit = false;
do {
if (compare(tmp[cursor2], array[cursor1]) < 0) {
array[dest--] = array[cursor1--];
count1++;
count2 = 0;
if (--length1 === 0) {
exit = true;
break;
}
} else {
array[dest--] = tmp[cursor2--];
count2++;
count1 = 0;
if (--length2 === 1) {
exit = true;
break;
}
}
} while ((count1 | count2) < minGallop);
if (exit) {
break;
}
do {
count1 = length1 - gallopRight(tmp[cursor2], array, start1, length1, length1 - 1, compare);
if (count1 !== 0) {
dest -= count1;
cursor1 -= count1;
length1 -= count1;
customDest = dest + 1;
customCursor = cursor1 + 1;
for (i = count1 - 1; i >= 0; i--) {
array[customDest + i] = array[customCursor + i];
}
if (length1 === 0) {
exit = true;
break;
}
}
array[dest--] = tmp[cursor2--];
if (--length2 === 1) {
exit = true;
break;
}
count2 = length2 - gallopLeft(array[cursor1], tmp, 0, length2, length2 - 1, compare);
if (count2 !== 0) {
dest -= count2;
cursor2 -= count2;
length2 -= count2;
customDest = dest + 1;
customCursor = cursor2 + 1;
for (i = 0; i < count2; i++) {
array[customDest + i] = tmp[customCursor + i];
}
if (length2 <= 1) {
exit = true;
break;
}
}
array[dest--] = array[cursor1--];
if (--length1 === 0) {
exit = true;
break;
}
minGallop--;
} while (count1 >= DEFAULT_MIN_GALLOPING || count2 >= DEFAULT_MIN_GALLOPING);
if (exit) {
break;
}
if (minGallop < 0) {
minGallop = 0;
}
minGallop += 2;
}
this.minGallop = minGallop;
if (minGallop < 1) {
this.minGallop = 1;
}
if (length2 === 1) {
dest -= length1;
cursor1 -= length1;
customDest = dest + 1;
customCursor = cursor1 + 1;
for (i = length1 - 1; i >= 0; i--) {
array[customDest + i] = array[customCursor + i];
}
array[dest] = tmp[cursor2];
} else if (length2 === 0) {
throw new Error('mergeHigh preconditions were not respected');
} else {
customCursor = dest - (length2 - 1);
for (i = 0; i < length2; i++) {
array[customCursor + i] = tmp[i];
}
}
}
}
/**
* Sort an array in the range [lo, hi) using TimSort.
*
* @param {array} array - The array to sort.
* @param {number} lo - First element in the range (inclusive).
* @param {number} hi - Last element in the range.
* @param {function=} compare - Item comparison function. Default is alphabetical.
*/
export default function (array, lo, hi, compare) {
if (!Array.isArray(array)) {
throw new TypeError('Can only sort arrays');
}
/*
* Handle the case where a comparison function is not provided. We do
* lexicographic sorting
*/
if (lo === undefined) {
lo = 0;
}
if (hi === undefined) {
hi = array.length;
}
if (compare === undefined) {
compare = alphabeticalCompare;
}
let remaining = hi - lo;
// The array is already sorted
if (remaining < 2) {
return;
}
let runLength = 0;
// On small arrays binary sort can be used directly
if (remaining < DEFAULT_MIN_MERGE) {
runLength = makeAscendingRun(array, lo, hi, compare);
binaryInsertionSort(array, lo, hi, lo + runLength, compare);
return;
}
let ts = new TimSort(array, compare);
let minRun = minRunLength(remaining);
do {
runLength = makeAscendingRun(array, lo, hi, compare);
if (runLength < minRun) {
let force = remaining;
if (force > minRun) {
force = minRun;
}
binaryInsertionSort(array, lo, lo + force, lo + runLength, compare);
runLength = force;
}
// Push new run and merge if necessary
ts.pushRun(lo, runLength);
ts.mergeRuns();
// Go find next run
remaining -= runLength;
lo += runLength;
} while (remaining !== 0);
// Force merging of remaining runs
ts.forceMergeRuns();
}

View File

@@ -0,0 +1,129 @@
let _bufferPools = Array(8);
for (let i = 0; i < 8; ++i) {
_bufferPools[i] = [];
}
function _nextPow16(v) {
for (let i = 16; i <= (1 << 28); i *= 16) {
if (v <= i) {
return i;
}
}
return 0;
}
function _log2(v) {
let r, shift;
r = (v > 0xFFFF) << 4; v >>>= r;
shift = (v > 0xFF) << 3; v >>>= shift; r |= shift;
shift = (v > 0xF) << 2; v >>>= shift; r |= shift;
shift = (v > 0x3) << 1; v >>>= shift; r |= shift;
return r | (v >> 1);
}
function _alloc(n) {
let sz = _nextPow16(n);
let bin = _bufferPools[_log2(sz) >> 2];
if (bin.length > 0) {
return bin.pop();
}
return new ArrayBuffer(sz);
}
function _free(buf) {
_bufferPools[_log2(buf.byteLength) >> 2].push(buf);
}
export default {
alloc_int8(n) {
let result = new Int8Array(_alloc(n), 0, n);
if (result.length !== n) {
return result.subarray(0, n);
}
return result;
},
alloc_uint8(n) {
let result = new Uint8Array(_alloc(n), 0, n);
if (result.length !== n) {
return result.subarray(0, n);
}
return result;
},
alloc_int16(n) {
let result = new Int16Array(_alloc(2 * n), 0, n);
if (result.length !== n) {
return result.subarray(0, n);
}
return result;
},
alloc_uint16(n) {
let result = new Uint16Array(_alloc(2 * n), 0, n);
if (result.length !== n) {
return result.subarray(0, n);
}
return result;
},
alloc_int32(n) {
let result = new Int32Array(_alloc(4 * n), 0, n);
if (result.length !== n) {
return result.subarray(0, n);
}
return result;
},
alloc_uint32(n) {
let result = new Uint32Array(_alloc(4 * n), 0, n);
if (result.length !== n) {
return result.subarray(0, n);
}
return result;
},
alloc_float32(n) {
let result = new Float32Array(_alloc(4 * n), 0, n);
if (result.length !== n) {
return result.subarray(0, n);
}
return result;
},
alloc_float64(n) {
let result = new Float64Array(_alloc(8 * n), 0, n);
if (result.length !== n) {
return result.subarray(0, n);
}
return result;
},
alloc_dataview(n) {
let result = new DataView(_alloc(n), 0, n);
if (result.length !== n) {
return result.subarray(0, n);
}
return result;
},
free(array) {
_free(array.buffer);
},
reset() {
let _bufferPools = Array(8);
for (let i = 0; i < 8; ++i) {
_bufferPools[i] = [];
}
},
};

View File

@@ -0,0 +1,50 @@
/**
* JS Implementation of MurmurHash2
*
* @author <a href="mailto:gary.court@gmail.com">Gary Court</a>
* @see http://github.com/garycourt/murmurhash-js
* @author <a href="mailto:aappleby@gmail.com">Austin Appleby</a>
* @see http://sites.google.com/site/murmurhash/
*
* @param {string} str ASCII only
* @param {number} seed Positive integer only
* @return {number} 32-bit positive integer hash
*/
export default function murmurhash2_32_gc(str, seed) {
var
l = str.length,
h = seed ^ l,
i = 0,
k;
while (l >= 4) {
k =
((str.charCodeAt(i) & 0xff)) |
((str.charCodeAt(++i) & 0xff) << 8) |
((str.charCodeAt(++i) & 0xff) << 16) |
((str.charCodeAt(++i) & 0xff) << 24);
k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
k ^= k >>> 24;
k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k;
l -= 4;
++i;
}
switch (l) {
case 3: h ^= (str.charCodeAt(i + 2) & 0xff) << 16;
case 2: h ^= (str.charCodeAt(i + 1) & 0xff) << 8;
case 1: h ^= (str.charCodeAt(i) & 0xff);
h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
}
h ^= h >>> 13;
h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
h ^= h >>> 15;
return h >>> 0;
}

View File

@@ -0,0 +1,336 @@
// Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
import { Vec3, Vec4, Mat4 } from '../../core/value-types';
import BaseRenderer from '../core/base-renderer';
import enums from '../enums';
import { RecyclePool } from '../memop';
let _a16_view = new Float32Array(16);
let _a16_view_inv = new Float32Array(16);
let _a16_proj = new Float32Array(16);
let _a16_viewProj = new Float32Array(16);
let _a4_camPos = new Float32Array(4);
let _a64_shadow_lightViewProj = new Float32Array(64);
let _a16_shadow_lightViewProjs = [];
let _a4_shadow_info = new Float32Array(4);
let _camPos = new Vec4(0, 0, 0, 0);
let _camFwd = new Vec3(0, 0, 0);
let _v3_tmp1 = new Vec3(0, 0, 0);
const CC_MAX_LIGHTS = 4;
const CC_MAX_SHADOW_LIGHTS = 2;
let _float16_pool = new RecyclePool(() => {
return new Float32Array(16);
}, 8);
function sortView (a, b) {
return (a._priority - b._priority);
}
export default class ForwardRenderer extends BaseRenderer {
constructor(device, builtin) {
super(device, builtin);
this._time = new Float32Array(4);
this._lights = [];
this._shadowLights = [];
this._numLights = 0;
this._defines = {
};
this._registerStage('shadowcast', this._shadowStage.bind(this));
this._registerStage('opaque', this._opaqueStage.bind(this));
this._registerStage('transparent', this._transparentStage.bind(this));
}
reset () {
_float16_pool.reset();
super.reset();
}
render (scene, dt) {
this.reset();
if (!CC_EDITOR) {
if (dt) {
this._time[0] += dt;
this._time[1] = dt;
this._time[2] ++;
}
this._device.setUniform('cc_time', this._time);
}
this._updateLights(scene);
const canvas = this._device._gl.canvas;
for (let i = 0; i < scene._cameras.length; ++i) {
let view = this._requestView();
let width = canvas.width;
let height = canvas.height;
let camera = scene._cameras.data[i];
camera.extractView(view, width, height);
}
// render by cameras
this._viewPools.sort(sortView);
for (let i = 0; i < this._viewPools.length; ++i) {
let view = this._viewPools.data[i];
this._render(view, scene);
}
}
// direct render a single camera
renderCamera (camera, scene) {
this.reset();
this._updateLights(scene);
const canvas = this._device._gl.canvas;
let width = canvas.width;
let height = canvas.height;
let view = this._requestView();
camera.extractView(view, width, height);
// render by cameras
this._viewPools.sort(sortView);
for (let i = 0; i < this._viewPools.length; ++i) {
let view = this._viewPools.data[i];
this._render(view, scene);
}
}
_updateLights (scene) {
this._lights.length = 0;
this._shadowLights.length = 0;
let lights = scene._lights;
for (let i = 0; i < lights.length; ++i) {
let light = lights.data[i];
light.update(this._device);
if (light.shadowType !== enums.SHADOW_NONE) {
if (this._shadowLights.length < CC_MAX_SHADOW_LIGHTS) {
this._shadowLights.unshift(light);
}
let view = this._requestView();
light.extractView(view, ['shadowcast']);
this._lights.splice(0, 0, light);
}
else {
this._lights.push(light);
}
}
this._updateLightDefines();
this._numLights = lights._count;
}
_updateLightDefines () {
let defines = this._defines;
for (let i = 0; i < this._lights.length; ++i) {
let light = this._lights[i];
let lightKey = `CC_LIGHT_${i}_TYPE`;
let shadowKey = `CC_SHADOW_${i}_TYPE`;
if (defines[lightKey] !== light._type){
defines[lightKey] = light._type;
this._definesChanged = true;
}
if (defines[shadowKey] !== light._shadowType){
defines[shadowKey] = light._shadowType;
this._definesChanged = true;
}
}
let newCount = Math.min(CC_MAX_LIGHTS, this._lights.length);
if (defines.CC_NUM_LIGHTS !== newCount) {
defines.CC_NUM_LIGHTS = newCount;
this._definesChanged = true;
}
newCount = Math.min(CC_MAX_LIGHTS, this._shadowLights.length);
if (defines.CC_NUM_SHADOW_LIGHTS !== newCount) {
defines.CC_NUM_SHADOW_LIGHTS = newCount;
this._definesChanged = true;
}
}
_submitLightsUniforms () {
let device = this._device;
if (this._lights.length > 0) {
let positionAndRanges = _float16_pool.add();
let directions = _float16_pool.add();
let colors = _float16_pool.add();
let lightNum = Math.min(CC_MAX_LIGHTS, this._lights.length);
for (let i = 0; i < lightNum; ++i) {
let light = this._lights[i];
let index = i * 4;
colors.set(light._colorUniform, index);
directions.set(light._directionUniform, index);
positionAndRanges.set(light._positionUniform, index);
positionAndRanges[index+3] = light._range;
if (light._type === enums.LIGHT_SPOT) {
directions[index+3] = light._spotUniform[0];
colors[index+3] = light._spotUniform[1];
}
else {
directions[index+3] = 0;
colors[index+3] = 0;
}
}
device.setUniform('cc_lightDirection', directions);
device.setUniform('cc_lightColor', colors);
device.setUniform('cc_lightPositionAndRange', positionAndRanges);
}
}
_submitShadowStageUniforms(view) {
let light = view._shadowLight;
let shadowInfo = _a4_shadow_info;
shadowInfo[0] = light.shadowMinDepth;
shadowInfo[1] = light.shadowMaxDepth;
shadowInfo[2] = light.shadowDepthScale;
shadowInfo[3] = light.shadowDarkness;
this._device.setUniform('cc_shadow_map_lightViewProjMatrix', Mat4.toArray(_a16_viewProj, view._matViewProj));
this._device.setUniform('cc_shadow_map_info', shadowInfo);
this._device.setUniform('cc_shadow_map_bias', light.shadowBias);
this._defines.CC_SHADOW_TYPE = light._shadowType;
}
_submitOtherStagesUniforms() {
let shadowInfo = _float16_pool.add();
for (let i = 0; i < this._shadowLights.length; ++i) {
let light = this._shadowLights[i];
let view = _a16_shadow_lightViewProjs[i];
if (!view) {
view = _a16_shadow_lightViewProjs[i] = new Float32Array(_a64_shadow_lightViewProj.buffer, i * 64, 16);
}
Mat4.toArray(view, light.viewProjMatrix);
let index = i*4;
shadowInfo[index] = light.shadowMinDepth;
shadowInfo[index+1] = light.shadowMaxDepth;
shadowInfo[index+2] = light._shadowResolution;
shadowInfo[index+3] = light.shadowDarkness;
}
this._device.setUniform(`cc_shadow_lightViewProjMatrix`, _a64_shadow_lightViewProj);
this._device.setUniform(`cc_shadow_info`, shadowInfo);
// this._device.setUniform(`cc_frustumEdgeFalloff_${index}`, light.frustumEdgeFalloff);
}
_sortItems (items) {
// sort items
items.sort((a, b) => {
// if (a.layer !== b.layer) {
// return a.layer - b.layer;
// }
if (a.passes.length !== b.passes.length) {
return a.passes.length - b.passes.length;
}
return a.sortKey - b.sortKey;
});
}
_shadowStage (view, items) {
// update rendering
this._submitShadowStageUniforms(view);
// this._sortItems(items);
// draw it
for (let i = 0; i < items.length; ++i) {
let item = items.data[i];
if (item.effect.getDefine('CC_CASTING_SHADOW')) {
this._draw(item);
}
}
}
_drawItems (view, items) {
let shadowLights = this._shadowLights;
if (shadowLights.length === 0 && this._numLights === 0) {
for (let i = 0; i < items.length; ++i) {
let item = items.data[i];
this._draw(item);
}
}
else {
for (let i = 0; i < items.length; ++i) {
let item = items.data[i];
for (let shadowIdx = 0; shadowIdx < shadowLights.length; ++shadowIdx) {
this._device.setTexture('cc_shadow_map_'+shadowIdx, shadowLights[shadowIdx].shadowMap, this._allocTextureUnit());
}
this._draw(item);
}
}
}
_opaqueStage (view, items) {
view.getPosition(_camPos);
// update uniforms
this._device.setUniform('cc_matView', Mat4.toArray(_a16_view, view._matView));
this._device.setUniform('cc_matViewInv', Mat4.toArray(_a16_view_inv, view._matViewInv));
this._device.setUniform('cc_matProj', Mat4.toArray(_a16_proj, view._matProj));
this._device.setUniform('cc_matViewProj', Mat4.toArray(_a16_viewProj, view._matViewProj));
this._device.setUniform('cc_cameraPos', Vec4.toArray(_a4_camPos, _camPos));
// update rendering
this._submitLightsUniforms();
this._submitOtherStagesUniforms();
this._drawItems(view, items);
}
_transparentStage (view, items) {
view.getPosition(_camPos);
view.getForward(_camFwd);
// update uniforms
this._device.setUniform('cc_matView', Mat4.toArray(_a16_view, view._matView));
this._device.setUniform('cc_matViewInv', Mat4.toArray(_a16_view_inv, view._matViewInv));
this._device.setUniform('cc_matProj', Mat4.toArray(_a16_proj, view._matProj));
this._device.setUniform('cc_matViewProj', Mat4.toArray(_a16_viewProj, view._matViewProj));
this._device.setUniform('cc_cameraPos', Vec4.toArray(_a4_camPos, _camPos));
this._submitLightsUniforms();
this._submitOtherStagesUniforms();
// calculate zdist
for (let i = 0; i < items.length; ++i) {
let item = items.data[i];
// TODO: we should use mesh center instead!
item.node.getWorldPosition(_v3_tmp1);
Vec3.sub(_v3_tmp1, _v3_tmp1, _camPos);
item.sortKey = -Vec3.dot(_v3_tmp1, _camFwd);
}
this._sortItems(items);
this._drawItems(view, items);
}
}

View File

@@ -0,0 +1,503 @@
// Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
import { Vec3, Mat4, lerp, Vec4 } from '../../core/value-types';
import { Ray } from '../../core/geom-utils';
import enums from '../enums';
let _tmp_mat4 = new Mat4();
let _matView = new Mat4();
let _matViewInv = new Mat4();
let _matProj = new Mat4();
let _matViewProj = new Mat4();
let _matInvViewProj = new Mat4();
let _tmp_v3 = new Vec3();
let _tmp2_v3 = new Vec3();
/**
* A representation of a camera instance
*/
export default class Camera {
_poolID = -1;
_node = null;
_projection = enums.PROJ_PERSPECTIVE;
// priority. the smaller one will be rendered first
_priority = 0;
// clear options
_color = new Vec4(0.2, 0.3, 0.47, 1);
_depth = 1;
_stencil = 0;
_clearFlags = enums.CLEAR_COLOR | enums.CLEAR_DEPTH;
_clearModel = null;
// stages & framebuffer
_stages = [];
_framebuffer = null;
// projection properties
_near = 0.01;
_far = 1000.0;
_fov = Math.PI / 4.0; // vertical fov
_rect = {
x: 0, y: 0, w: 1, h: 1
};
// ortho properties
_orthoHeight = 10;
_cullingMask = 0xffffffff;
// culling mask
get cullingMask () {
return this._cullingMask;
}
set cullingMask (mask) {
this._cullingMask = mask;
}
setCullingMask (mask) {
this._cullingMask = mask;
}
/**
* Get the hosting node of this camera
* @returns {Node} the hosting node
*/
getNode () {
return this._node;
}
/**
* Set the hosting node of this camera
* @param {Node} node the hosting node
*/
setNode (node) {
this._node = node;
}
/**
* Get the projection type of the camera
* @returns {number} camera projection type
*/
getType () {
return this._projection;
}
/**
* Set the projection type of the camera
* @param {number} type camera projection type
*/
setType (type) {
this._projection = type;
}
/**
* Get the priority of the camera
* @returns {number} camera priority
*/
getPriority () {
return this._priority;
}
/**
* Set the priority of the camera
* @param {number} priority camera priority
*/
setPriority (priority) {
this._priority = priority;
}
/**
* Get the orthogonal height of the camera
* @returns {number} camera height
*/
getOrthoHeight () {
return this._orthoHeight;
}
/**
* Set the orthogonal height of the camera
* @param {number} val camera height
*/
setOrthoHeight (val) {
this._orthoHeight = val;
}
/**
* Get the field of view of the camera
* @returns {number} camera field of view
*/
getFov () {
return this._fov;
}
/**
* Set the field of view of the camera
* @param {number} fov camera field of view
*/
setFov (fov) {
this._fov = fov;
}
/**
* Get the near clipping distance of the camera
* @returns {number} camera near clipping distance
*/
getNear () {
return this._near;
}
/**
* Set the near clipping distance of the camera
* @param {number} near camera near clipping distance
*/
setNear (near) {
this._near = near;
}
/**
* Get the far clipping distance of the camera
* @returns {number} camera far clipping distance
*/
getFar () {
return this._far;
}
/**
* Set the far clipping distance of the camera
* @param {number} far camera far clipping distance
*/
setFar (far) {
this._far = far;
}
/**
* Get the clear color of the camera
* @returns {Vec4} out the receiving color vector
*/
getColor (out) {
return Vec4.copy(out, this._color);
}
/**
* Set the clear color of the camera
* @param {number} r red channel of camera clear color
* @param {number} g green channel of camera clear color
* @param {number} b blue channel of camera clear color
* @param {number} a alpha channel of camera clear color
*/
setColor (r, g, b, a) {
Vec4.set(this._color, r, g, b, a);
}
/**
* Get the clear depth of the camera
* @returns {number} camera clear depth
*/
getDepth () {
return this._depth;
}
/**
* Set the clear depth of the camera
* @param {number} depth camera clear depth
*/
setDepth (depth) {
this._depth = depth;
}
/**
* Get the clearing stencil value of the camera
* @returns {number} camera clearing stencil value
*/
getStencil () {
return this._stencil;
}
/**
* Set the clearing stencil value of the camera
* @param {number} stencil camera clearing stencil value
*/
setStencil (stencil) {
this._stencil = stencil;
}
/**
* Get the clearing flags of the camera
* @returns {number} camera clearing flags
*/
getClearFlags () {
return this._clearFlags;
}
/**
* Set the clearing flags of the camera
* @param {number} flags camera clearing flags
*/
setClearFlags (flags) {
this._clearFlags = flags;
}
/**
* Get the rect of the camera
* @param {Object} out the receiving object
* @returns {Object} camera rect
*/
getRect (out) {
out.x = this._rect.x;
out.y = this._rect.y;
out.w = this._rect.w;
out.h = this._rect.h;
return out;
}
/**
* Set the rect of the camera
* @param {Number} x - [0,1]
* @param {Number} y - [0,1]
* @param {Number} w - [0,1]
* @param {Number} h - [0,1]
*/
setRect (x, y, w, h) {
this._rect.x = x;
this._rect.y = y;
this._rect.w = w;
this._rect.h = h;
}
/**
* Get the stages of the camera
* @returns {string[]} camera stages
*/
getStages () {
return this._stages;
}
/**
* Set the stages of the camera
* @param {string[]} stages camera stages
*/
setStages (stages) {
this._stages = stages;
}
/**
* Get the framebuffer of the camera
* @returns {FrameBuffer} camera framebuffer
*/
getFramebuffer () {
return this._framebuffer;
}
/**
* Set the framebuffer of the camera
* @param {FrameBuffer} framebuffer camera framebuffer
*/
setFrameBuffer (framebuffer) {
this._framebuffer = framebuffer;
}
_calcMatrices (width, height) {
// view matrix
this._node.getWorldRT(_matViewInv);
Mat4.invert(_matView, _matViewInv);
// projection matrix
let aspect = width / height;
if (this._projection === enums.PROJ_PERSPECTIVE) {
Mat4.perspective(_matProj,
this._fov,
aspect,
this._near,
this._far
);
} else {
let x = this._orthoHeight * aspect;
let y = this._orthoHeight;
Mat4.ortho(_matProj,
-x, x, -y, y, this._near, this._far
);
}
// view-projection
Mat4.mul(_matViewProj, _matProj, _matView);
// inv view-projection
Mat4.invert(_matInvViewProj, _matViewProj);
}
/**
* extract a view of this camera
* @param {View} out the receiving view
* @param {number} width framebuffer width
* @param {number} height framebuffer height
*/
extractView (out, width, height) {
if (this._framebuffer) {
width = this._framebuffer._width;
height = this._framebuffer._height;
}
// priority
out._priority = this._priority;
// rect
out._rect.x = this._rect.x * width;
out._rect.y = this._rect.y * height;
out._rect.w = this._rect.w * width;
out._rect.h = this._rect.h * height;
// clear opts
this.getColor(out._color);
out._depth = this._depth;
out._stencil = this._stencil;
out._clearFlags = this._clearFlags;
out._clearModel = this._clearModel;
// stages & framebuffer
out._stages = this._stages;
out._framebuffer = this._framebuffer;
this._calcMatrices(width, height);
Mat4.copy(out._matView, _matView);
Mat4.copy(out._matViewInv, _matViewInv);
Mat4.copy(out._matProj, _matProj);
Mat4.copy(out._matViewProj, _matViewProj);
Mat4.copy(out._matInvViewProj, _matInvViewProj);
out._cullingMask = this._cullingMask;
}
/**
* transform a screen position to a world space ray
* @param {number} x the screen x position to be transformed
* @param {number} y the screen y position to be transformed
* @param {number} width framebuffer width
* @param {number} height framebuffer height
* @param {Ray} out the resulting ray
* @returns {Ray} the resulting ray
*/
screenPointToRay (x, y, width, height, out) {
if (!cc.geomUtils) return out;
out = out || new Ray();
this._calcMatrices(width, height);
let cx = this._rect.x * width;
let cy = this._rect.y * height;
let cw = this._rect.w * width;
let ch = this._rect.h * height;
// far plane intersection
Vec3.set(_tmp2_v3, (x - cx) / cw * 2 - 1, (y - cy) / ch * 2 - 1, 1);
Vec3.transformMat4(_tmp2_v3, _tmp2_v3, _matInvViewProj);
if (this._projection === enums.PROJ_PERSPECTIVE) {
// camera origin
this._node.getWorldPosition(_tmp_v3);
} else {
// near plane intersection
Vec3.set(_tmp_v3, (x - cx) / cw * 2 - 1, (y - cy) / ch * 2 - 1, -1);
Vec3.transformMat4(_tmp_v3, _tmp_v3, _matInvViewProj);
}
return Ray.fromPoints(out, _tmp_v3, _tmp2_v3);
}
/**
* transform a screen position to world space
* @param {Vec3} out the resulting vector
* @param {Vec3} screenPos the screen position to be transformed
* @param {number} width framebuffer width
* @param {number} height framebuffer height
* @returns {Vec3} the resulting vector
*/
screenToWorld (out, screenPos, width, height) {
this._calcMatrices(width, height);
let cx = this._rect.x * width;
let cy = this._rect.y * height;
let cw = this._rect.w * width;
let ch = this._rect.h * height;
if (this._projection === enums.PROJ_PERSPECTIVE) {
// calculate screen pos in far clip plane
Vec3.set(out,
(screenPos.x - cx) / cw * 2 - 1,
(screenPos.y - cy) / ch * 2 - 1,
0.9999
);
// transform to world
Vec3.transformMat4(out, out, _matInvViewProj);
// lerp to depth z
this._node.getWorldPosition(_tmp_v3);
Vec3.lerp(out, _tmp_v3, out, lerp(this._near / this._far, 1, screenPos.z));
} else {
Vec3.set(out,
(screenPos.x - cx) / cw * 2 - 1,
(screenPos.y - cy) / ch * 2 - 1,
screenPos.z * 2 - 1
);
// transform to world
Vec3.transformMat4(out, out, _matInvViewProj);
}
return out;
}
/**
* transform a world space position to screen space
* @param {Vec3} out the resulting vector
* @param {Vec3} worldPos the world space position to be transformed
* @param {number} width framebuffer width
* @param {number} height framebuffer height
* @returns {Vec3} the resulting vector
*/
worldToScreen (out, worldPos, width, height) {
this._calcMatrices(width, height);
let cx = this._rect.x * width;
let cy = this._rect.y * height;
let cw = this._rect.w * width;
let ch = this._rect.h * height;
Vec3.transformMat4(out, worldPos, _matViewProj);
out.x = cx + (out.x + 1) * 0.5 * cw;
out.y = cy + (out.y + 1) * 0.5 * ch;
out.z = out.z * 0.5 + 0.5;
return out;
}
/**
* transform a world space matrix to screen space
* @param {Mat4} out the resulting vector
* @param {Mat4} worldMatrix the world space matrix to be transformed
* @param {number} width framebuffer width
* @param {number} height framebuffer height
* @returns {Mat4} the resulting vector
*/
worldMatrixToScreen (out, worldMatrix, width, height) {
this._calcMatrices(width, height);
Mat4.mul(out, _matViewProj, worldMatrix);
let halfWidth = width / 2;
let halfHeight = height / 2;
Mat4.identity(_tmp_mat4);
Mat4.transform(_tmp_mat4, _tmp_mat4, Vec3.set(_tmp_v3, halfWidth, halfHeight, 0));
Mat4.scale(_tmp_mat4, _tmp_mat4, Vec3.set(_tmp_v3, halfWidth, halfHeight, 1));
Mat4.mul(out, _tmp_mat4, out);
return out;
}
}

View File

@@ -0,0 +1,485 @@
// Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
import { Mat4, Mat3, Vec3, toRadian } from '../../core/value-types';
import gfx from '../gfx';
import enums from '../enums';
const _forward = cc.v3(0, 0, -1);
let _m4_tmp = cc.mat4();
let _m3_tmp = Mat3.create();
let _transformedLightDirection = cc.v3(0, 0, 0);
// compute light viewProjMat for shadow.
function _computeSpotLightViewProjMatrix(light, outView, outProj) {
// view matrix
light._node.getWorldRT(outView);
Mat4.invert(outView, outView);
// proj matrix
Mat4.perspective(outProj, light._spotAngle * light._spotAngleScale, 1, light._shadowMinDepth, light._shadowMaxDepth);
}
function _computeDirectionalLightViewProjMatrix(light, outView, outProj) {
// view matrix
light._node.getWorldRT(outView);
Mat4.invert(outView, outView);
// TODO: should compute directional light frustum based on rendered meshes in scene.
// proj matrix
let halfSize = light._shadowFrustumSize / 2;
Mat4.ortho(outProj, -halfSize, halfSize, -halfSize, halfSize, light._shadowMinDepth, light._shadowMaxDepth);
}
function _computePointLightViewProjMatrix(light, outView, outProj) {
// view matrix
light._node.getWorldRT(outView);
Mat4.invert(outView, outView);
// The transformation from Cartesian to polar coordinates is not a linear function,
// so it cannot be achieved by means of a fixed matrix multiplication.
// Here we just use a nearly 180 degree perspective matrix instead.
Mat4.perspective(outProj, toRadian(179), 1, light._shadowMinDepth, light._shadowMaxDepth);
}
/**
* A representation of a light source.
* Could be a point light, a spot light or a directional light.
*/
export default class Light {
/**
* Setup a default directional light with no shadows
*/
constructor() {
this._poolID = -1;
this._node = null;
this._type = enums.LIGHT_DIRECTIONAL;
this._color = new Vec3(1, 1, 1);
this._intensity = 1;
// used for spot and point light
this._range = 1;
// used for spot light, default to 60 degrees
this._spotAngle = toRadian(60);
this._spotExp = 1;
// cached for uniform
this._directionUniform = new Float32Array(3);
this._positionUniform = new Float32Array(3);
this._colorUniform = new Float32Array([this._color.x * this._intensity, this._color.y * this._intensity, this._color.z * this._intensity]);
this._spotUniform = new Float32Array([Math.cos(this._spotAngle * 0.5), this._spotExp]);
// shadow params
this._shadowType = enums.SHADOW_NONE;
this._shadowFrameBuffer = null;
this._shadowMap = null;
this._shadowMapDirty = false;
this._shadowDepthBuffer = null;
this._shadowResolution = 1024;
this._shadowBias = 0.0005;
this._shadowDarkness = 1;
this._shadowMinDepth = 1;
this._shadowMaxDepth = 1000;
this._frustumEdgeFalloff = 0; // used by directional and spot light.
this._viewProjMatrix = cc.mat4();
this._spotAngleScale = 1; // used for spot light.
this._shadowFrustumSize = 50; // used for directional light.
}
/**
* Get the hosting node of this camera
* @returns {Node} the hosting node
*/
getNode() {
return this._node;
}
/**
* Set the hosting node of this camera
* @param {Node} node the hosting node
*/
setNode(node) {
this._node = node;
}
/**
* set the color of the light source
* @param {number} r red channel of the light color
* @param {number} g green channel of the light color
* @param {number} b blue channel of the light color
*/
setColor(r, g, b) {
Vec3.set(this._color, r, g, b);
this._colorUniform[0] = r * this._intensity;
this._colorUniform[1] = g * this._intensity;
this._colorUniform[2] = b * this._intensity;
}
/**
* get the color of the light source
* @returns {Vec3} the light color
*/
get color() {
return this._color;
}
/**
* set the intensity of the light source
* @param {number} val the light intensity
*/
setIntensity(val) {
this._intensity = val;
this._colorUniform[0] = val * this._color.x;
this._colorUniform[1] = val * this._color.y;
this._colorUniform[2] = val * this._color.z;
}
/**
* get the intensity of the light source
* @returns {number} the light intensity
*/
get intensity() {
return this._intensity;
}
/**
* set the type of the light source
* @param {number} type light source type
*/
setType(type) {
this._type = type;
}
/**
* get the type of the light source
* @returns {number} light source type
*/
get type() {
return this._type;
}
/**
* set the spot light angle
* @param {number} val spot light angle
*/
setSpotAngle(val) {
this._spotAngle = val;
this._spotUniform[0] = Math.cos(this._spotAngle * 0.5);
}
/**
* get the spot light angle
* @returns {number} spot light angle
*/
get spotAngle() {
return this._spotAngle;
}
/**
* set the spot light exponential
* @param {number} val spot light exponential
*/
setSpotExp(val) {
this._spotExp = val;
this._spotUniform[1] = val;
}
/**
* get the spot light exponential
* @returns {number} spot light exponential
*/
get spotExp() {
return this._spotExp;
}
/**
* set the range of the light source
* @param {number} val light source range
*/
setRange(val) {
this._range = val;
}
/**
* get the range of the light source
* @returns {number} range of the light source
*/
get range() {
return this._range;
}
/**
* set the shadow type of the light source
* @param {number} type light source shadow type
*/
setShadowType(type) {
if (this._shadowType === enums.SHADOW_NONE && type !== enums.SHADOW_NONE) {
this._shadowMapDirty = true;
}
this._shadowType = type;
}
/**
* get the shadow type of the light source
* @returns {number} light source shadow type
*/
get shadowType() {
return this._shadowType;
}
/**
* get the shadowmap of the light source
* @returns {Texture2D} light source shadowmap
*/
get shadowMap() {
return this._shadowMap;
}
/**
* get the view-projection matrix of the light source
* @returns {Mat4} light source view-projection matrix
*/
get viewProjMatrix() {
return this._viewProjMatrix;
}
/**
* set the shadow resolution of the light source
* @param {number} val light source shadow resolution
*/
setShadowResolution(val) {
if (this._shadowResolution !== val) {
this._shadowMapDirty = true;
}
this._shadowResolution = val;
}
/**
* get the shadow resolution of the light source
* @returns {number} light source shadow resolution
*/
get shadowResolution() {
return this._shadowResolution;
}
/**
* set the shadow bias of the light source
* @param {number} val light source shadow bias
*/
setShadowBias(val) {
this._shadowBias = val;
}
/**
* get the shadow bias of the light source
* @returns {number} light source shadow bias
*/
get shadowBias() {
return this._shadowBias;
}
/**
* set the shadow darkness of the light source
* @param {number} val light source shadow darkness
*/
setShadowDarkness(val) {
this._shadowDarkness = val;
}
/**
* get the shadow darkness of the light source
* @returns {number} light source shadow darkness
*/
get shadowDarkness() {
return this._shadowDarkness;
}
/**
* set the shadow min depth of the light source
* @param {number} val light source shadow min depth
*/
setShadowMinDepth(val) {
this._shadowMinDepth = val;
}
/**
* get the shadow min depth of the light source
* @returns {number} light source shadow min depth
*/
get shadowMinDepth() {
if (this._type === enums.LIGHT_DIRECTIONAL) {
return 1.0;
}
return this._shadowMinDepth;
}
/**
* set the shadow max depth of the light source
* @param {number} val light source shadow max depth
*/
setShadowMaxDepth(val) {
this._shadowMaxDepth = val;
}
/**
* get the shadow max depth of the light source
* @returns {number} light source shadow max depth
*/
get shadowMaxDepth() {
if (this._type === enums.LIGHT_DIRECTIONAL) {
return 1.0;
}
return this._shadowMaxDepth;
}
/**
* set the frustum edge falloff of the light source
* @param {number} val light source frustum edge falloff
*/
setFrustumEdgeFalloff(val) {
this._frustumEdgeFalloff = val;
}
/**
* get the frustum edge falloff of the light source
* @returns {number} light source frustum edge falloff
*/
get frustumEdgeFalloff() {
return this._frustumEdgeFalloff;
}
/**
* set the shadow frustum size of the light source
* @param {number} val light source shadow frustum size
*/
setShadowFrustumSize(val) {
this._shadowFrustumSize = val;
}
/**
* get the shadow frustum size of the light source
* @returns {number} light source shadow frustum size
*/
get shadowFrustumSize() {
return this._shadowFrustumSize;
}
/**
* extract a view of this light source
* @param {View} out the receiving view
* @param {string[]} stages the stages using the view
*/
extractView(out, stages) {
// TODO: view should not handle light.
out._shadowLight = this;
// priority. TODO: use varying value for shadow view?
out._priority = -1;
// rect
out._rect.x = 0;
out._rect.y = 0;
out._rect.w = this._shadowResolution;
out._rect.h = this._shadowResolution;
// clear opts
Vec3.set(out._color, 1, 1, 1);
out._depth = 1;
out._stencil = 1;
out._clearFlags = enums.CLEAR_COLOR | enums.CLEAR_DEPTH;
// stages & framebuffer
out._stages = stages;
out._framebuffer = this._shadowFrameBuffer;
// view projection matrix
switch(this._type) {
case enums.LIGHT_SPOT:
_computeSpotLightViewProjMatrix(this, out._matView, out._matProj);
break;
case enums.LIGHT_DIRECTIONAL:
_computeDirectionalLightViewProjMatrix(this, out._matView, out._matProj);
break;
case enums.LIGHT_POINT:
_computePointLightViewProjMatrix(this, out._matView, out._matProj);
break;
case enums.LIGHT_AMBIENT:
break;
default:
console.warn('shadow of this light type is not supported');
}
// view-projection
Mat4.mul(out._matViewProj, out._matProj, out._matView);
this._viewProjMatrix = out._matViewProj;
Mat4.invert(out._matInvViewProj, out._matViewProj);
// update view's frustum
// out._frustum.update(out._matViewProj, out._matInvViewProj);
out._cullingMask = 0xffffffff;
}
_updateLightPositionAndDirection() {
this._node.getWorldMatrix(_m4_tmp);
Mat3.fromMat4(_m3_tmp, _m4_tmp);
Vec3.transformMat3(_transformedLightDirection, _forward, _m3_tmp);
Vec3.toArray(this._directionUniform, _transformedLightDirection);
let pos = this._positionUniform;
let m = _m4_tmp.m;
pos[0] = m[12];
pos[1] = m[13];
pos[2] = m[14];
}
_generateShadowMap(device) {
this._shadowMap = new gfx.Texture2D(device, {
width: this._shadowResolution,
height: this._shadowResolution,
format: gfx.TEXTURE_FMT_RGBA8,
wrapS: gfx.WRAP_CLAMP,
wrapT: gfx.WRAP_CLAMP,
});
this._shadowDepthBuffer = new gfx.RenderBuffer(device,
gfx.RB_FMT_D16,
this._shadowResolution,
this._shadowResolution
);
this._shadowFrameBuffer = new gfx.FrameBuffer(device, this._shadowResolution, this._shadowResolution, {
colors: [this._shadowMap],
depth: this._shadowDepthBuffer,
});
}
_destroyShadowMap() {
if (this._shadowMap) {
this._shadowMap.destroy();
this._shadowDepthBuffer.destroy();
this._shadowFrameBuffer.destroy();
this._shadowMap = null;
this._shadowDepthBuffer = null;
this._shadowFrameBuffer = null;
}
}
/**
* update the light source
* @param {Device} device the rendering device
*/
update(device) {
this._updateLightPositionAndDirection();
if (this._shadowType === enums.SHADOW_NONE) {
this._destroyShadowMap();
} else if (this._shadowMapDirty) {
this._destroyShadowMap();
this._generateShadowMap(device);
this._shadowMapDirty = false;
}
}
}

View File

@@ -0,0 +1,65 @@
// Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
/**
* A representation of a model
*/
export default class Model {
/**
* Setup a default empty model
*/
constructor() {
this._type = 'default';
this._poolID = -1;
this._node = null;
this._inputAssembler = null;
this._effect = null;
this._viewID = -1;
this._cameraID = -1;
this._userKey = -1;
this._castShadow = false;
this._boundingShape = null;
}
/**
* Set the hosting node of this model
* @param {Node} node the hosting node
*/
setNode(node) {
this._node = node;
}
/**
* Set the input assembler
* @param {InputAssembler} ia
*/
setInputAssembler(ia) {
this._inputAssembler = ia;
}
/**
* Set the model effect
* @param {?Effect} effect the effect to use
*/
setEffect(effect) {
this._effect = effect;
}
/**
* Set the user key
* @param {number} key
*/
setUserKey(key) {
this._userKey = key;
}
/**
* Extract a drawing item
* @param {Object} out the receiving item
*/
extractDrawItem(out) {
out.model = this;
out.node = this._node;
out.ia = this._inputAssembler;
out.effect = this._effect;
}
}

View File

@@ -0,0 +1,181 @@
// Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
import { FixedArray } from '../memop';
/**
* A representation of the scene
*/
class Scene {
/**
* Setup a default empty scene
*/
constructor(app) {
this._lights = new FixedArray(16);
this._models = new FixedArray(16);
this._cameras = new FixedArray(16);
this._debugCamera = null;
this._app = app;
// NOTE: we don't use pool for views (because it's less changed and it doesn't have poolID)
this._views = [];
}
_add(pool, item) {
if (item._poolID !== -1) {
return;
}
pool.push(item);
item._poolID = pool.length - 1;
}
_remove(pool, item) {
if (item._poolID === -1) {
return;
}
pool.data[pool.length-1]._poolID = item._poolID;
pool.fastRemove(item._poolID);
item._poolID = -1;
}
/**
* reset the model viewIDs
*/
reset() {
for (let i = 0; i < this._models.length; ++i) {
let model = this._models.data[i];
model._viewID = -1;
}
}
/**
* Set the debug camera
* @param {Camera} cam the debug camera
*/
setDebugCamera(cam) {
this._debugCamera = cam;
}
/**
* Get the count of registered cameras
* @returns {number} camera count
*/
getCameraCount() {
return this._cameras.length;
}
/**
* Get the specified camera
* @param {number} idx camera index
* @returns {Camera} the specified camera
*/
getCamera(idx) {
return this._cameras.data[idx];
}
/**
* register a camera
* @param {Camera} camera the new camera
*/
addCamera(camera) {
this._add(this._cameras, camera);
}
/**
* remove a camera
* @param {Camera} camera the camera to be removed
*/
removeCamera(camera) {
this._remove(this._cameras, camera);
}
/**
* Get the count of registered model
* @returns {number} model count
*/
getModelCount() {
return this._models.length;
}
/**
* Get the specified model
* @param {number} idx model index
* @returns {Model} the specified model
*/
getModel(idx) {
return this._models.data[idx];
}
/**
* register a model
* @param {Model} model the new model
*/
addModel(model) {
this._add(this._models, model);
}
/**
* remove a model
* @param {Model} model the model to be removed
*/
removeModel(model) {
this._remove(this._models, model);
}
/**
* Get the count of registered light
* @returns {number} light count
*/
getLightCount() {
return this._lights.length;
}
/**
* Get the specified light
* @param {number} idx light index
* @returns {Light} the specified light
*/
getLight(idx) {
return this._lights.data[idx];
}
/**
* register a light
* @param {Light} light the new light
*/
addLight(light) {
this._add(this._lights, light);
}
/**
* remove a light
* @param {Light} light the light to be removed
*/
removeLight(light) {
this._remove(this._lights, light);
}
/**
* register a view
* @param {View} view the new view
*/
addView(view) {
if (this._views.indexOf(view) === -1) {
this._views.push(view);
}
}
/**
* remove a view
* @param {View} view the view to be removed
*/
removeView(view) {
let idx = this._views.indexOf(view);
if (idx !== -1) {
this._views.splice(idx, 1);
}
}
}
export default Scene;

View File

@@ -0,0 +1,117 @@
import enums from './enums';
import { Vec2, Vec3, Vec4, Color, Mat4 } from '../core/value-types';
import Texture2D from '../core/assets/CCTexture2D';
let gfxTexture2D = null, gfxTextureCube = null;
if (CC_JSB && CC_NATIVERENDERER) {
gfxTexture2D = gfx.Texture2D;
} else {
gfxTexture2D = require('./gfx/texture-2d');
}
const CCObject = cc.Object;
let ctor2default = {
[Boolean]: v => v || false,
[Number]: v => v ? (ArrayBuffer.isView(v) ? v[0] : v) : 0,
[Vec2]: v => v ? cc.v2(v[0], v[1]) : cc.v2(),
[Vec3]: v => v ? cc.v3(v[0], v[1], v[2]) : cc.v3(),
[Vec4]: v => v ? cc.v4(v[0], v[1], v[2], v[3]) : cc.v4(),
[Color]: v => v ? cc.color(v[0] * 255, v[1] * 255, v[2] * 255,
(v[3] || 1) * 255) : cc.color(),
[Mat4]: v => v ? cc.mat4(
v[0], v[1], v[2], v[3],
v[4], v[5], v[6], v[7],
v[8], v[9], v[10], v[11],
v[12], v[13], v[14], v[15],
) : cc.mat4(),
[Texture2D]: () => null,
[CCObject]: () => null
};
let enums2ctor = {
[enums.PARAM_INT]: Number,
[enums.PARAM_INT2]: Vec2,
[enums.PARAM_INT3]: Vec3,
[enums.PARAM_INT4]: Vec4,
[enums.PARAM_FLOAT]: Number,
[enums.PARAM_FLOAT2]: Vec2,
[enums.PARAM_FLOAT3]: Vec3,
[enums.PARAM_FLOAT4]: Vec4,
[enums.PARAM_MAT4]: Mat4,
[enums.PARAM_TEXTURE_2D]: Texture2D,
color: Color,
number: Number,
boolean: Boolean,
default: CCObject
};
export let ctor2enums = {
[Number]: enums.PARAM_FLOAT,
[Vec2]: enums.PARAM_FLOAT2,
[Vec3]: enums.PARAM_FLOAT3,
[Vec4]: enums.PARAM_FLOAT4,
[Color]: enums.PARAM_COLOR3,
[Color]: enums.PARAM_COLOR4,
[Mat4]: enums.PARAM_MAT4,
[Texture2D]: enums.PARAM_TEXTURE_2D,
[gfxTexture2D]: enums.PARAM_TEXTURE_2D,
};
export let enums2default = {
[enums.PARAM_INT]: new Uint32Array([0]),
[enums.PARAM_INT2]: new Uint32Array([0, 0]),
[enums.PARAM_INT3]: new Uint32Array([0, 0, 0]),
[enums.PARAM_INT4]: new Uint32Array([0, 0, 0, 0]),
[enums.PARAM_FLOAT]: new Float32Array([0]),
[enums.PARAM_FLOAT2]: new Float32Array([0, 0]),
[enums.PARAM_FLOAT3]: new Float32Array([0, 0, 0]),
[enums.PARAM_FLOAT4]: new Float32Array([0, 0, 0, 0]),
[enums.PARAM_MAT4]: new Float32Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
[enums.PARAM_TEXTURE_2D]: null,
number: 0,
boolean: false,
}
export let getInstanceType = function (t) {
return enums2ctor[t] || enums2ctor.default;
};
export let getInstanceCtor = function (t) {
return ctor2default[getInstanceType(t)];
};
export let getClassName = function (t) {
return cc.js.getClassName(getInstanceType(t));
};
let className2InspectorName = {
Number: 'number',
Boolean: 'boolean'
};
export function getInspectorProps (prop) {
let editor = {
type: prop.type
};
Object.assign(editor, prop.editor || prop.inspector);
editor.defines = prop.defines;
editor.value = getInstanceCtor(editor.type)(prop.value);
if (prop.range) {
editor.range = prop.range;
}
let className = getClassName(editor.type);
editor.typeName = className2InspectorName[className] || className;
editor.valueCtor = enums2ctor[editor.type];
if (editor.typeName == 'cc.Texture2D') {
editor.typeName = 'cc.Asset';
editor.assetType = 'cc.Texture2D';
}
return editor;
};