blob: 661eb2bfcaffd558d4433f1692002a29c69bb6bb [file] [log] [blame]
// Copyright 2018 The Immersive Web Community Group
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import {CAP, MAT_STATE, RENDER_ORDER, stateToBlendFunc} from './material.js';
import {Node} from './node.js';
import {Program} from './program.js';
import {DataTexture, VideoTexture} from './texture.js';
import {mat4, vec3} from '../math/gl-matrix.js';
export const ATTRIB = {
POSITION: 1,
NORMAL: 2,
TANGENT: 3,
TEXCOORD_0: 4,
TEXCOORD_1: 5,
COLOR_0: 6,
};
export const ATTRIB_MASK = {
POSITION: 0x0001,
NORMAL: 0x0002,
TANGENT: 0x0004,
TEXCOORD_0: 0x0008,
TEXCOORD_1: 0x0010,
COLOR_0: 0x0020,
};
const GL = WebGLRenderingContext; // For enums
const DEF_LIGHT_DIR = new Float32Array([-0.1, -1.0, -0.2]);
const DEF_LIGHT_COLOR = new Float32Array([3.0, 3.0, 3.0]);
const PRECISION_REGEX = new RegExp('precision (lowp|mediump|highp) float;');
const VERTEX_SHADER_SINGLE_ENTRY = `
uniform mat4 PROJECTION_MATRIX, VIEW_MATRIX, MODEL_MATRIX;
void main() {
gl_Position = vertex_main(PROJECTION_MATRIX, VIEW_MATRIX, MODEL_MATRIX);
}
`;
const VERTEX_SHADER_MULTI_ENTRY = `
#ERROR Multiview rendering is not implemented
void main() {
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
}
`;
const FRAGMENT_SHADER_ENTRY = `
void main() {
gl_FragColor = fragment_main();
}
`;
function isPowerOfTwo(n) {
return (n & (n - 1)) === 0;
}
// Creates a WebGL context and initializes it with some common default state.
export function createWebGLContext(glAttribs) {
glAttribs = glAttribs || {alpha: false};
let webglCanvas = document.createElement('canvas');
let contextTypes = glAttribs.webgl2 ? ['webgl2'] : ['webgl', 'experimental-webgl'];
let context = null;
for (let contextType of contextTypes) {
context = webglCanvas.getContext(contextType, glAttribs);
if (context) {
break;
}
}
if (!context) {
let webglType = (glAttribs.webgl2 ? 'WebGL 2' : 'WebGL');
console.error('This browser does not support ' + webglType + '.');
return null;
}
return context;
}
export class RenderView {
constructor(projectionMatrix, viewMatrix, viewport = null, eye = 'left') {
this.projectionMatrix = projectionMatrix;
this.viewMatrix = viewMatrix;
this.viewport = viewport;
// If an eye isn't given the left eye is assumed.
this._eye = eye;
this._eyeIndex = (eye == 'left' ? 0 : 1);
}
get eye() {
return this._eye;
}
set eye(value) {
this._eye = value;
this._eyeIndex = (value == 'left' ? 0 : 1);
}
get eyeIndex() {
return this._eyeIndex;
}
}
class RenderBuffer {
constructor(target, usage, buffer, length = 0) {
this._target = target;
this._usage = usage;
this._length = length;
if (buffer instanceof Promise) {
this._buffer = null;
this._promise = buffer.then((buffer) => {
this._buffer = buffer;
return this;
});
} else {
this._buffer = buffer;
this._promise = Promise.resolve(this);
}
}
waitForComplete() {
return this._promise;
}
}
class RenderPrimitiveAttribute {
constructor(primitiveAttribute) {
this._attrib_index = ATTRIB[primitiveAttribute.name];
this._componentCount = primitiveAttribute.componentCount;
this._componentType = primitiveAttribute.componentType;
this._stride = primitiveAttribute.stride;
this._byteOffset = primitiveAttribute.byteOffset;
this._normalized = primitiveAttribute.normalized;
}
}
class RenderPrimitiveAttributeBuffer {
constructor(buffer) {
this._buffer = buffer;
this._attributes = [];
}
}
class RenderPrimitive {
constructor(primitive) {
this._activeFrameId = 0;
this._instances = [];
this._material = null;
this.setPrimitive(primitive);
}
setPrimitive(primitive) {
this._mode = primitive.mode;
this._elementCount = primitive.elementCount;
this._promise = null;
this._vao = null;
this._complete = false;
this._attributeBuffers = [];
this._attributeMask = 0;
for (let attribute of primitive.attributes) {
this._attributeMask |= ATTRIB_MASK[attribute.name];
let renderAttribute = new RenderPrimitiveAttribute(attribute);
let foundBuffer = false;
for (let attributeBuffer of this._attributeBuffers) {
if (attributeBuffer._buffer == attribute.buffer) {
attributeBuffer._attributes.push(renderAttribute);
foundBuffer = true;
break;
}
}
if (!foundBuffer) {
let attributeBuffer = new RenderPrimitiveAttributeBuffer(attribute.buffer);
attributeBuffer._attributes.push(renderAttribute);
this._attributeBuffers.push(attributeBuffer);
}
}
this._indexBuffer = null;
this._indexByteOffset = 0;
this._indexType = 0;
if (primitive.indexBuffer) {
this._indexByteOffset = primitive.indexByteOffset;
this._indexType = primitive.indexType;
this._indexBuffer = primitive.indexBuffer;
}
if (primitive._min) {
this._min = vec3.clone(primitive._min);
this._max = vec3.clone(primitive._max);
} else {
this._min = null;
this._max = null;
}
if (this._material != null) {
this.waitForComplete(); // To flip the _complete flag.
}
}
setRenderMaterial(material) {
this._material = material;
this._promise = null;
this._complete = false;
if (this._material != null) {
this.waitForComplete(); // To flip the _complete flag.
}
}
markActive(frameId) {
if (this._complete && this._activeFrameId != frameId) {
if (this._material) {
if (!this._material.markActive(frameId)) {
return;
}
}
this._activeFrameId = frameId;
}
}
get samplers() {
return this._material._samplerDictionary;
}
get uniforms() {
return this._material._uniform_dictionary;
}
waitForComplete() {
if (!this._promise) {
if (!this._material) {
return Promise.reject('RenderPrimitive does not have a material');
}
let completionPromises = [];
for (let attributeBuffer of this._attributeBuffers) {
if (!attributeBuffer._buffer._buffer) {
completionPromises.push(attributeBuffer._buffer._promise);
}
}
if (this._indexBuffer && !this._indexBuffer._buffer) {
completionPromises.push(this._indexBuffer._promise);
}
this._promise = Promise.all(completionPromises).then(() => {
this._complete = true;
return this;
});
}
return this._promise;
}
}
export class RenderTexture {
constructor(texture) {
this._texture = texture;
this._complete = false;
this._activeFrameId = 0;
this._activeCallback = null;
}
markActive(frameId) {
if (this._activeCallback && this._activeFrameId != frameId) {
this._activeFrameId = frameId;
this._activeCallback(this);
}
}
}
const inverseMatrix = mat4.create();
function setCap(gl, glEnum, cap, prevState, state) {
let change = (state & cap) - (prevState & cap);
if (!change) {
return;
}
if (change > 0) {
gl.enable(glEnum);
} else {
gl.disable(glEnum);
}
}
class RenderMaterialSampler {
constructor(renderer, materialSampler, index) {
this._renderer = renderer;
this._uniformName = materialSampler._uniformName;
this._renderTexture = renderer._getRenderTexture(materialSampler._texture);
this._index = index;
}
set texture(value) {
this._renderTexture = this._renderer._getRenderTexture(value);
}
}
class RenderMaterialUniform {
constructor(materialUniform) {
this._uniformName = materialUniform._uniformName;
this._uniform = null;
this._length = materialUniform._length;
if (materialUniform._value instanceof Array) {
this._value = new Float32Array(materialUniform._value);
} else {
this._value = new Float32Array([materialUniform._value]);
}
}
set value(value) {
if (this._value.length == 1) {
this._value[0] = value;
} else {
for (let i = 0; i < this._value.length; ++i) {
this._value[i] = value[i];
}
}
}
}
class RenderMaterial {
constructor(renderer, material, program) {
this._program = program;
this._state = material.state._state;
this._activeFrameId = 0;
this._completeForActiveFrame = false;
this._samplerDictionary = {};
this._samplers = [];
for (let i = 0; i < material._samplers.length; ++i) {
let renderSampler = new RenderMaterialSampler(renderer, material._samplers[i], i);
this._samplers.push(renderSampler);
this._samplerDictionary[renderSampler._uniformName] = renderSampler;
}
this._uniform_dictionary = {};
this._uniforms = [];
for (let uniform of material._uniforms) {
let renderUniform = new RenderMaterialUniform(uniform);
this._uniforms.push(renderUniform);
this._uniform_dictionary[renderUniform._uniformName] = renderUniform;
}
this._firstBind = true;
this._renderOrder = material.renderOrder;
if (this._renderOrder == RENDER_ORDER.DEFAULT) {
if (this._state & CAP.BLEND) {
this._renderOrder = RENDER_ORDER.TRANSPARENT;
} else {
this._renderOrder = RENDER_ORDER.OPAQUE;
}
}
}
bind(gl) {
// First time we do a binding, cache the uniform locations and remove
// unused uniforms from the list.
if (this._firstBind) {
for (let i = 0; i < this._samplers.length;) {
let sampler = this._samplers[i];
if (!this._program.uniform[sampler._uniformName]) {
this._samplers.splice(i, 1);
continue;
}
++i;
}
for (let i = 0; i < this._uniforms.length;) {
let uniform = this._uniforms[i];
uniform._uniform = this._program.uniform[uniform._uniformName];
if (!uniform._uniform) {
this._uniforms.splice(i, 1);
continue;
}
++i;
}
this._firstBind = false;
}
for (let sampler of this._samplers) {
gl.activeTexture(gl.TEXTURE0 + sampler._index);
if (sampler._renderTexture && sampler._renderTexture._complete) {
gl.bindTexture(gl.TEXTURE_2D, sampler._renderTexture._texture);
} else {
gl.bindTexture(gl.TEXTURE_2D, null);
}
}
for (let uniform of this._uniforms) {
switch (uniform._length) {
case 1: gl.uniform1fv(uniform._uniform, uniform._value); break;
case 2: gl.uniform2fv(uniform._uniform, uniform._value); break;
case 3: gl.uniform3fv(uniform._uniform, uniform._value); break;
case 4: gl.uniform4fv(uniform._uniform, uniform._value); break;
}
}
}
markActive(frameId) {
if (this._activeFrameId != frameId) {
this._activeFrameId = frameId;
this._completeForActiveFrame = true;
for (let i = 0; i < this._samplers.length; ++i) {
let sampler = this._samplers[i];
if (sampler._renderTexture) {
if (!sampler._renderTexture._complete) {
this._completeForActiveFrame = false;
break;
}
sampler._renderTexture.markActive(frameId);
}
}
}
return this._completeForActiveFrame;
}
// Material State fetchers
get cullFace() {
return !!(this._state & CAP.CULL_FACE);
}
get blend() {
return !!(this._state & CAP.BLEND);
}
get depthTest() {
return !!(this._state & CAP.DEPTH_TEST);
}
get stencilTest() {
return !!(this._state & CAP.STENCIL_TEST);
}
get colorMask() {
return !!(this._state & CAP.COLOR_MASK);
}
get depthMask() {
return !!(this._state & CAP.DEPTH_MASK);
}
get stencilMask() {
return !!(this._state & CAP.STENCIL_MASK);
}
get depthFunc() {
return ((this._state & MAT_STATE.DEPTH_FUNC_RANGE) >> MAT_STATE.DEPTH_FUNC_SHIFT) + GL.NEVER;
}
get blendFuncSrc() {
return stateToBlendFunc(this._state, MAT_STATE.BLEND_SRC_RANGE, MAT_STATE.BLEND_SRC_SHIFT);
}
get blendFuncDst() {
return stateToBlendFunc(this._state, MAT_STATE.BLEND_DST_RANGE, MAT_STATE.BLEND_DST_SHIFT);
}
// Only really for use from the renderer
_capsDiff(otherState) {
return (otherState & MAT_STATE.CAPS_RANGE) ^ (this._state & MAT_STATE.CAPS_RANGE);
}
_blendDiff(otherState) {
if (!(this._state & CAP.BLEND)) {
return 0;
}
return (otherState & MAT_STATE.BLEND_FUNC_RANGE) ^ (this._state & MAT_STATE.BLEND_FUNC_RANGE);
}
_depthFuncDiff(otherState) {
if (!(this._state & CAP.DEPTH_TEST)) {
return 0;
}
return (otherState & MAT_STATE.DEPTH_FUNC_RANGE) ^ (this._state & MAT_STATE.DEPTH_FUNC_RANGE);
}
}
export class Renderer {
constructor(gl) {
this._gl = gl || createWebGLContext();
this._frameId = 0;
this._programCache = {};
this._textureCache = {};
this._renderPrimitives = Array(RENDER_ORDER.DEFAULT);
this._cameraPositions = [];
this._vaoExt = gl.getExtension('OES_vertex_array_object');
let fragHighPrecision = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT);
this._defaultFragPrecision = fragHighPrecision.precision > 0 ? 'highp' : 'mediump';
this._depthMaskNeedsReset = false;
this._colorMaskNeedsReset = false;
this._globalLightColor = vec3.clone(DEF_LIGHT_COLOR);
this._globalLightDir = vec3.clone(DEF_LIGHT_DIR);
}
get gl() {
return this._gl;
}
set globalLightColor(value) {
vec3.copy(this._globalLightColor, value);
}
get globalLightColor() {
return vec3.clone(this._globalLightColor);
}
set globalLightDir(value) {
vec3.copy(this._globalLightDir, value);
}
get globalLightDir() {
return vec3.clone(this._globalLightDir);
}
createRenderBuffer(target, data, usage = GL.STATIC_DRAW) {
let gl = this._gl;
let glBuffer = gl.createBuffer();
if (data instanceof Promise) {
let renderBuffer = new RenderBuffer(target, usage, data.then((data) => {
gl.bindBuffer(target, glBuffer);
gl.bufferData(target, data, usage);
renderBuffer._length = data.byteLength;
return glBuffer;
}));
return renderBuffer;
} else {
gl.bindBuffer(target, glBuffer);
gl.bufferData(target, data, usage);
return new RenderBuffer(target, usage, glBuffer, data.byteLength);
}
}
updateRenderBuffer(buffer, data, offset = 0) {
if (buffer._buffer) {
let gl = this._gl;
gl.bindBuffer(buffer._target, buffer._buffer);
if (offset == 0 && buffer._length == data.byteLength) {
gl.bufferData(buffer._target, data, buffer._usage);
} else {
gl.bufferSubData(buffer._target, offset, data);
}
} else {
buffer.waitForComplete().then((buffer) => {
this.updateRenderBuffer(buffer, data, offset);
});
}
}
createRenderPrimitive(primitive, material) {
let renderPrimitive = new RenderPrimitive(primitive);
let program = this._getMaterialProgram(material, renderPrimitive);
let renderMaterial = new RenderMaterial(this, material, program);
renderPrimitive.setRenderMaterial(renderMaterial);
if (!this._renderPrimitives[renderMaterial._renderOrder]) {
this._renderPrimitives[renderMaterial._renderOrder] = [];
}
this._renderPrimitives[renderMaterial._renderOrder].push(renderPrimitive);
return renderPrimitive;
}
createMesh(primitive, material) {
let meshNode = new Node();
meshNode.addRenderPrimitive(this.createRenderPrimitive(primitive, material));
return meshNode;
}
drawViews(views, rootNode) {
if (!rootNode) {
return;
}
let gl = this._gl;
this._frameId++;
rootNode.markActive(this._frameId);
// If there's only one view then flip the algorithm a bit so that we're only
// setting the viewport once.
if (views.length == 1 && views[0].viewport) {
let vp = views[0].viewport;
this._gl.viewport(vp.x, vp.y, vp.width, vp.height);
}
// Get the positions of the 'camera' for each view matrix.
for (let i = 0; i < views.length; ++i) {
mat4.invert(inverseMatrix, views[i].viewMatrix);
if (this._cameraPositions.length <= i) {
this._cameraPositions.push(vec3.create());
}
let cameraPosition = this._cameraPositions[i];
vec3.set(cameraPosition, 0, 0, 0);
vec3.transformMat4(cameraPosition, cameraPosition, inverseMatrix);
}
// Draw each set of render primitives in order
for (let renderPrimitives of this._renderPrimitives) {
if (renderPrimitives && renderPrimitives.length) {
this._drawRenderPrimitiveSet(views, renderPrimitives);
}
}
if (this._vaoExt) {
this._vaoExt.bindVertexArrayOES(null);
}
if (this._depthMaskNeedsReset) {
gl.depthMask(true);
}
if (this._colorMaskNeedsReset) {
gl.colorMask(true, true, true, true);
}
}
_drawRenderPrimitiveSet(views, renderPrimitives) {
let gl = this._gl;
let program = null;
let material = null;
let attribMask = 0;
// Loop through every primitive known to the renderer.
for (let primitive of renderPrimitives) {
// Skip over those that haven't been marked as active for this frame.
if (primitive._activeFrameId != this._frameId) {
continue;
}
// Bind the primitive material's program if it's different than the one we
// were using for the previous primitive.
// TODO: The ording of this could be more efficient.
if (program != primitive._material._program) {
program = primitive._material._program;
program.use();
if (program.uniform.LIGHT_DIRECTION) {
gl.uniform3fv(program.uniform.LIGHT_DIRECTION, this._globalLightDir);
}
if (program.uniform.LIGHT_COLOR) {
gl.uniform3fv(program.uniform.LIGHT_COLOR, this._globalLightColor);
}
if (views.length == 1) {
gl.uniformMatrix4fv(program.uniform.PROJECTION_MATRIX, false, views[0].projectionMatrix);
gl.uniformMatrix4fv(program.uniform.VIEW_MATRIX, false, views[0].viewMatrix);
gl.uniform3fv(program.uniform.CAMERA_POSITION, this._cameraPositions[0]);
gl.uniform1i(program.uniform.EYE_INDEX, views[0].eyeIndex);
}
}
if (material != primitive._material) {
this._bindMaterialState(primitive._material, material);
primitive._material.bind(gl, program, material);
material = primitive._material;
}
if (this._vaoExt) {
if (primitive._vao) {
this._vaoExt.bindVertexArrayOES(primitive._vao);
} else {
primitive._vao = this._vaoExt.createVertexArrayOES();
this._vaoExt.bindVertexArrayOES(primitive._vao);
this._bindPrimitive(primitive);
}
} else {
this._bindPrimitive(primitive, attribMask);
attribMask = primitive._attributeMask;
}
for (let i = 0; i < views.length; ++i) {
let view = views[i];
if (views.length > 1) {
if (view.viewport) {
let vp = view.viewport;
gl.viewport(vp.x, vp.y, vp.width, vp.height);
}
gl.uniformMatrix4fv(program.uniform.PROJECTION_MATRIX, false, view.projectionMatrix);
gl.uniformMatrix4fv(program.uniform.VIEW_MATRIX, false, view.viewMatrix);
gl.uniform3fv(program.uniform.CAMERA_POSITION, this._cameraPositions[i]);
gl.uniform1i(program.uniform.EYE_INDEX, view.eyeIndex);
}
for (let instance of primitive._instances) {
if (instance._activeFrameId != this._frameId) {
continue;
}
gl.uniformMatrix4fv(program.uniform.MODEL_MATRIX, false, instance.worldMatrix);
if (primitive._indexBuffer) {
gl.drawElements(primitive._mode, primitive._elementCount,
primitive._indexType, primitive._indexByteOffset);
} else {
gl.drawArrays(primitive._mode, 0, primitive._elementCount);
}
}
}
}
}
_getRenderTexture(texture) {
if (!texture) {
return null;
}
let key = texture.textureKey;
if (!key) {
throw new Error('Texure does not have a valid key');
}
if (key in this._textureCache) {
return this._textureCache[key];
} else {
let gl = this._gl;
let textureHandle = gl.createTexture();
let renderTexture = new RenderTexture(textureHandle);
this._textureCache[key] = renderTexture;
if (texture instanceof DataTexture) {
gl.bindTexture(gl.TEXTURE_2D, textureHandle);
gl.texImage2D(gl.TEXTURE_2D, 0, texture.format, texture.width, texture.height,
0, texture.format, texture._type, texture._data);
this._setSamplerParameters(texture);
renderTexture._complete = true;
} else {
texture.waitForComplete().then(() => {
gl.bindTexture(gl.TEXTURE_2D, textureHandle);
gl.texImage2D(gl.TEXTURE_2D, 0, texture.format, texture.format, gl.UNSIGNED_BYTE, texture.source);
this._setSamplerParameters(texture);
renderTexture._complete = true;
if (texture instanceof VideoTexture) {
// Once the video starts playing, set a callback to update it's
// contents each frame.
texture._video.addEventListener('playing', () => {
renderTexture._activeCallback = () => {
if (!texture._video.paused && !texture._video.waiting) {
gl.bindTexture(gl.TEXTURE_2D, textureHandle);
gl.texImage2D(gl.TEXTURE_2D, 0, texture.format, texture.format, gl.UNSIGNED_BYTE, texture.source);
}
};
});
}
});
}
return renderTexture;
}
}
_setSamplerParameters(texture) {
let gl = this._gl;
let sampler = texture.sampler;
let powerOfTwo = isPowerOfTwo(texture.width) && isPowerOfTwo(texture.height);
let mipmap = powerOfTwo && texture.mipmap;
if (mipmap) {
gl.generateMipmap(gl.TEXTURE_2D);
}
let minFilter = sampler.minFilter || (mipmap ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR);
let wrapS = sampler.wrapS || (powerOfTwo ? gl.REPEAT : gl.CLAMP_TO_EDGE);
let wrapT = sampler.wrapT || (powerOfTwo ? gl.REPEAT : gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, sampler.magFilter || gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, minFilter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrapS);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrapT);
}
_getProgramKey(name, defines) {
let key = `${name}:`;
for (let define in defines) {
key += `${define}=${defines[define]},`;
}
return key;
}
_getMaterialProgram(material, renderPrimitive) {
let materialName = material.materialName;
let vertexSource = material.vertexSource;
let fragmentSource = material.fragmentSource;
// These should always be defined for every material
if (materialName == null) {
throw new Error('Material does not have a name');
}
if (vertexSource == null) {
throw new Error(`Material "${materialName}" does not have a vertex source`);
}
if (fragmentSource == null) {
throw new Error(`Material "${materialName}" does not have a fragment source`);
}
let defines = material.getProgramDefines(renderPrimitive);
let key = this._getProgramKey(materialName, defines);
if (key in this._programCache) {
return this._programCache[key];
} else {
let multiview = false; // Handle this dynamically later
let fullVertexSource = vertexSource;
fullVertexSource += multiview ? VERTEX_SHADER_MULTI_ENTRY :
VERTEX_SHADER_SINGLE_ENTRY;
let precisionMatch = fragmentSource.match(PRECISION_REGEX);
let fragPrecisionHeader = precisionMatch ? '' : `precision ${this._defaultFragPrecision} float;\n`;
let fullFragmentSource = fragPrecisionHeader + fragmentSource;
fullFragmentSource += FRAGMENT_SHADER_ENTRY;
let program = new Program(this._gl, fullVertexSource, fullFragmentSource, ATTRIB, defines);
this._programCache[key] = program;
program.onNextUse((program) => {
// Bind the samplers to the right texture index. This is constant for
// the lifetime of the program.
for (let i = 0; i < material._samplers.length; ++i) {
let sampler = material._samplers[i];
let uniform = program.uniform[sampler._uniformName];
if (uniform) {
this._gl.uniform1i(uniform, i);
}
}
});
return program;
}
}
_bindPrimitive(primitive, attribMask) {
let gl = this._gl;
// If the active attributes have changed then update the active set.
if (attribMask != primitive._attributeMask) {
for (let attrib in ATTRIB) {
if (primitive._attributeMask & ATTRIB_MASK[attrib]) {
gl.enableVertexAttribArray(ATTRIB[attrib]);
} else {
gl.disableVertexAttribArray(ATTRIB[attrib]);
}
}
}
// Bind the primitive attributes and indices.
for (let attributeBuffer of primitive._attributeBuffers) {
gl.bindBuffer(gl.ARRAY_BUFFER, attributeBuffer._buffer._buffer);
for (let attrib of attributeBuffer._attributes) {
gl.vertexAttribPointer(
attrib._attrib_index, attrib._componentCount, attrib._componentType,
attrib._normalized, attrib._stride, attrib._byteOffset);
}
}
if (primitive._indexBuffer) {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, primitive._indexBuffer._buffer);
} else {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
}
_bindMaterialState(material, prevMaterial = null) {
let gl = this._gl;
let state = material._state;
let prevState = prevMaterial ? prevMaterial._state : ~state;
// Return early if both materials use identical state
if (state == prevState) {
return;
}
// Any caps bits changed?
if (material._capsDiff(prevState)) {
setCap(gl, gl.CULL_FACE, CAP.CULL_FACE, prevState, state);
setCap(gl, gl.BLEND, CAP.BLEND, prevState, state);
setCap(gl, gl.DEPTH_TEST, CAP.DEPTH_TEST, prevState, state);
setCap(gl, gl.STENCIL_TEST, CAP.STENCIL_TEST, prevState, state);
let colorMaskChange = (state & CAP.COLOR_MASK) - (prevState & CAP.COLOR_MASK);
if (colorMaskChange) {
let mask = colorMaskChange > 1;
this._colorMaskNeedsReset = !mask;
gl.colorMask(mask, mask, mask, mask);
}
let depthMaskChange = (state & CAP.DEPTH_MASK) - (prevState & CAP.DEPTH_MASK);
if (depthMaskChange) {
this._depthMaskNeedsReset = !(depthMaskChange > 1);
gl.depthMask(depthMaskChange > 1);
}
let stencilMaskChange = (state & CAP.STENCIL_MASK) - (prevState & CAP.STENCIL_MASK);
if (stencilMaskChange) {
gl.stencilMask(stencilMaskChange > 1);
}
}
// Blending enabled and blend func changed?
if (material._blendDiff(prevState)) {
gl.blendFunc(material.blendFuncSrc, material.blendFuncDst);
}
// Depth testing enabled and depth func changed?
if (material._depthFuncDiff(prevState)) {
gl.depthFunc(material.depthFunc);
}
}
}