UNPKG

@polygonjs/plugin-mapbox

Version:

Mapbox plugin for the 3D engine https://polygonjs.com

1,832 lines (1,730 loc) 123 kB
/* webgl-lint@1.8.0, license MIT */ (function (factory) { typeof define === 'function' && define.amd ? define(factory) : factory(); })(function () { 'use strict'; function isBuiltIn(name) { return name.startsWith('gl_') || name.startsWith('webgl_'); } function isWebGL2(gl) { // a proxy for if this is webgl return !!gl.texImage3D; } function isTypedArray(v) { return v && v.buffer && v.buffer instanceof ArrayBuffer; } function isArrayThatCanHaveBadValues(v) { return Array.isArray(v) || v instanceof Float32Array || v instanceof Float64Array; } function quotedStringOrEmpty(s) { return s ? `"${s}"` : ''; } /** * Map of names to numbers. * @type {Object} */ const enumStringToValue = {}; function enumArrayToString(gl, enums) { const enumStrings = []; if (enums.length) { for (let i = 0; i < enums.length; ++i) { enums.push(glEnumToString(enums[i])); // eslint-disable-line } return '[' + enumStrings.join(', ') + ']'; } return enumStrings.toString(); } function makeBitFieldToStringFunc(enums) { return function (gl, value) { let orResult = 0; const orEnums = []; for (let i = 0; i < enums.length; ++i) { const enumValue = enumStringToValue[enums[i]]; if ((value & enumValue) !== 0) { orResult |= enumValue; orEnums.push(glEnumToString(enumValue)); // eslint-disable-line } } if (orResult === value) { return orEnums.join(' | '); } else { return glEnumToString(value); // eslint-disable-line } }; } /** @type Map<int, Set<string>> */ const enumToStringsMap = new Map(); function addEnumsFromAPI(api) { for (const key in api) { const value = api[key]; if (typeof value === 'number') { if (!enumToStringsMap.has(value)) { enumToStringsMap.set(value, new Set()); } enumToStringsMap.get(value).add(key); } } } /** * Gets an string version of an WebGL enum. * * Example: * var str = WebGLDebugUtil.glEnumToString(ctx.getError()); * * @param {number} value Value to return an enum for * @return {string} The string version of the enum. */ function glEnumToString(value) { const matches = enumToStringsMap.get(value); return matches ? [...matches.keys()].map((v) => `${v}`).join(' | ') : `/*UNKNOWN WebGL ENUM*/ ${typeof value === 'number' ? `0x${value.toString(16)}` : value}`; } // --------------------------------- const FLOAT = 0x1406; const FLOAT_VEC2 = 0x8b50; const FLOAT_VEC3 = 0x8b51; const FLOAT_VEC4 = 0x8b52; const INT = 0x1404; const INT_VEC2 = 0x8b53; const INT_VEC3 = 0x8b54; const INT_VEC4 = 0x8b55; const BOOL = 0x8b56; const BOOL_VEC2 = 0x8b57; const BOOL_VEC3 = 0x8b58; const BOOL_VEC4 = 0x8b59; const FLOAT_MAT2 = 0x8b5a; const FLOAT_MAT3 = 0x8b5b; const FLOAT_MAT4 = 0x8b5c; const SAMPLER_2D = 0x8b5e; const SAMPLER_CUBE = 0x8b60; const SAMPLER_3D = 0x8b5f; const SAMPLER_2D_SHADOW = 0x8b62; const FLOAT_MAT2x3 = 0x8b65; const FLOAT_MAT2x4 = 0x8b66; const FLOAT_MAT3x2 = 0x8b67; const FLOAT_MAT3x4 = 0x8b68; const FLOAT_MAT4x2 = 0x8b69; const FLOAT_MAT4x3 = 0x8b6a; const SAMPLER_2D_ARRAY = 0x8dc1; const SAMPLER_2D_ARRAY_SHADOW = 0x8dc4; const SAMPLER_CUBE_SHADOW = 0x8dc5; const UNSIGNED_INT = 0x1405; const UNSIGNED_INT_VEC2 = 0x8dc6; const UNSIGNED_INT_VEC3 = 0x8dc7; const UNSIGNED_INT_VEC4 = 0x8dc8; const INT_SAMPLER_2D = 0x8dca; const INT_SAMPLER_3D = 0x8dcb; const INT_SAMPLER_CUBE = 0x8dcc; const INT_SAMPLER_2D_ARRAY = 0x8dcf; const UNSIGNED_INT_SAMPLER_2D = 0x8dd2; const UNSIGNED_INT_SAMPLER_3D = 0x8dd3; const UNSIGNED_INT_SAMPLER_CUBE = 0x8dd4; const UNSIGNED_INT_SAMPLER_2D_ARRAY = 0x8dd7; const uniformTypeMap = new Map([ [FLOAT, {size: 1, name: 'float'}], [FLOAT_VEC2, {size: 2, name: 'vec2'}], [FLOAT_VEC3, {size: 3, name: 'vec3'}], [FLOAT_VEC4, {size: 4, name: 'vec4'}], [INT, {size: 1, name: 'int'}], [INT_VEC2, {size: 2, name: 'ivec2'}], [INT_VEC3, {size: 3, name: 'ivec3'}], [INT_VEC4, {size: 4, name: 'ivec4'}], [UNSIGNED_INT, {size: 1, name: 'uint'}], [UNSIGNED_INT_VEC2, {size: 2, name: 'uvec2'}], [UNSIGNED_INT_VEC3, {size: 3, name: 'uvec3'}], [UNSIGNED_INT_VEC4, {size: 4, name: 'uvec4'}], [BOOL, {size: 1, name: 'bool'}], [BOOL_VEC2, {size: 2, name: 'bvec2'}], [BOOL_VEC3, {size: 3, name: 'bvec3'}], [BOOL_VEC4, {size: 4, name: 'bvec4'}], [FLOAT_MAT2, {size: 4, name: 'mat2'}], [FLOAT_MAT3, {size: 9, name: 'mat3'}], [FLOAT_MAT4, {size: 16, name: 'mat4'}], [FLOAT_MAT2x3, {size: 6, name: 'mat2x3'}], [FLOAT_MAT2x4, {size: 8, name: 'mat2x4'}], [FLOAT_MAT3x2, {size: 6, name: 'mat3x2'}], [FLOAT_MAT3x4, {size: 12, name: 'mat3x4'}], [FLOAT_MAT4x2, {size: 8, name: 'mat4x2'}], [FLOAT_MAT4x3, {size: 12, name: 'mat4x3'}], [SAMPLER_2D, {size: 1, name: 'sampler2D'}], [SAMPLER_CUBE, {size: 1, name: 'samplerCube'}], [SAMPLER_3D, {size: 1, name: 'sampler3D'}], [SAMPLER_2D_SHADOW, {size: 1, name: 'sampler2DShadow'}], [SAMPLER_2D_ARRAY, {size: 1, name: 'sampler2DArray'}], [SAMPLER_2D_ARRAY_SHADOW, {size: 1, name: 'sampler2DArrayShadow'}], [SAMPLER_CUBE_SHADOW, {size: 1, name: 'samplerCubeShadow'}], [INT_SAMPLER_2D, {size: 1, name: 'isampler2D'}], [INT_SAMPLER_3D, {size: 1, name: 'isampler3D'}], [INT_SAMPLER_CUBE, {size: 1, name: 'isamplerCube'}], [INT_SAMPLER_2D_ARRAY, {size: 1, name: 'isampler2DArray'}], [UNSIGNED_INT_SAMPLER_2D, {size: 1, name: 'usampler2D'}], [UNSIGNED_INT_SAMPLER_3D, {size: 1, name: 'usampler3D'}], [UNSIGNED_INT_SAMPLER_CUBE, {size: 1, name: 'usamplerCube'}], [UNSIGNED_INT_SAMPLER_2D_ARRAY, {size: 1, name: 'usampler2DArray'}], ]); function getUniformTypeInfo(type) { return uniformTypeMap.get(type); } // --------------------------------- const TEXTURE_BINDING_2D = 0x8069; const TEXTURE_BINDING_CUBE_MAP = 0x8514; const TEXTURE_BINDING_3D = 0x806a; const TEXTURE_BINDING_2D_ARRAY = 0x8c1d; const ARRAY_BUFFER = 0x8892; const ELEMENT_ARRAY_BUFFER = 0x8893; const ARRAY_BUFFER_BINDING = 0x8894; const ELEMENT_ARRAY_BUFFER_BINDING = 0x8895; const TEXTURE_2D = 0x0de1; const TEXTURE_3D = 0x806f; const TEXTURE_2D_ARRAY = 0x8c1a; const TEXTURE_CUBE_MAP = 0x8513; const FRAMEBUFFER = 0x8d40; const RENDERBUFFER = 0x8d41; const FRAMEBUFFER_BINDING = 0x8ca6; const RENDERBUFFER_BINDING = 0x8ca7; const TRANSFORM_FEEDBACK_BUFFER = 0x8c8e; const TRANSFORM_FEEDBACK_BUFFER_BINDING = 0x8c8f; const DRAW_FRAMEBUFFER = 0x8ca9; const READ_FRAMEBUFFER = 0x8ca8; const READ_FRAMEBUFFER_BINDING = 0x8caa; const UNIFORM_BUFFER = 0x8a11; const UNIFORM_BUFFER_BINDING = 0x8a28; const TRANSFORM_FEEDBACK = 0x8e22; const TRANSFORM_FEEDBACK_BINDING = 0x8e25; const bindPointMap = new Map([ [ARRAY_BUFFER, ARRAY_BUFFER_BINDING], [ELEMENT_ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER_BINDING], [TEXTURE_2D, TEXTURE_BINDING_2D], [TEXTURE_CUBE_MAP, TEXTURE_BINDING_CUBE_MAP], [TEXTURE_3D, TEXTURE_BINDING_3D], [TEXTURE_2D_ARRAY, TEXTURE_BINDING_2D_ARRAY], [RENDERBUFFER, RENDERBUFFER_BINDING], [FRAMEBUFFER, FRAMEBUFFER_BINDING], [DRAW_FRAMEBUFFER, FRAMEBUFFER_BINDING], [READ_FRAMEBUFFER, READ_FRAMEBUFFER_BINDING], [UNIFORM_BUFFER, UNIFORM_BUFFER_BINDING], [TRANSFORM_FEEDBACK_BUFFER, TRANSFORM_FEEDBACK_BUFFER_BINDING], [TRANSFORM_FEEDBACK, TRANSFORM_FEEDBACK_BINDING], ]); function getBindingQueryEnumForBindPoint(bindPoint) { return bindPointMap.get(bindPoint); } const BYTE = 0x1400; const SHORT = 0x1402; const UNSIGNED_BYTE = 0x1401; const UNSIGNED_SHORT = 0x1403; const glTypeToSizeMap = new Map([ [BOOL, 1], [BYTE, 1], [UNSIGNED_BYTE, 1], [SHORT, 2], [UNSIGNED_SHORT, 2], [INT, 4], [UNSIGNED_INT, 4], [FLOAT, 4], ]); function getBytesPerValueForGLType(type) { return glTypeToSizeMap.get(type) || 0; } const glTypeToTypedArrayMap = new Map([ [UNSIGNED_BYTE, Uint8Array], [UNSIGNED_SHORT, Uint16Array], [UNSIGNED_INT, Uint32Array], ]); function glTypeToTypedArray(type) { return glTypeToTypedArrayMap.get(type); } const drawFuncsToArgs = { drawArrays(primType, startOffset, vertCount) { return {startOffset, vertCount, instances: 1}; }, drawElements(primType, vertCount, indexType, startOffset) { return {startOffset, vertCount, instances: 1, indexType}; }, drawArraysInstanced(primType, startOffset, vertCount, instances) { return {startOffset, vertCount, instances}; }, drawElementsInstanced(primType, vertCount, indexType, startOffset, instances) { return {startOffset, vertCount, instances, indexType}; }, drawArraysInstancedANGLE(primType, startOffset, vertCount, instances) { return {startOffset, vertCount, instances}; }, drawElementsInstancedANGLE(primType, vertCount, indexType, startOffset, instances) { return {startOffset, vertCount, instances, indexType}; }, drawRangeElements(primType, start, end, vertCount, indexType, startOffset) { return {startOffset, vertCount, instances: 1, indexType}; }, }; function getDrawFunctionArgs(funcName, args) { return drawFuncsToArgs[funcName](...args); } function isDrawFunction(funcName) { return !!drawFuncsToArgs[funcName]; } const attrTypeMap = new Map([ [FLOAT, {size: 4}], [FLOAT_VEC2, {size: 8}], [FLOAT_VEC3, {size: 12}], [FLOAT_VEC4, {size: 16}], [INT, {size: 4}], [INT_VEC2, {size: 8}], [INT_VEC3, {size: 12}], [INT_VEC4, {size: 16}], [UNSIGNED_INT, {size: 4}], [UNSIGNED_INT_VEC2, {size: 8}], [UNSIGNED_INT_VEC3, {size: 12}], [UNSIGNED_INT_VEC4, {size: 16}], [BOOL, {size: 4}], [BOOL_VEC2, {size: 8}], [BOOL_VEC3, {size: 12}], [BOOL_VEC4, {size: 16}], [FLOAT_MAT2, {size: 4, count: 2}], [FLOAT_MAT3, {size: 9, count: 3}], [FLOAT_MAT4, {size: 16, count: 4}], ]); function getAttributeTypeInfo(type) { return attrTypeMap.get(type); } const VERTEX_ATTRIB_ARRAY_DIVISOR = 0x88fe; function computeLastUseIndexForDrawArrays(startOffset, vertCount /*, instances, errors*/) { return startOffset + vertCount - 1; } function getLastUsedIndexForDrawElements( gl, funcName, startOffset, vertCount, instances, indexType, getWebGLObjectString, getIndicesForBuffer, errors ) { const elementBuffer = gl.getParameter(gl.ELEMENT_ARRAY_BUFFER_BINDING); if (!elementBuffer) { errors.push('No ELEMENT_ARRAY_BUFFER bound'); return undefined; } const bytesPerIndex = getBytesPerValueForGLType(indexType); const bufferSize = gl.getBufferParameter(gl.ELEMENT_ARRAY_BUFFER, gl.BUFFER_SIZE); const sizeNeeded = startOffset + vertCount * bytesPerIndex; if (sizeNeeded > bufferSize) { errors.push(`offset: ${startOffset} and count: ${vertCount} with index type: ${glEnumToString( indexType )} passed to ${funcName} are out of range for current ELEMENT_ARRAY_BUFFER. Those parameters require ${sizeNeeded} bytes but the current ELEMENT_ARRAY_BUFFER ${getWebGLObjectString( elementBuffer )} only has ${bufferSize} bytes`); return undefined; } const buffer = getIndicesForBuffer(elementBuffer); const Type = glTypeToTypedArray(indexType); const view = new Type(buffer, startOffset); let maxIndex = view[0]; for (let i = 1; i < vertCount; ++i) { maxIndex = Math.max(maxIndex, view[i]); } return maxIndex; } function checkAttributesForBufferOverflow(gl, funcName, args, getWebGLObjectString, getIndicesForBuffer) { const {vertCount, startOffset, indexType, instances} = getDrawFunctionArgs(funcName, args); if (vertCount <= 0 || instances <= 0) { return []; } const program = gl.getParameter(gl.CURRENT_PROGRAM); const errors = []; const nonInstancedLastIndex = indexType ? getLastUsedIndexForDrawElements( gl, funcName, startOffset, vertCount, instances, indexType, getWebGLObjectString, getIndicesForBuffer, errors ) : computeLastUseIndexForDrawArrays(startOffset, vertCount); if (errors.length) { return errors; } const hasDivisor = isWebGL2(gl) || gl.getExtension('ANGLE_instanced_arrays'); // get the attributes used by the current program const numAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); const oldArrayBuffer = gl.getParameter(gl.ARRAY_BUFFER_BINDING); for (let ii = 0; ii < numAttributes; ++ii) { const {name, type} = gl.getActiveAttrib(program, ii); if (isBuiltIn(name)) { continue; } const index = gl.getAttribLocation(program, name); const {count} = {count: 1, ...getAttributeTypeInfo(type)}; for (let jj = 0; jj < count; ++jj) { const ndx = index + jj; const enabled = gl.getVertexAttrib(ndx, gl.VERTEX_ATTRIB_ARRAY_ENABLED); if (!enabled) { continue; } const buffer = gl.getVertexAttrib(ndx, gl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING); if (!buffer) { errors.push(`no buffer bound to attribute (${name}) location: ${index}`); continue; } const numComponents = gl.getVertexAttrib(ndx, gl.VERTEX_ATTRIB_ARRAY_SIZE); const type = gl.getVertexAttrib(ndx, gl.VERTEX_ATTRIB_ARRAY_TYPE); const bytesPerElement = getBytesPerValueForGLType(type) * numComponents; const offset = gl.getVertexAttribOffset(ndx, gl.VERTEX_ATTRIB_ARRAY_POINTER); const specifiedStride = gl.getVertexAttrib(ndx, gl.VERTEX_ATTRIB_ARRAY_STRIDE); const stride = specifiedStride ? specifiedStride : bytesPerElement; const divisor = hasDivisor ? gl.getVertexAttrib(ndx, VERTEX_ATTRIB_ARRAY_DIVISOR) : 0; gl.bindBuffer(gl.ARRAY_BUFFER, buffer); const bufferSize = gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE); const effectiveLastIndex = divisor > 0 ? (((instances + divisor - 1) / divisor) | 0) - 1 : nonInstancedLastIndex; const sizeNeeded = offset + effectiveLastIndex * stride + bytesPerElement; if (sizeNeeded > bufferSize) { errors.push(`${getWebGLObjectString( buffer )} assigned to attribute ${ndx} used as attribute '${name}' in current program is too small for draw parameters. index of highest vertex accessed: ${effectiveLastIndex} attribute size: ${numComponents}, type: ${glEnumToString( type )}, stride: ${specifiedStride}, offset: ${offset}, divisor: ${divisor} needs ${sizeNeeded} bytes for draw but buffer is only ${bufferSize} bytes`); } } } gl.bindBuffer(gl.ARRAY_BUFFER, oldArrayBuffer); return errors; } const SAMPLER_2D$1 = 0x8b5e; const SAMPLER_CUBE$1 = 0x8b60; const SAMPLER_3D$1 = 0x8b5f; const SAMPLER_2D_SHADOW$1 = 0x8b62; const SAMPLER_2D_ARRAY$1 = 0x8dc1; const SAMPLER_2D_ARRAY_SHADOW$1 = 0x8dc4; const SAMPLER_CUBE_SHADOW$1 = 0x8dc5; const INT_SAMPLER_2D$1 = 0x8dca; const INT_SAMPLER_3D$1 = 0x8dcb; const INT_SAMPLER_CUBE$1 = 0x8dcc; const INT_SAMPLER_2D_ARRAY$1 = 0x8dcf; const UNSIGNED_INT_SAMPLER_2D$1 = 0x8dd2; const UNSIGNED_INT_SAMPLER_3D$1 = 0x8dd3; const UNSIGNED_INT_SAMPLER_CUBE$1 = 0x8dd4; const UNSIGNED_INT_SAMPLER_2D_ARRAY$1 = 0x8dd7; const samplerTypes = new Map([ [SAMPLER_2D$1, {uniformType: 'sampler2D', numberType: 'float/normalized', bindPoint: '2D'}], [SAMPLER_CUBE$1, {uniformType: 'samplerCube', numberType: 'float/normalized', bindPoint: 'CUBE'}], [SAMPLER_3D$1, {uniformType: 'sampler3D', numberType: 'float/normalized', bindPoint: '3D'}], [SAMPLER_2D_SHADOW$1, {uniformType: 'sampler2D', numberType: 'float/normalized', bindPoint: '2D'}], [SAMPLER_2D_ARRAY$1, {uniformType: 'sampler2DArray', numberType: 'float/normalized', bindPoint: '2D_ARRAY'}], [ SAMPLER_2D_ARRAY_SHADOW$1, {uniformType: 'sampler2DArray', numberType: 'float/normalized', bindPoint: '2D_ARRAY'}, ], [SAMPLER_CUBE_SHADOW$1, {uniformType: 'samplerCube', numberType: 'float/normalized', bindPoint: 'CUBE'}], [INT_SAMPLER_2D$1, {uniformType: 'isampler2D', numberType: 'int', bindPoint: '2D'}], [INT_SAMPLER_3D$1, {uniformType: 'isampler3D', numberType: 'int', bindPoint: '3D'}], [INT_SAMPLER_CUBE$1, {uniformType: 'isamplerCube', numberType: 'int', bindPoint: 'CUBE'}], [INT_SAMPLER_2D_ARRAY$1, {uniformType: 'isampler2DArray', numberType: 'int', bindPoint: '2D_ARRAY'}], [UNSIGNED_INT_SAMPLER_2D$1, {uniformType: 'usampler2D', numberType: 'unsigned int', bindPoint: '2D'}], [UNSIGNED_INT_SAMPLER_3D$1, {uniformType: 'usampler3D', numberType: 'unsigned int', bindPoint: '3D'}], [UNSIGNED_INT_SAMPLER_CUBE$1, {uniformType: 'usamplerCube', numberType: 'unsigned int', bindPoint: 'CUBE'}], [ UNSIGNED_INT_SAMPLER_2D_ARRAY$1, {uniformType: 'usampler2DArray', numberType: 'unsigned int', bindPoint: '2D_ARRAY'}, ], ]); function getBindPointForSampler(type) { return samplerTypes.get(type).bindPoint; } function uniformTypeIsSampler(type) { return samplerTypes.has(type); } function getNumberTypeForUniformSamplerType(type) { return samplerTypes.get(type).numberType; } function getUniformTypeForUniformSamplerType(type) { return samplerTypes.get(type).uniformType; } const TEXTURE_2D$1 = 0x0de1; const TEXTURE_3D$1 = 0x806f; const TEXTURE_2D_ARRAY$1 = 0x8c1a; const TEXTURE_CUBE_MAP$1 = 0x8513; const TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515; const TEXTURE_CUBE_MAP_NEGATIVE_X = 0x8516; const TEXTURE_CUBE_MAP_POSITIVE_Y = 0x8517; const TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518; const TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519; const TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851a; const targetToBindPointMap = new Map([ [TEXTURE_2D$1, '2D'], [TEXTURE_3D$1, '3D'], [TEXTURE_CUBE_MAP$1, 'CUBE'], [TEXTURE_CUBE_MAP_POSITIVE_X, 'CUBE'], [TEXTURE_CUBE_MAP_NEGATIVE_X, 'CUBE'], [TEXTURE_CUBE_MAP_POSITIVE_Y, 'CUBE'], [TEXTURE_CUBE_MAP_NEGATIVE_Y, 'CUBE'], [TEXTURE_CUBE_MAP_POSITIVE_Z, 'CUBE'], [TEXTURE_CUBE_MAP_NEGATIVE_Z, 'CUBE'], [TEXTURE_2D_ARRAY$1, '2D_ARRAY'], ]); function getBindPointForTarget(target) { return targetToBindPointMap.get(target); } const TEXTURE_BINDING_2D$1 = 0x8069; const TEXTURE_BINDING_CUBE_MAP$1 = 0x8514; const TEXTURE_BINDING_3D$1 = 0x806a; const TEXTURE_BINDING_2D_ARRAY$1 = 0x8c1d; const samplerTypeToBinding = new Map([ [SAMPLER_2D$1, TEXTURE_BINDING_2D$1], [SAMPLER_2D_SHADOW$1, TEXTURE_BINDING_2D$1], [SAMPLER_3D$1, TEXTURE_BINDING_3D$1], [SAMPLER_2D_ARRAY$1, TEXTURE_BINDING_2D_ARRAY$1], [SAMPLER_2D_ARRAY_SHADOW$1, TEXTURE_BINDING_2D_ARRAY$1], [SAMPLER_CUBE$1, TEXTURE_BINDING_CUBE_MAP$1], [SAMPLER_CUBE_SHADOW$1, TEXTURE_BINDING_CUBE_MAP$1], ]); function getTextureForUnit(gl, unit, type) { gl.activeTexture(gl.TEXTURE0 + unit); const binding = samplerTypeToBinding.get(type); return gl.getParameter(binding); } /* global WebGLTexture */ const MAX_COLOR_ATTACHMENTS = 0x8cdf; function getMaxColorAttachments(gl) { if (!isWebGL2(gl)) { const ext = gl.getExtension('WEBGL_draw_buffers'); if (!ext) { return 1; } } return gl.getParameter(MAX_COLOR_ATTACHMENTS); } /** * slow non-cached version * @param {WebGLRenderingContext} gl * @param {number} attachment * @param {Map<WebGLTexture, [number]>} textureAttachments */ function addTextureAttachment(gl, attachment, textureAttachments) { const type = gl.getFramebufferAttachmentParameter( gl.FRAMEBUFFER, attachment, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE ); if (type === gl.NONE) { return; } const obj = gl.getFramebufferAttachmentParameter( gl.FRAMEBUFFER, attachment, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME ); if (obj instanceof WebGLTexture) { if (!textureAttachments.has(obj)) { textureAttachments.set(obj, []); } textureAttachments.get(obj).push(attachment); } } /** * slow non-cached version * @param {WebGLRenderingContext} gl */ function checkFramebufferFeedback(gl, getWebGLObjectString) { const framebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING); if (!framebuffer) { // drawing to canvas return []; } // get framebuffer texture attachments const maxColorAttachments = getMaxColorAttachments(gl); const textureAttachments = new Map(); for (let i = 0; i < maxColorAttachments; ++i) { addTextureAttachment(gl, gl.COLOR_ATTACHMENT0 + i, textureAttachments); } addTextureAttachment(gl, gl.DEPTH_ATTACHMENT, textureAttachments); addTextureAttachment(gl, gl.STENCIL_ATTACHMENT, textureAttachments); if (!isWebGL2(gl)) { addTextureAttachment(gl, gl.DEPTH_STENCIL_ATTACHMENT, textureAttachments); } const oldActiveTexture = gl.getParameter(gl.ACTIVE_TEXTURE); const program = gl.getParameter(gl.CURRENT_PROGRAM); // get the texture units used by the current program const numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); const errors = []; for (let ii = 0; ii < numUniforms; ++ii) { const {name, type, size} = gl.getActiveUniform(program, ii); if (isBuiltIn(name) || !uniformTypeIsSampler(type)) { continue; } if (size > 1) { const baseName = name.substr(-3) === '[0]' ? name.substr(0, name.length - 3) : name; for (let t = 0; t < size; ++t) { errors.push( ...checkTextureUsage( gl, framebuffer, textureAttachments, program, `${baseName}[${t}]`, type, getWebGLObjectString ) ); } } else { errors.push( ...checkTextureUsage(gl, framebuffer, textureAttachments, program, name, type, getWebGLObjectString) ); } } gl.activeTexture(oldActiveTexture); return errors; } function checkTextureUsage( gl, framebuffer, textureAttachments, program, uniformName, uniformType, getWebGLObjectString ) { const location = gl.getUniformLocation(program, uniformName); const textureUnit = gl.getUniform(program, location); const texture = getTextureForUnit(gl, textureUnit, uniformType); const attachments = textureAttachments.get(texture); return attachments ? [ `${getWebGLObjectString( texture )} on uniform: ${uniformName} bound to texture unit ${textureUnit} is also attached to ${getWebGLObjectString( framebuffer )} on attachment: ${attachments.map((a) => glEnumToString(a)).join(', ')}`, ] : []; } /* global navigator */ // adapted from http://stackoverflow.com/a/2401861/128511 function getBrowser() { const userAgent = navigator.userAgent; let m = userAgent.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || []; if (/trident/i.test(m[1])) { m = /\brv[ :]+(\d+)/g.exec(userAgent) || []; return { name: 'IE', version: m[1], }; } if (m[1] === 'Chrome') { const temp = userAgent.match(/\b(OPR|Edge)\/(\d+)/); if (temp) { return { name: temp[1].replace('OPR', 'Opera'), version: temp[2], }; } } m = m[2] ? [m[1], m[2]] : [navigator.appName, navigator.appVersion, '-?']; const version = userAgent.match(/version\/(\d+)/i); if (version) { m.splice(1, 1, version[1]); } return { name: m[0], version: m[1], }; } /** * @typedef {Object} StackInfo * @property {string} url Url of line * @property {number} lineNo line number of error * @property {number} colNo column number of error * @property {string} [funcName] name of function */ /** * @parameter {string} stack A stack string as in `(new Error()).stack` * @returns {StackInfo} */ const parseStack = (function () { const browser = getBrowser(); let lineNdx; let matcher; if (/chrome|opera/i.test(browser.name)) { lineNdx = 3; matcher = function (line) { const m = /at ([^(]+)*\(*(.*?):(\d+):(\d+)/.exec(line); if (m) { let userFnName = m[1]; let url = m[2]; const lineNo = parseInt(m[3]); const colNo = parseInt(m[4]); if (url === '') { url = userFnName; userFnName = ''; } return { url: url, lineNo: lineNo, colNo: colNo, funcName: userFnName, }; } return undefined; }; } else if (/firefox|safari/i.test(browser.name)) { lineNdx = 2; matcher = function (line) { const m = /@(.*?):(\d+):(\d+)/.exec(line); if (m) { const url = m[1]; const lineNo = parseInt(m[2]); const colNo = parseInt(m[3]); return { url: url, lineNo: lineNo, colNo: colNo, }; } return undefined; }; } return function stackParser(stack) { if (matcher) { try { const lines = stack.split('\n'); // window.fooLines = lines; // lines.forEach(function(line, ndx) { // origConsole.log("#", ndx, line); // }); return matcher(lines[lineNdx]); } catch (e) { // do nothing } } return undefined; }; })(); function createTextureUnits(gl) { const textureUnits = []; const numUnits = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS); for (let i = 0; i < numUnits; ++i) { textureUnits.push(new Map()); } return textureUnits; } const TEXTURE0 = 0x84c0; const TEXTURE_2D$2 = 0x0de1; const TEXTURE_3D$2 = 0x806f; const TEXTURE_CUBE_MAP$2 = 0x8513; const TEXTURE_CUBE_MAP_POSITIVE_X$1 = 0x8515; const TEXTURE_CUBE_MAP_NEGATIVE_X$1 = 0x8516; const TEXTURE_CUBE_MAP_POSITIVE_Y$1 = 0x8517; const TEXTURE_CUBE_MAP_NEGATIVE_Y$1 = 0x8518; const TEXTURE_CUBE_MAP_POSITIVE_Z$1 = 0x8519; const TEXTURE_CUBE_MAP_NEGATIVE_Z$1 = 0x851a; const TEXTURE_MIN_FILTER = 0x2801; const TEXTURE_MAG_FILTER = 0x2800; const TEXTURE_BASE_LEVEL = 0x813c; // int const TEXTURE_MAX_LEVEL = 0x813d; // int const TEXTURE_WRAP_S = 0x2802; const TEXTURE_WRAP_T = 0x2803; const REPEAT = 0x2901; const TEXTURE_2D_ARRAY$2 = 0x8c1a; const CLAMP_TO_EDGE = 0x812f; const NEAREST = 0x2600; const LINEAR = 0x2601; const NEAREST_MIPMAP_LINEAR = 0x2702; const texImage2DArgParersMap = new Map([ [ 9, function ([target, level, internalFormat, width, height, , format, type]) { return {target, level, internalFormat, width, height, format, type}; }, ], [ 6, function ([target, level, internalFormat, format, type, texImageSource]) { return { target, level, internalFormat, width: texImageSource.width, height: texImageSource.height, format, type, }; }, ], [ 10, function ([target, level, internalFormat, width, height, , format, type]) { return {target, level, internalFormat, width, height, format, type}; }, ], ]); const ALPHA = 0x1906; const RGB = 0x1907; const RGBA = 0x1908; const LUMINANCE = 0x1909; const LUMINANCE_ALPHA = 0x190a; const DEPTH_COMPONENT = 0x1902; const DEPTH_STENCIL = 0x84f9; const unsizedInternalFormats = new Set([ALPHA, LUMINANCE, LUMINANCE_ALPHA, RGB, RGBA]); function getInternalFormatStringForInternalFormatType(internalFormat, type) { return unsizedInternalFormats.has(internalFormat) ? `${glEnumToString(internalFormat)}/${glEnumToString(type)}` : glEnumToString(internalFormat); } const targetToFaceIndex = new Map([ [TEXTURE_2D$2, 0], [TEXTURE_3D$2, 0], [TEXTURE_2D_ARRAY$2, 0], [TEXTURE_CUBE_MAP$2, 0], [TEXTURE_CUBE_MAP_POSITIVE_X$1, 0], [TEXTURE_CUBE_MAP_NEGATIVE_X$1, 1], [TEXTURE_CUBE_MAP_POSITIVE_Y$1, 2], [TEXTURE_CUBE_MAP_NEGATIVE_Y$1, 3], [TEXTURE_CUBE_MAP_POSITIVE_Z$1, 4], [TEXTURE_CUBE_MAP_NEGATIVE_Z$1, 5], ]); function getFaceTarget(face, type) { if (type === TEXTURE_CUBE_MAP$2) { return `(${glEnumToString(TEXTURE_CUBE_MAP_POSITIVE_X$1 + face)})`; } else { return ''; } } /* const targetToBindPointMap = new Map([ [TEXTURE_2D, TEXTURE_2D], [TEXTURE_3D, TEXTURE_3D], [TEXTURE_2D_ARRAY, TEXTURE_2D_ARRAY], [TEXTURE_CUBE_MAP_POSITIVE_X, TEXTURE_CUBE_MAP], [TEXTURE_CUBE_MAP_NEGATIVE_X, TEXTURE_CUBE_MAP], [TEXTURE_CUBE_MAP_POSITIVE_Y, TEXTURE_CUBE_MAP], [TEXTURE_CUBE_MAP_NEGATIVE_Y, TEXTURE_CUBE_MAP], [TEXTURE_CUBE_MAP_POSITIVE_Z, TEXTURE_CUBE_MAP], [TEXTURE_CUBE_MAP_NEGATIVE_Z, TEXTURE_CUBE_MAP], ]); */ const R8 = 0x8229; const R8_SNORM = 0x8f94; const R16F = 0x822d; const R32F = 0x822e; const R8UI = 0x8232; const R8I = 0x8231; const RG16UI = 0x823a; const RG16I = 0x8239; const RG32UI = 0x823c; const RG32I = 0x823b; const RG8 = 0x822b; const RG8_SNORM = 0x8f95; const RG16F = 0x822f; const RG32F = 0x8230; const RG8UI = 0x8238; const RG8I = 0x8237; const R16UI = 0x8234; const R16I = 0x8233; const R32UI = 0x8236; const R32I = 0x8235; const RGB8 = 0x8051; const SRGB8 = 0x8c41; const RGB565 = 0x8d62; const RGB8_SNORM = 0x8f96; const R11F_G11F_B10F = 0x8c3a; const RGB9_E5 = 0x8c3d; const RGB16F = 0x881b; const RGB32F = 0x8815; const RGB8UI = 0x8d7d; const RGB8I = 0x8d8f; const RGB16UI = 0x8d77; const RGB16I = 0x8d89; const RGB32UI = 0x8d71; const RGB32I = 0x8d83; const RGBA8 = 0x8058; const SRGB8_ALPHA8 = 0x8c43; const RGBA8_SNORM = 0x8f97; const RGB5_A1 = 0x8057; const RGBA4 = 0x8056; const RGB10_A2 = 0x8059; const RGBA16F = 0x881a; const RGBA32F = 0x8814; const RGBA8UI = 0x8d7c; const RGBA8I = 0x8d8e; const RGB10_A2UI = 0x906f; const RGBA16UI = 0x8d76; const RGBA16I = 0x8d88; const RGBA32I = 0x8d82; const RGBA32UI = 0x8d70; const DEPTH_COMPONENT16 = 0x81a5; const DEPTH_COMPONENT24 = 0x81a6; const DEPTH_COMPONENT32F = 0x8cac; const DEPTH32F_STENCIL8 = 0x8cad; const DEPTH24_STENCIL8 = 0x88f0; /* DataType */ const BYTE$1 = 0x1400; const UNSIGNED_BYTE$1 = 0x1401; const SHORT$1 = 0x1402; const UNSIGNED_SHORT$1 = 0x1403; const INT$1 = 0x1404; const UNSIGNED_INT$1 = 0x1405; const FLOAT$1 = 0x1406; const UNSIGNED_SHORT_4_4_4_4 = 0x8033; const UNSIGNED_SHORT_5_5_5_1 = 0x8034; const UNSIGNED_SHORT_5_6_5 = 0x8363; const HALF_FLOAT = 0x140b; const HALF_FLOAT_OES = 0x8d61; // Thanks Khronos for making this different >:( const UNSIGNED_INT_2_10_10_10_REV = 0x8368; const UNSIGNED_INT_10F_11F_11F_REV = 0x8c3b; const UNSIGNED_INT_5_9_9_9_REV = 0x8c3e; const FLOAT_32_UNSIGNED_INT_24_8_REV = 0x8dad; const UNSIGNED_INT_24_8 = 0x84fa; const RG = 0x8227; const RG_INTEGER = 0x8228; const RED = 0x1903; const RED_INTEGER = 0x8d94; const RGB_INTEGER = 0x8d98; const RGBA_INTEGER = 0x8d99; function createTextureInternalFormatInfoMap() { const textureInternalFormatInfoMap = new Map([ // unsized formats [ ALPHA, { textureFormat: ALPHA, colorRenderable: true, textureFilterable: true, bytesPerElement: [1, 2, 2, 4], type: [UNSIGNED_BYTE$1], }, ], [ LUMINANCE, { textureFormat: LUMINANCE, colorRenderable: true, textureFilterable: true, bytesPerElement: [1, 2, 2, 4], type: [UNSIGNED_BYTE$1], }, ], [ LUMINANCE_ALPHA, { textureFormat: LUMINANCE_ALPHA, colorRenderable: true, textureFilterable: true, bytesPerElement: [2, 4, 4, 8], type: [UNSIGNED_BYTE$1], }, ], [ RGB, { textureFormat: RGB, colorRenderable: true, textureFilterable: true, bytesPerElement: [3, 6, 6, 12, 2], type: [UNSIGNED_BYTE$1, UNSIGNED_SHORT_5_6_5], }, ], [ RGBA, { textureFormat: RGBA, colorRenderable: true, textureFilterable: true, bytesPerElement: [4, 8, 8, 16, 2, 2], type: [UNSIGNED_BYTE$1, UNSIGNED_SHORT_4_4_4_4, UNSIGNED_SHORT_5_5_5_1], }, ], // sized formats [ R8, { textureFormat: RED, colorRenderable: true, textureFilterable: true, bytesPerElement: [1], type: [UNSIGNED_BYTE$1], }, ], [ R8_SNORM, { textureFormat: RED, colorRenderable: false, textureFilterable: true, bytesPerElement: [1], type: [BYTE$1], }, ], [ R16F, { textureFormat: RED, colorRenderable: false, textureFilterable: true, bytesPerElement: [4, 2], type: [FLOAT$1, HALF_FLOAT], }, ], [ R32F, { textureFormat: RED, colorRenderable: false, textureFilterable: false, bytesPerElement: [4], type: [FLOAT$1], }, ], [ R8UI, { textureFormat: RED_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [1], type: [UNSIGNED_BYTE$1], }, ], [ R8I, { textureFormat: RED_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [1], type: [BYTE$1], }, ], [ R16UI, { textureFormat: RED_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [2], type: [UNSIGNED_SHORT$1], }, ], [ R16I, { textureFormat: RED_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [2], type: [SHORT$1], }, ], [ R32UI, { textureFormat: RED_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [UNSIGNED_INT$1], }, ], [ R32I, { textureFormat: RED_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [INT$1], }, ], [ RG8, { textureFormat: RG, colorRenderable: true, textureFilterable: true, bytesPerElement: [2], type: [UNSIGNED_BYTE$1], }, ], [ RG8_SNORM, { textureFormat: RG, colorRenderable: false, textureFilterable: true, bytesPerElement: [2], type: [BYTE$1], }, ], [ RG16F, { textureFormat: RG, colorRenderable: false, textureFilterable: true, bytesPerElement: [8, 4], type: [FLOAT$1, HALF_FLOAT], }, ], [ RG32F, { textureFormat: RG, colorRenderable: false, textureFilterable: false, bytesPerElement: [8], type: [FLOAT$1], }, ], [ RG8UI, { textureFormat: RG_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [2], type: [UNSIGNED_BYTE$1], }, ], [ RG8I, { textureFormat: RG_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [2], type: [BYTE$1], }, ], [ RG16UI, { textureFormat: RG_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [UNSIGNED_SHORT$1], }, ], [ RG16I, { textureFormat: RG_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [SHORT$1], }, ], [ RG32UI, { textureFormat: RG_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [8], type: [UNSIGNED_INT$1], }, ], [ RG32I, { textureFormat: RG_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [8], type: [INT$1], }, ], [ RGB8, { textureFormat: RGB, colorRenderable: true, textureFilterable: true, bytesPerElement: [3], type: [UNSIGNED_BYTE$1], }, ], [ SRGB8, { textureFormat: RGB, colorRenderable: false, textureFilterable: true, bytesPerElement: [3], type: [UNSIGNED_BYTE$1], }, ], [ RGB565, { textureFormat: RGB, colorRenderable: true, textureFilterable: true, bytesPerElement: [3, 2], type: [UNSIGNED_BYTE$1, UNSIGNED_SHORT_5_6_5], }, ], [ RGB8_SNORM, { textureFormat: RGB, colorRenderable: false, textureFilterable: true, bytesPerElement: [3], type: [BYTE$1], }, ], [ R11F_G11F_B10F, { textureFormat: RGB, colorRenderable: false, textureFilterable: true, bytesPerElement: [12, 6, 4], type: [FLOAT$1, HALF_FLOAT, UNSIGNED_INT_10F_11F_11F_REV], }, ], [ RGB9_E5, { textureFormat: RGB, colorRenderable: false, textureFilterable: true, bytesPerElement: [12, 6, 4], type: [FLOAT$1, HALF_FLOAT, UNSIGNED_INT_5_9_9_9_REV], }, ], [ RGB16F, { textureFormat: RGB, colorRenderable: false, textureFilterable: true, bytesPerElement: [12, 6], type: [FLOAT$1, HALF_FLOAT], }, ], [ RGB32F, { textureFormat: RGB, colorRenderable: false, textureFilterable: false, bytesPerElement: [12], type: [FLOAT$1], }, ], [ RGB8UI, { textureFormat: RGB_INTEGER, colorRenderable: false, textureFilterable: false, bytesPerElement: [3], type: [UNSIGNED_BYTE$1], }, ], [ RGB8I, { textureFormat: RGB_INTEGER, colorRenderable: false, textureFilterable: false, bytesPerElement: [3], type: [BYTE$1], }, ], [ RGB16UI, { textureFormat: RGB_INTEGER, colorRenderable: false, textureFilterable: false, bytesPerElement: [6], type: [UNSIGNED_SHORT$1], }, ], [ RGB16I, { textureFormat: RGB_INTEGER, colorRenderable: false, textureFilterable: false, bytesPerElement: [6], type: [SHORT$1], }, ], [ RGB32UI, { textureFormat: RGB_INTEGER, colorRenderable: false, textureFilterable: false, bytesPerElement: [12], type: [UNSIGNED_INT$1], }, ], [ RGB32I, { textureFormat: RGB_INTEGER, colorRenderable: false, textureFilterable: false, bytesPerElement: [12], type: [INT$1], }, ], [ RGBA8, { textureFormat: RGBA, colorRenderable: true, textureFilterable: true, bytesPerElement: [4], type: [UNSIGNED_BYTE$1], }, ], [ SRGB8_ALPHA8, { textureFormat: RGBA, colorRenderable: true, textureFilterable: true, bytesPerElement: [4], type: [UNSIGNED_BYTE$1], }, ], [ RGBA8_SNORM, { textureFormat: RGBA, colorRenderable: false, textureFilterable: true, bytesPerElement: [4], type: [BYTE$1], }, ], [ RGB5_A1, { textureFormat: RGBA, colorRenderable: true, textureFilterable: true, bytesPerElement: [4, 2, 4], type: [UNSIGNED_BYTE$1, UNSIGNED_SHORT_5_5_5_1, UNSIGNED_INT_2_10_10_10_REV], }, ], [ RGBA4, { textureFormat: RGBA, colorRenderable: true, textureFilterable: true, bytesPerElement: [4, 2], type: [UNSIGNED_BYTE$1, UNSIGNED_SHORT_4_4_4_4], }, ], [ RGB10_A2, { textureFormat: RGBA, colorRenderable: true, textureFilterable: true, bytesPerElement: [4], type: [UNSIGNED_INT_2_10_10_10_REV], }, ], [ RGBA16F, { textureFormat: RGBA, colorRenderable: false, textureFilterable: true, bytesPerElement: [16, 8], type: [FLOAT$1, HALF_FLOAT], }, ], [ RGBA32F, { textureFormat: RGBA, colorRenderable: false, textureFilterable: false, bytesPerElement: [16], type: [FLOAT$1], }, ], [ RGBA8UI, { textureFormat: RGBA_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [UNSIGNED_BYTE$1], }, ], [ RGBA8I, { textureFormat: RGBA_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [BYTE$1], }, ], [ RGB10_A2UI, { textureFormat: RGBA_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [UNSIGNED_INT_2_10_10_10_REV], }, ], [ RGBA16UI, { textureFormat: RGBA_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [8], type: [UNSIGNED_SHORT$1], }, ], [ RGBA16I, { textureFormat: RGBA_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [8], type: [SHORT$1], }, ], [ RGBA32I, { textureFormat: RGBA_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [16], type: [INT$1], }, ], [ RGBA32UI, { textureFormat: RGBA_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [16], type: [UNSIGNED_INT$1], }, ], // Sized Internal [ DEPTH_COMPONENT16, { textureFormat: DEPTH_COMPONENT, colorRenderable: true, textureFilterable: false, bytesPerElement: [2, 4], type: [UNSIGNED_SHORT$1, UNSIGNED_INT$1], }, ], [ DEPTH_COMPONENT24, { textureFormat: DEPTH_COMPONENT, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [UNSIGNED_INT$1], }, ], [ DEPTH_COMPONENT32F, { textureFormat: DEPTH_COMPONENT, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [FLOAT$1], }, ], [ DEPTH24_STENCIL8, { textureFormat: DEPTH_STENCIL, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [UNSIGNED_INT_24_8], }, ], [ DEPTH32F_STENCIL8, { textureFormat: DEPTH_STENCIL, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [FLOAT_32_UNSIGNED_INT_24_8_REV], }, ], ]); textureInternalFormatInfoMap.forEach((info) => { info.bytesPerElementMap = {}; info.bytesPerElement.forEach(function (bytesPerElement, ndx) { const type = info.type[ndx]; info.bytesPerElementMap[type] = bytesPerElement; }); }); const internalFormatStringToFormatInfoMap = new Map(); textureInternalFormatInfoMap.forEach((info, internalFormat) => { if (unsizedInternalFormats.has(internalFormat)) { info.type.forEach((type) => { internalFormatStringToFormatInfoMap.set( getInternalFormatStringForInternalFormatType(internalFormat, type), info ); }); } else { internalFormatStringToFormatInfoMap.set( getInternalFormatStringForInternalFormatType(internalFormat), info ); } }); return { textureInternalFormatInfoMap, internalFormatStringToFormatInfoMap, }; } function isPowerOf2(value) { return (value & (value - 1)) === 0; } function computeNumMipsNeeded(width, height = 0, depth = 0) { return (Math.log2(Math.max(width, height, depth)) | 0) + 1; } function insertIf(condition, ...elements) { return condition ? elements : []; } function getNPotIssues(width, height) { return [ ...insertIf(!isPowerOf2(width), `width(${width}) is not a power of 2`), ...insertIf(!isPowerOf2(height), `height(${height}) is not a power of 2`), ].join(' and '); } function getClampToEdgeIssues(wrapS, wrapT) { return [ ...insertIf(wrapS !== CLAMP_TO_EDGE, `TEXTURE_WRAP_S (${glEnumToString(wrapS)}) is not CLAMP_TO_EDGE`), ...insertIf(wrapT !== CLAMP_TO_EDGE, `TEXTURE_WRAP_T (${glEnumToString(wrapT)}) is not CLAMP_TO_EDGE`), ].join(' and '); } function getNumberTypeForInternalFormat(internalFormat) { const str = glEnumToString(internalFormat); if (str.endsWith('UI')) { return 'unsigned int'; } if (str.endsWith('I')) { return 'int'; } return 'float/normalized'; } function getDimensionsString(type, width, height, depth) { return type === TEXTURE_2D$2 || type === TEXTURE_CUBE_MAP$2 ? `${width}x${height}` : `${width}x${height}x${depth}`; } class TextureManager { constructor(gl) { const isWebGL2$1 = isWebGL2(gl); const needPOT = !isWebGL2$1; const extensions = new Set(); const textureToTextureInfoMap = new Map(); const samplerToParametersMap = new Map(); const textureUnits = createTextureUnits(gl); const maxMips = computeNumMipsNeeded(gl.getParameter(gl.MAX_TEXTURE_SIZE)); const {internalFormatStringToFormatInfoMap} = createTextureInternalFormatInfoMap(); let activeTextureUnitIndex = 0; let activeTextureUnit = textureUnits[0]; this.numTextureUnits = textureUnits.length; function recomputeRenderability(textureInfo) { textureInfo.notRenderable = computeRenderability(textureInfo, textureInfo.parameters); } function recomputeAllTextureUnrenderability() { textureToTextureInfoMap.forEach(recomputeRenderability); } function computeRenderability(textureInfo, parameters) { const {type, mips} = textureInfo; const baseLevel = parameters.get(TEXTURE_BASE_LEVEL) || 0; const maxLevel = parameters.get(TEXTURE_MAX_LEVEL) || maxMips; if (maxLevel < baseLevel) { return `TEXTURE_MAX_LEVEL(${maxLevel}) is less than TEXTURE_BASE_LEVEL(${baseLevel})`; } const baseLevelFaces = mips[baseLevel]; if (!baseLevelFaces) { return 'no base level mip ${baseLevel}'; } const baseMipFace = baseLevelFaces[0]; if (!baseMipFace) { return 'TEXTURE_CUBE_MAP_POSITIVE_X face does not exist'; } const { width: baseWidth, height: baseHeight, depth: baseDepth, // internalFormat: baseInternalFormat, internalFormatString: baseInternalFormatString, } = baseMipFace; const numFaces = type === TEXTURE_CUBE_MAP$2 ? 6 : 1; const minFilter = parameters.get(TEXTURE_MIN_FILTER); const internalFormatInfo = internalFormatStringToFormatInfoMap.get(baseInternalFormatString); // there is no format info for compressed texture ATM. // we could add it but AFAIK compressed textures are colorFilterable // so for now let's just not do the check if we don't know about the format if (internalFormatInfo) { const textureFilterable = internalFormatInfo.textureFilterable; if (!textureFilterable) { if (minFilter !== NEAREST) { return `texture of type (${baseInternalFormatString}) is not filterable but TEXTURE_MIN_FILTER is set to ${glEnumToString( minFilter )}`; } else { const magFilter = parameters.get(TEXTURE_MAG_FILTER); if (magFilter !== NEAREST) { return `texture of type (${baseInternalFormatString}) is not filterable but TEXTURE_MAG_FILTER is set to ${glEnumToString( magFilter )}`; } } } } const numMipsNeeded = minFilter === LINEAR || minFilter === NEAREST ? 1 : computeNumMipsNeeded(baseWidth, baseHeight, baseDepth); { let mipWidth = baseWidth; let mipHeight = baseHeight; let mipDepth = baseDepth; const lastMip = Math.min(maxLevel, baseLevel + numMipsNeeded - 1); for (let mipLevel = baseLevel; mipLevel <= lastMip; ++mipLevel) { const faceMips = mips[mipLevel]; if (!faceMips) { return `filtering is set to use mips (TEXTURE_MIN_FILTER = ${glEnumToString( minFilter )}) but mip level ${mipLevel} does not exist`; } for (let face = 0; face < numFaces; ++face) { const mip = faceMips[face]; if (!mip) { return `filtering is set to use mips (TEXTURE_MIN_FILTER = ${glEnumToString( minFilter )}) but mip level ${mipLevel}${getFaceTarget(face, type)} does not exist`; } if (mip.width !== mipWidth || mip.height !== mipHeight || mip.depth !== mipDepth) { return `mip level ${mipLevel}${getFaceTarget( face, type )} needs to be ${getDimensionsString( type, mipWidth, mipHeight, mipDepth )} but it is ${getDimensionsString(type, mip.width, mip.height, mip.depth)}`; } if (mip.internalFormatString !== baseInternalFormatString) { return `mip level ${mipLevel}${getFaceTarget(face, type)}'s internal format ${ mip.internalFormatString } does not match mip level 0's internal format ${baseInternalFormatString}`; } } mipWidth = Math.max(1, (mipWidth / 2) | 0); mipHeight = Math.max(1, (mipHeight / 2) | 0); if (type !== TEXTURE_2D_ARRAY$2) { mipDepth = Math.max(1, (mipDepth / 2) | 0); } } } if (needPOT) { if (!isPowerOf2(baseWidth) || !isPowerOf2(baseHeight)) { if (numMipsNeeded > 1) { return `texture's ${getNPotIssues( baseWidth, baseHeight )} but TEXTURE_MIN_FILTER (${glEnumToString(minFilter)}) is set to need mips`; } const wrapS = parameters.get(TEXTURE_WRAP_S); const wrapT = parameters.get(TEXTURE_WRAP_T); if (wrapS !== CLAMP_TO_EDGE || wrapT !== CLAMP_TO_EDGE) { return `texture's ${getNPotIssues(baseWidth, baseHeight)} but ${getClampToEdgeIssues( wrapS, wrapT )}.`; } } } if (type === TEXTURE_CUBE_MAP$2) { if (baseWidth !== baseHeight) { return `texture is CUBE_MAP but dimensions ${baseWidth}x${baseHeight} are not square`; } } return undefined; } function getTextureInfoForTarget(target) { const bindPoint = getBindPointForTarget(target); const texture = activeTextureUnit.get(bindPoint); return textureToTextureInfoMap.get(texture); } function getMipInfoForTarget(target, level) { const textureInfo = getTextureInfoForTarget(target); const faceIndex = targetToFaceIndex.get(target); return textureInfo.mips[level][faceIndex]; } function setTexParameterForTarget(target, pname, value) { const textureInfo = getTextureInfoForTarget(target); textureInfo.parameters.set(pname, value); recomputeRenderability(textureInfo); } function removeFromTextureUnits(type, obj) { for (let i = 0; i < textureUnits.length; ++i) { const unit = textureUnits[i]; if (unit.get(type) === obj) { unit.set(type, null); } } } function getInternalFormatStringForTextureInfo(textureInfo) { const {mips, parameters} = textureInfo; const baseLevel = parameters.get(TEXTURE_BASE_LEVEL) || 0; const baseFaces = mips[baseLevel]; if (!baseFaces) { return ''; } const baseMipFace = baseFaces[0]; if (!baseMipFace) { return ''; } return baseMipFace.internalFormatString; } function addInternalFormatStringInfos(type, textureFilterableExtensionName) { const textureFilterable = extensions.has(textureFilterableExtensionName); [RGBA, RGB, LUMINANCE, LUMINANCE_ALPHA, ALPHA].forEach((internalFormat) => { internalFormatStringToFormatInfoMap.set(