summaryrefslogtreecommitdiff
path: root/chromium/third_party/webxr_test_pages/webxr-samples/js/cottontail/src/core/renderer.js
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/webxr_test_pages/webxr-samples/js/cottontail/src/core/renderer.js')
-rw-r--r--chromium/third_party/webxr_test_pages/webxr-samples/js/cottontail/src/core/renderer.js939
1 files changed, 939 insertions, 0 deletions
diff --git a/chromium/third_party/webxr_test_pages/webxr-samples/js/cottontail/src/core/renderer.js b/chromium/third_party/webxr_test_pages/webxr-samples/js/cottontail/src/core/renderer.js
new file mode 100644
index 00000000000..661eb2bfcaf
--- /dev/null
+++ b/chromium/third_party/webxr_test_pages/webxr-samples/js/cottontail/src/core/renderer.js
@@ -0,0 +1,939 @@
+// 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);
+ }
+ }
+}