UNPKG

@animech-public/playcanvas

Version:
1,105 lines (1,050 loc) 99.5 kB
import { math } from '../../../core/math/math.js'; import { Debug } from '../../../core/debug.js'; import { platform } from '../../../core/platform.js'; import { Color } from '../../../core/math/color.js'; import { DEVICETYPE_WEBGL2, DEVICETYPE_WEBGL1, UNIFORMTYPE_BOOL, UNIFORMTYPE_INT, UNIFORMTYPE_FLOAT, UNIFORMTYPE_VEC2, UNIFORMTYPE_VEC3, UNIFORMTYPE_VEC4, UNIFORMTYPE_IVEC2, UNIFORMTYPE_IVEC3, UNIFORMTYPE_IVEC4, UNIFORMTYPE_BVEC2, UNIFORMTYPE_BVEC3, UNIFORMTYPE_BVEC4, UNIFORMTYPE_MAT2, UNIFORMTYPE_MAT3, UNIFORMTYPE_MAT4, UNIFORMTYPE_TEXTURE2D, UNIFORMTYPE_TEXTURECUBE, UNIFORMTYPE_UINT, UNIFORMTYPE_UVEC2, UNIFORMTYPE_UVEC3, UNIFORMTYPE_UVEC4, UNIFORMTYPE_TEXTURE2D_SHADOW, UNIFORMTYPE_TEXTURECUBE_SHADOW, UNIFORMTYPE_TEXTURE2D_ARRAY, UNIFORMTYPE_TEXTURE3D, UNIFORMTYPE_ITEXTURE2D, UNIFORMTYPE_UTEXTURE2D, UNIFORMTYPE_ITEXTURECUBE, UNIFORMTYPE_UTEXTURECUBE, UNIFORMTYPE_ITEXTURE3D, UNIFORMTYPE_UTEXTURE3D, UNIFORMTYPE_ITEXTURE2D_ARRAY, UNIFORMTYPE_UTEXTURE2D_ARRAY, UNIFORMTYPE_FLOATARRAY, UNIFORMTYPE_VEC2ARRAY, UNIFORMTYPE_VEC3ARRAY, UNIFORMTYPE_VEC4ARRAY, UNIFORMTYPE_INTARRAY, UNIFORMTYPE_UINTARRAY, UNIFORMTYPE_BOOLARRAY, UNIFORMTYPE_IVEC2ARRAY, UNIFORMTYPE_UVEC2ARRAY, UNIFORMTYPE_BVEC2ARRAY, UNIFORMTYPE_IVEC3ARRAY, UNIFORMTYPE_UVEC3ARRAY, UNIFORMTYPE_BVEC3ARRAY, UNIFORMTYPE_IVEC4ARRAY, UNIFORMTYPE_UVEC4ARRAY, UNIFORMTYPE_BVEC4ARRAY, UNIFORMTYPE_MAT4ARRAY, PIXELFORMAT_RGBA8, PIXELFORMAT_RGBA16F, PIXELFORMAT_RGBA32F, PIXELFORMAT_RGB8, FUNC_ALWAYS, STENCILOP_KEEP, ADDRESS_CLAMP_TO_EDGE, semanticToLocation, CLEARFLAG_COLOR, CLEARFLAG_DEPTH, CLEARFLAG_STENCIL, CULLFACE_NONE, PRIMITIVE_TRISTRIP, FILTER_NEAREST_MIPMAP_NEAREST, FILTER_NEAREST_MIPMAP_LINEAR, FILTER_NEAREST, FILTER_LINEAR_MIPMAP_NEAREST, FILTER_LINEAR_MIPMAP_LINEAR, FILTER_LINEAR } from '../constants.js'; import { GraphicsDevice } from '../graphics-device.js'; import { RenderTarget } from '../render-target.js'; import { Texture } from '../texture.js'; import { DebugGraphics } from '../debug-graphics.js'; import { WebglVertexBuffer } from './webgl-vertex-buffer.js'; import { WebglIndexBuffer } from './webgl-index-buffer.js'; import { WebglShader } from './webgl-shader.js'; import { WebglTexture } from './webgl-texture.js'; import { WebglRenderTarget } from './webgl-render-target.js'; import { ShaderUtils } from '../shader-utils.js'; import { Shader } from '../shader.js'; import { BlendState } from '../blend-state.js'; import { DepthState } from '../depth-state.js'; import { StencilParameters } from '../stencil-parameters.js'; import { WebglGpuProfiler } from './webgl-gpu-profiler.js'; const invalidateAttachments = []; const _fullScreenQuadVS = /* glsl */` attribute vec2 vertex_position; varying vec2 vUv0; void main(void) { gl_Position = vec4(vertex_position, 0.5, 1.0); vUv0 = vertex_position.xy*0.5+0.5; } `; const _precisionTest1PS = /* glsl */` void main(void) { gl_FragColor = vec4(2147483648.0); } `; const _precisionTest2PS = /* glsl */` uniform sampler2D source; vec4 packFloat(float depth) { const vec4 bit_shift = vec4(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0); const vec4 bit_mask = vec4(0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0); vec4 res = mod(depth * bit_shift * vec4(255), vec4(256) ) / vec4(255); res -= res.xxyz * bit_mask; return res; } void main(void) { float c = texture2D(source, vec2(0.0)).r; float diff = abs(c - 2147483648.0) / 2147483648.0; gl_FragColor = packFloat(diff); } `; const _outputTexture2D = /* glsl */` varying vec2 vUv0; uniform sampler2D source; void main(void) { gl_FragColor = texture2D(source, vUv0); } `; function quadWithShader(device, target, shader) { DebugGraphics.pushGpuMarker(device, 'QuadWithShader'); const oldRt = device.renderTarget; device.setRenderTarget(target); device.updateBegin(); device.setCullMode(CULLFACE_NONE); device.setBlendState(BlendState.NOBLEND); device.setDepthState(DepthState.NODEPTH); device.setStencilState(null, null); device.setVertexBuffer(device.quadVertexBuffer, 0); device.setShader(shader); device.draw({ type: PRIMITIVE_TRISTRIP, base: 0, count: 4, indexed: false }); device.updateEnd(); device.setRenderTarget(oldRt); device.updateBegin(); DebugGraphics.popGpuMarker(device); } function testRenderable(gl, pixelFormat) { let result = true; // Create a 2x2 texture const texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, pixelFormat, null); // Try to use this texture as a render target const framebuffer = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); // It is legal for a WebGL implementation exposing the OES_texture_float extension to // support floating-point textures but not as attachments to framebuffer objects. if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) { result = false; } // Clean up gl.bindTexture(gl.TEXTURE_2D, null); gl.deleteTexture(texture); gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.deleteFramebuffer(framebuffer); return result; } function testTextureHalfFloatUpdatable(gl, pixelFormat) { let result = true; // Create a 2x2 texture const texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); // upload some data - on iOS prior to about November 2019, passing data to half texture would fail here // see details here: https://bugs.webkit.org/show_bug.cgi?id=169999 // note that if not supported, this prints an error to console, the error can be safely ignored as it's handled const data = new Uint16Array(4 * 2 * 2); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, pixelFormat, data); if (gl.getError() !== gl.NO_ERROR) { result = false; console.log('Above error related to HALF_FLOAT_OES can be ignored, it was triggered by testing half float texture support'); } // Clean up gl.bindTexture(gl.TEXTURE_2D, null); gl.deleteTexture(texture); return result; } function testTextureFloatHighPrecision(device) { if (!device.textureFloatRenderable) { return false; } const shader1 = new Shader(device, ShaderUtils.createDefinition(device, { name: 'ptest1', vertexCode: _fullScreenQuadVS, fragmentCode: _precisionTest1PS })); const shader2 = new Shader(device, ShaderUtils.createDefinition(device, { name: 'ptest2', vertexCode: _fullScreenQuadVS, fragmentCode: _precisionTest2PS })); const textureOptions = { format: PIXELFORMAT_RGBA32F, width: 1, height: 1, mipmaps: false, minFilter: FILTER_NEAREST, magFilter: FILTER_NEAREST, name: 'testFHP' }; const tex1 = new Texture(device, textureOptions); const targ1 = new RenderTarget({ colorBuffer: tex1, depth: false }); quadWithShader(device, targ1, shader1); textureOptions.format = PIXELFORMAT_RGBA8; const tex2 = new Texture(device, textureOptions); const targ2 = new RenderTarget({ colorBuffer: tex2, depth: false }); device.constantTexSource.setValue(tex1); quadWithShader(device, targ2, shader2); const prevFramebuffer = device.activeFramebuffer; device.setFramebuffer(targ2.impl._glFrameBuffer); const pixels = new Uint8Array(4); device.readPixels(0, 0, 1, 1, pixels); device.setFramebuffer(prevFramebuffer); const x = pixels[0] / 255; const y = pixels[1] / 255; const z = pixels[2] / 255; const w = pixels[3] / 255; const f = x / (256 * 256 * 256) + y / (256 * 256) + z / 256 + w; tex1.destroy(); targ1.destroy(); tex2.destroy(); targ2.destroy(); shader1.destroy(); shader2.destroy(); return f === 0; } /** * The graphics device manages the underlying graphics context. It is responsible for submitting * render state changes and graphics primitives to the hardware. A graphics device is tied to a * specific canvas HTML element. It is valid to have more than one canvas element per page and * create a new graphics device against each. * * @category Graphics */ class WebglGraphicsDevice extends GraphicsDevice { /** * Creates a new WebglGraphicsDevice instance. * * @param {HTMLCanvasElement} canvas - The canvas to which the graphics device will render. * @param {object} [options] - Options passed when creating the WebGL context. * @param {boolean} [options.alpha] - Boolean that indicates if the canvas contains an * alpha buffer. Defaults to true. * @param {boolean} [options.depth] - Boolean that indicates that the drawing buffer is * requested to have a depth buffer of at least 16 bits. Defaults to true. * @param {boolean} [options.stencil] - Boolean that indicates that the drawing buffer is * requested to have a stencil buffer of at least 8 bits. Defaults to true. * @param {boolean} [options.antialias] - Boolean that indicates whether or not to perform * anti-aliasing if possible. Defaults to true. * @param {boolean} [options.premultipliedAlpha] - Boolean that indicates that the page * compositor will assume the drawing buffer contains colors with pre-multiplied alpha. * Defaults to true. * @param {boolean} [options.preserveDrawingBuffer] - If the value is true the buffers will not * be cleared and will preserve their values until cleared or overwritten by the author. * Defaults to false. * @param {'default'|'high-performance'|'low-power'} [options.powerPreference] - A hint to the * user agent indicating what configuration of GPU is suitable for the WebGL context. Possible * values are: * * - 'default': Let the user agent decide which GPU configuration is most suitable. This is the * default value. * - 'high-performance': Prioritizes rendering performance over power consumption. * - 'low-power': Prioritizes power saving over rendering performance. * * Defaults to 'default'. * @param {boolean} [options.failIfMajorPerformanceCaveat] - Boolean that indicates if a * context will be created if the system performance is low or if no hardware GPU is available. * Defaults to false. * @param {boolean} [options.preferWebGl2] - Boolean that indicates if a WebGl2 context should * be preferred. Defaults to true. * @param {boolean} [options.desynchronized] - Boolean that hints the user agent to reduce the * latency by desynchronizing the canvas paint cycle from the event loop. Defaults to false. * @param {boolean} [options.xrCompatible] - Boolean that hints to the user agent to use a * compatible graphics adapter for an immersive XR device. * @param {WebGLRenderingContext | WebGL2RenderingContext} [options.gl] - The rendering context * to use. If not specified, a new context will be created. */ constructor(canvas, options = {}) { var _options$antialias; super(canvas, options); /** * The WebGL context managed by the graphics device. The type could also technically be * `WebGLRenderingContext` if WebGL 2.0 is not available. But in order for IntelliSense to be * able to function for all WebGL calls in the codebase, we specify `WebGL2RenderingContext` * here instead. * * @type {WebGL2RenderingContext} * @ignore */ this.gl = void 0; /** * WebGLFramebuffer object that represents the backbuffer of the device for a rendering frame. * When null, this is a framebuffer created when the device was created, otherwise it is a * framebuffer supplied by the XR session. * * @ignore */ this._defaultFramebuffer = null; /** * True if the default framebuffer has changed since the last frame. * * @ignore */ this._defaultFramebufferChanged = false; options = this.initOptions; this.updateClientRect(); // initialize this before registering lost context handlers to avoid undefined access when the device is created lost. this.initTextureUnits(); // Add handlers for when the WebGL context is lost or restored this.contextLost = false; this._contextLostHandler = event => { event.preventDefault(); this.loseContext(); Debug.log('pc.GraphicsDevice: WebGL context lost.'); this.fire('devicelost'); }; this._contextRestoredHandler = () => { Debug.log('pc.GraphicsDevice: WebGL context restored.'); this.restoreContext(); this.fire('devicerestored'); }; // #4136 - turn off antialiasing on AppleWebKit browsers 15.4 const ua = typeof navigator !== 'undefined' && navigator.userAgent; this.forceDisableMultisampling = ua && ua.includes('AppleWebKit') && (ua.includes('15.4') || ua.includes('15_4')); if (this.forceDisableMultisampling) { options.antialias = false; Debug.log('Antialiasing has been turned off due to rendering issues on AppleWebKit 15.4'); } // #5856 - turn off antialiasing on Firefox running on Windows / Android if (platform.browserName === 'firefox') { const _ua = typeof navigator !== 'undefined' ? navigator.userAgent : ''; const match = _ua.match(/Firefox\/(\d+(\.\d+)*)/); const firefoxVersion = match ? match[1] : null; if (firefoxVersion) { const version = parseFloat(firefoxVersion); const disableAntialias = platform.name === 'windows' && (version >= 120 || version === 115) || platform.name === 'android' && version >= 132; if (disableAntialias) { options.antialias = false; Debug.log(`Antialiasing has been turned off due to rendering issues on Firefox ${platform.name} platform version ${firefoxVersion}`); } } } /** @type {WebGL2RenderingContext} */ let gl = null; // we always allocate the default framebuffer without antialiasing, so remove that option this.backBufferAntialias = (_options$antialias = options.antialias) != null ? _options$antialias : false; options.antialias = false; // Retrieve the WebGL context if (options.gl) { gl = options.gl; } else { const preferWebGl2 = options.preferWebGl2 !== undefined ? options.preferWebGl2 : true; const names = preferWebGl2 ? ['webgl2', 'webgl', 'experimental-webgl'] : ['webgl', 'experimental-webgl']; for (let i = 0; i < names.length; i++) { gl = canvas.getContext(names[i], options); if (gl) { break; } } } if (!gl) { throw new Error('WebGL not supported'); } this.gl = gl; this.isWebGL2 = typeof WebGL2RenderingContext !== 'undefined' && gl instanceof WebGL2RenderingContext; this.isWebGL1 = !this.isWebGL2; this._deviceType = this.isWebGL2 ? DEVICETYPE_WEBGL2 : DEVICETYPE_WEBGL1; // pixel format of the framebuffer this.updateBackbufferFormat(null); const isChrome = platform.browserName === 'chrome'; const isSafari = platform.browserName === 'safari'; const isMac = platform.browser && navigator.appVersion.indexOf('Mac') !== -1; // enable temporary texture unit workaround on desktop safari this._tempEnableSafariTextureUnitWorkaround = isSafari; // enable temporary workaround for glBlitFramebuffer failing on Mac Chrome (#2504) this._tempMacChromeBlitFramebufferWorkaround = isMac && isChrome && !options.alpha; canvas.addEventListener('webglcontextlost', this._contextLostHandler, false); canvas.addEventListener('webglcontextrestored', this._contextRestoredHandler, false); this.initializeExtensions(); this.initializeCapabilities(); this.initializeRenderState(); this.initializeContextCaches(); this.createBackbuffer(null); // only enable ImageBitmap on chrome this.supportsImageBitmap = !isSafari && typeof ImageBitmap !== 'undefined'; // supported sampler types this._samplerTypes = new Set([...[gl.SAMPLER_2D, gl.SAMPLER_CUBE], ...(this.isWebGL2 ? [gl.UNSIGNED_INT_SAMPLER_2D, gl.INT_SAMPLER_2D, gl.SAMPLER_2D_SHADOW, gl.SAMPLER_CUBE_SHADOW, gl.SAMPLER_3D, gl.INT_SAMPLER_3D, gl.UNSIGNED_INT_SAMPLER_3D, gl.SAMPLER_2D_ARRAY, gl.INT_SAMPLER_2D_ARRAY, gl.UNSIGNED_INT_SAMPLER_2D_ARRAY] : [])]); this.glAddress = [gl.REPEAT, gl.CLAMP_TO_EDGE, gl.MIRRORED_REPEAT]; this.glBlendEquation = [gl.FUNC_ADD, gl.FUNC_SUBTRACT, gl.FUNC_REVERSE_SUBTRACT, this.isWebGL2 ? gl.MIN : this.extBlendMinmax ? this.extBlendMinmax.MIN_EXT : gl.FUNC_ADD, this.isWebGL2 ? gl.MAX : this.extBlendMinmax ? this.extBlendMinmax.MAX_EXT : gl.FUNC_ADD]; this.glBlendFunctionColor = [gl.ZERO, gl.ONE, gl.SRC_COLOR, gl.ONE_MINUS_SRC_COLOR, gl.DST_COLOR, gl.ONE_MINUS_DST_COLOR, gl.SRC_ALPHA, gl.SRC_ALPHA_SATURATE, gl.ONE_MINUS_SRC_ALPHA, gl.DST_ALPHA, gl.ONE_MINUS_DST_ALPHA, gl.CONSTANT_COLOR, gl.ONE_MINUS_CONSTANT_COLOR]; this.glBlendFunctionAlpha = [gl.ZERO, gl.ONE, gl.SRC_COLOR, gl.ONE_MINUS_SRC_COLOR, gl.DST_COLOR, gl.ONE_MINUS_DST_COLOR, gl.SRC_ALPHA, gl.SRC_ALPHA_SATURATE, gl.ONE_MINUS_SRC_ALPHA, gl.DST_ALPHA, gl.ONE_MINUS_DST_ALPHA, gl.CONSTANT_ALPHA, gl.ONE_MINUS_CONSTANT_ALPHA]; this.glComparison = [gl.NEVER, gl.LESS, gl.EQUAL, gl.LEQUAL, gl.GREATER, gl.NOTEQUAL, gl.GEQUAL, gl.ALWAYS]; this.glStencilOp = [gl.KEEP, gl.ZERO, gl.REPLACE, gl.INCR, gl.INCR_WRAP, gl.DECR, gl.DECR_WRAP, gl.INVERT]; this.glClearFlag = [0, gl.COLOR_BUFFER_BIT, gl.DEPTH_BUFFER_BIT, gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT, gl.STENCIL_BUFFER_BIT, gl.STENCIL_BUFFER_BIT | gl.COLOR_BUFFER_BIT, gl.STENCIL_BUFFER_BIT | gl.DEPTH_BUFFER_BIT, gl.STENCIL_BUFFER_BIT | gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT]; this.glCull = [0, gl.BACK, gl.FRONT, gl.FRONT_AND_BACK]; this.glFilter = [gl.NEAREST, gl.LINEAR, gl.NEAREST_MIPMAP_NEAREST, gl.NEAREST_MIPMAP_LINEAR, gl.LINEAR_MIPMAP_NEAREST, gl.LINEAR_MIPMAP_LINEAR]; this.glPrimitive = [gl.POINTS, gl.LINES, gl.LINE_LOOP, gl.LINE_STRIP, gl.TRIANGLES, gl.TRIANGLE_STRIP, gl.TRIANGLE_FAN]; this.glType = [gl.BYTE, gl.UNSIGNED_BYTE, gl.SHORT, gl.UNSIGNED_SHORT, gl.INT, gl.UNSIGNED_INT, gl.FLOAT, gl.HALF_FLOAT]; this.pcUniformType = {}; this.pcUniformType[gl.BOOL] = UNIFORMTYPE_BOOL; this.pcUniformType[gl.INT] = UNIFORMTYPE_INT; this.pcUniformType[gl.FLOAT] = UNIFORMTYPE_FLOAT; this.pcUniformType[gl.FLOAT_VEC2] = UNIFORMTYPE_VEC2; this.pcUniformType[gl.FLOAT_VEC3] = UNIFORMTYPE_VEC3; this.pcUniformType[gl.FLOAT_VEC4] = UNIFORMTYPE_VEC4; this.pcUniformType[gl.INT_VEC2] = UNIFORMTYPE_IVEC2; this.pcUniformType[gl.INT_VEC3] = UNIFORMTYPE_IVEC3; this.pcUniformType[gl.INT_VEC4] = UNIFORMTYPE_IVEC4; this.pcUniformType[gl.BOOL_VEC2] = UNIFORMTYPE_BVEC2; this.pcUniformType[gl.BOOL_VEC3] = UNIFORMTYPE_BVEC3; this.pcUniformType[gl.BOOL_VEC4] = UNIFORMTYPE_BVEC4; this.pcUniformType[gl.FLOAT_MAT2] = UNIFORMTYPE_MAT2; this.pcUniformType[gl.FLOAT_MAT3] = UNIFORMTYPE_MAT3; this.pcUniformType[gl.FLOAT_MAT4] = UNIFORMTYPE_MAT4; this.pcUniformType[gl.SAMPLER_2D] = UNIFORMTYPE_TEXTURE2D; this.pcUniformType[gl.SAMPLER_CUBE] = UNIFORMTYPE_TEXTURECUBE; this.pcUniformType[gl.UNSIGNED_INT] = UNIFORMTYPE_UINT; this.pcUniformType[gl.UNSIGNED_INT_VEC2] = UNIFORMTYPE_UVEC2; this.pcUniformType[gl.UNSIGNED_INT_VEC3] = UNIFORMTYPE_UVEC3; this.pcUniformType[gl.UNSIGNED_INT_VEC4] = UNIFORMTYPE_UVEC4; if (this.isWebGL2) { this.pcUniformType[gl.SAMPLER_2D_SHADOW] = UNIFORMTYPE_TEXTURE2D_SHADOW; this.pcUniformType[gl.SAMPLER_CUBE_SHADOW] = UNIFORMTYPE_TEXTURECUBE_SHADOW; this.pcUniformType[gl.SAMPLER_2D_ARRAY] = UNIFORMTYPE_TEXTURE2D_ARRAY; this.pcUniformType[gl.SAMPLER_3D] = UNIFORMTYPE_TEXTURE3D; this.pcUniformType[gl.INT_SAMPLER_2D] = UNIFORMTYPE_ITEXTURE2D; this.pcUniformType[gl.UNSIGNED_INT_SAMPLER_2D] = UNIFORMTYPE_UTEXTURE2D; this.pcUniformType[gl.INT_SAMPLER_CUBE] = UNIFORMTYPE_ITEXTURECUBE; this.pcUniformType[gl.UNSIGNED_INT_SAMPLER_2D] = UNIFORMTYPE_UTEXTURECUBE; this.pcUniformType[gl.INT_SAMPLER_3D] = UNIFORMTYPE_ITEXTURE3D; this.pcUniformType[gl.UNSIGNED_INT_SAMPLER_3D] = UNIFORMTYPE_UTEXTURE3D; this.pcUniformType[gl.INT_SAMPLER_2D_ARRAY] = UNIFORMTYPE_ITEXTURE2D_ARRAY; this.pcUniformType[gl.UNSIGNED_INT_SAMPLER_2D_ARRAY] = UNIFORMTYPE_UTEXTURE2D_ARRAY; } this.targetToSlot = {}; this.targetToSlot[gl.TEXTURE_2D] = 0; this.targetToSlot[gl.TEXTURE_CUBE_MAP] = 1; this.targetToSlot[gl.TEXTURE_3D] = 2; // Define the uniform commit functions let scopeX, scopeY, scopeZ, scopeW; let uniformValue; this.commitFunction = []; this.commitFunction[UNIFORMTYPE_BOOL] = function (uniform, value) { if (uniform.value !== value) { gl.uniform1i(uniform.locationId, value); uniform.value = value; } }; this.commitFunction[UNIFORMTYPE_INT] = this.commitFunction[UNIFORMTYPE_BOOL]; this.commitFunction[UNIFORMTYPE_FLOAT] = function (uniform, value) { if (uniform.value !== value) { gl.uniform1f(uniform.locationId, value); uniform.value = value; } }; this.commitFunction[UNIFORMTYPE_VEC2] = function (uniform, value) { uniformValue = uniform.value; scopeX = value[0]; scopeY = value[1]; if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY) { gl.uniform2fv(uniform.locationId, value); uniformValue[0] = scopeX; uniformValue[1] = scopeY; } }; this.commitFunction[UNIFORMTYPE_VEC3] = function (uniform, value) { uniformValue = uniform.value; scopeX = value[0]; scopeY = value[1]; scopeZ = value[2]; if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ) { gl.uniform3fv(uniform.locationId, value); uniformValue[0] = scopeX; uniformValue[1] = scopeY; uniformValue[2] = scopeZ; } }; this.commitFunction[UNIFORMTYPE_VEC4] = function (uniform, value) { uniformValue = uniform.value; scopeX = value[0]; scopeY = value[1]; scopeZ = value[2]; scopeW = value[3]; if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ || uniformValue[3] !== scopeW) { gl.uniform4fv(uniform.locationId, value); uniformValue[0] = scopeX; uniformValue[1] = scopeY; uniformValue[2] = scopeZ; uniformValue[3] = scopeW; } }; this.commitFunction[UNIFORMTYPE_IVEC2] = function (uniform, value) { uniformValue = uniform.value; scopeX = value[0]; scopeY = value[1]; if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY) { gl.uniform2iv(uniform.locationId, value); uniformValue[0] = scopeX; uniformValue[1] = scopeY; } }; this.commitFunction[UNIFORMTYPE_BVEC2] = this.commitFunction[UNIFORMTYPE_IVEC2]; this.commitFunction[UNIFORMTYPE_IVEC3] = function (uniform, value) { uniformValue = uniform.value; scopeX = value[0]; scopeY = value[1]; scopeZ = value[2]; if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ) { gl.uniform3iv(uniform.locationId, value); uniformValue[0] = scopeX; uniformValue[1] = scopeY; uniformValue[2] = scopeZ; } }; this.commitFunction[UNIFORMTYPE_BVEC3] = this.commitFunction[UNIFORMTYPE_IVEC3]; this.commitFunction[UNIFORMTYPE_IVEC4] = function (uniform, value) { uniformValue = uniform.value; scopeX = value[0]; scopeY = value[1]; scopeZ = value[2]; scopeW = value[3]; if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ || uniformValue[3] !== scopeW) { gl.uniform4iv(uniform.locationId, value); uniformValue[0] = scopeX; uniformValue[1] = scopeY; uniformValue[2] = scopeZ; uniformValue[3] = scopeW; } }; this.commitFunction[UNIFORMTYPE_BVEC4] = this.commitFunction[UNIFORMTYPE_IVEC4]; this.commitFunction[UNIFORMTYPE_MAT2] = function (uniform, value) { gl.uniformMatrix2fv(uniform.locationId, false, value); }; this.commitFunction[UNIFORMTYPE_MAT3] = function (uniform, value) { gl.uniformMatrix3fv(uniform.locationId, false, value); }; this.commitFunction[UNIFORMTYPE_MAT4] = function (uniform, value) { gl.uniformMatrix4fv(uniform.locationId, false, value); }; this.commitFunction[UNIFORMTYPE_FLOATARRAY] = function (uniform, value) { gl.uniform1fv(uniform.locationId, value); }; this.commitFunction[UNIFORMTYPE_VEC2ARRAY] = function (uniform, value) { gl.uniform2fv(uniform.locationId, value); }; this.commitFunction[UNIFORMTYPE_VEC3ARRAY] = function (uniform, value) { gl.uniform3fv(uniform.locationId, value); }; this.commitFunction[UNIFORMTYPE_VEC4ARRAY] = function (uniform, value) { gl.uniform4fv(uniform.locationId, value); }; this.commitFunction[UNIFORMTYPE_UINT] = function (uniform, value) { if (uniform.value !== value) { gl.uniform1ui(uniform.locationId, value); uniform.value = value; } }; this.commitFunction[UNIFORMTYPE_UVEC2] = function (uniform, value) { uniformValue = uniform.value; scopeX = value[0]; scopeY = value[1]; if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY) { gl.uniform2uiv(uniform.locationId, value); uniformValue[0] = scopeX; uniformValue[1] = scopeY; } }; this.commitFunction[UNIFORMTYPE_UVEC3] = function (uniform, value) { uniformValue = uniform.value; scopeX = value[0]; scopeY = value[1]; scopeZ = value[2]; if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ) { gl.uniform3uiv(uniform.locationId, value); uniformValue[0] = scopeX; uniformValue[1] = scopeY; uniformValue[2] = scopeZ; } }; this.commitFunction[UNIFORMTYPE_UVEC4] = function (uniform, value) { uniformValue = uniform.value; scopeX = value[0]; scopeY = value[1]; scopeZ = value[2]; scopeW = value[3]; if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ || uniformValue[3] !== scopeW) { gl.uniform4uiv(uniform.locationId, value); uniformValue[0] = scopeX; uniformValue[1] = scopeY; uniformValue[2] = scopeZ; uniformValue[3] = scopeW; } }; this.commitFunction[UNIFORMTYPE_INTARRAY] = function (uniform, value) { gl.uniform1iv(uniform.locationId, value); }; this.commitFunction[UNIFORMTYPE_UINTARRAY] = function (uniform, value) { gl.uniform1uiv(uniform.locationId, value); }; this.commitFunction[UNIFORMTYPE_BOOLARRAY] = this.commitFunction[UNIFORMTYPE_INTARRAY]; this.commitFunction[UNIFORMTYPE_IVEC2ARRAY] = function (uniform, value) { gl.uniform2iv(uniform.locationId, value); }; this.commitFunction[UNIFORMTYPE_UVEC2ARRAY] = function (uniform, value) { gl.uniform2uiv(uniform.locationId, value); }; this.commitFunction[UNIFORMTYPE_BVEC2ARRAY] = this.commitFunction[UNIFORMTYPE_IVEC2ARRAY]; this.commitFunction[UNIFORMTYPE_IVEC3ARRAY] = function (uniform, value) { gl.uniform3iv(uniform.locationId, value); }; this.commitFunction[UNIFORMTYPE_UVEC3ARRAY] = function (uniform, value) { gl.uniform3uiv(uniform.locationId, value); }; this.commitFunction[UNIFORMTYPE_BVEC3ARRAY] = this.commitFunction[UNIFORMTYPE_IVEC3ARRAY]; this.commitFunction[UNIFORMTYPE_IVEC4ARRAY] = function (uniform, value) { gl.uniform4iv(uniform.locationId, value); }; this.commitFunction[UNIFORMTYPE_UVEC4ARRAY] = function (uniform, value) { gl.uniform4uiv(uniform.locationId, value); }; this.commitFunction[UNIFORMTYPE_BVEC4ARRAY] = this.commitFunction[UNIFORMTYPE_IVEC4ARRAY]; this.commitFunction[UNIFORMTYPE_MAT4ARRAY] = function (uniform, value) { gl.uniformMatrix4fv(uniform.locationId, false, value); }; this.supportsBoneTextures = this.extTextureFloat && this.maxVertexTextures > 0; // Calculate an estimate of the maximum number of bones that can be uploaded to the GPU // based on the number of available uniforms and the number of uniforms required for non- // bone data. This is based off of the Standard shader. A user defined shader may have // even less space available for bones so this calculated value can be overridden via // pc.GraphicsDevice.setBoneLimit. let numUniforms = this.vertexUniformsCount; numUniforms -= 4 * 4; // Model, view, projection and shadow matrices numUniforms -= 8; // 8 lights max, each specifying a position vector numUniforms -= 1; // Eye position numUniforms -= 4 * 4; // Up to 4 texture transforms this.boneLimit = Math.floor(numUniforms / 3); // each bone uses 3 uniforms // Put a limit on the number of supported bones when texture skinning is not supported. // Some GPUs have demonstrated performance issues if the number of vectors allocated to the // skin matrix palette is left unbounded this.boneLimit = Math.min(this.boneLimit, 128); this.constantTexSource = this.scope.resolve('source'); if (this.extTextureFloat) { if (this.isWebGL2) { // In WebGL2 float texture renderability is dictated by the EXT_color_buffer_float extension this.textureFloatRenderable = !!this.extColorBufferFloat; } else { // In WebGL1 we should just try rendering into a float texture this.textureFloatRenderable = testRenderable(gl, gl.FLOAT); } } else { this.textureFloatRenderable = false; } // two extensions allow us to render to half float buffers if (this.extColorBufferHalfFloat) { this.textureHalfFloatRenderable = !!this.extColorBufferHalfFloat; } else if (this.extTextureHalfFloat) { if (this.isWebGL2) { // EXT_color_buffer_float should affect both float and halffloat formats this.textureHalfFloatRenderable = !!this.extColorBufferFloat; } else { // Manual render check for half float this.textureHalfFloatRenderable = testRenderable(gl, this.extTextureHalfFloat.HALF_FLOAT_OES); } } else { this.textureHalfFloatRenderable = false; } this.supportsMorphTargetTexturesCore = this.maxPrecision === 'highp' && this.maxVertexTextures >= 2; this.supportsDepthShadow = this.isWebGL2; this._textureFloatHighPrecision = undefined; this._textureHalfFloatUpdatable = undefined; // area light LUT format - order of preference: half, float, 8bit this.areaLightLutFormat = PIXELFORMAT_RGBA8; if (this.extTextureHalfFloat && this.textureHalfFloatUpdatable && this.extTextureHalfFloatLinear) { this.areaLightLutFormat = PIXELFORMAT_RGBA16F; } else if (this.extTextureFloat && this.extTextureFloatLinear) { this.areaLightLutFormat = PIXELFORMAT_RGBA32F; } this.postInit(); } postInit() { super.postInit(); this.gpuProfiler = new WebglGpuProfiler(this); } /** * Destroy the graphics device. */ destroy() { super.destroy(); const gl = this.gl; if (this.isWebGL2 && this.feedback) { gl.deleteTransformFeedback(this.feedback); } this.clearVertexArrayObjectCache(); this.canvas.removeEventListener('webglcontextlost', this._contextLostHandler, false); this.canvas.removeEventListener('webglcontextrestored', this._contextRestoredHandler, false); this._contextLostHandler = null; this._contextRestoredHandler = null; this.gl = null; super.postDestroy(); } createBackbuffer(frameBuffer) { this.supportsStencil = this.initOptions.stencil; this.backBuffer = new RenderTarget({ name: 'WebglFramebuffer', graphicsDevice: this, depth: this.initOptions.depth, stencil: this.supportsStencil, samples: this.samples }); // use the default WebGL framebuffer for rendering this.backBuffer.impl.suppliedColorFramebuffer = frameBuffer; } // Update framebuffer format based on the current framebuffer, as this is use to create matching multi-sampled framebuffer updateBackbufferFormat(framebuffer) { const gl = this.gl; gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); const alphaBits = this.gl.getParameter(this.gl.ALPHA_BITS); this.backBufferFormat = alphaBits ? PIXELFORMAT_RGBA8 : PIXELFORMAT_RGB8; } updateBackbuffer() { const resolutionChanged = this.canvas.width !== this.backBufferSize.x || this.canvas.height !== this.backBufferSize.y; if (this._defaultFramebufferChanged || resolutionChanged) { // if the default framebuffer changes (entering or exiting XR for example) if (this._defaultFramebufferChanged) { this.updateBackbufferFormat(this._defaultFramebuffer); } this._defaultFramebufferChanged = false; this.backBufferSize.set(this.canvas.width, this.canvas.height); // recreate the backbuffer with newly supplied framebuffer this.backBuffer.destroy(); this.createBackbuffer(this._defaultFramebuffer); } } // provide webgl implementation for the vertex buffer createVertexBufferImpl(vertexBuffer, format) { return new WebglVertexBuffer(); } // provide webgl implementation for the index buffer createIndexBufferImpl(indexBuffer) { return new WebglIndexBuffer(indexBuffer); } createShaderImpl(shader) { return new WebglShader(shader); } createTextureImpl(texture) { return new WebglTexture(); } createRenderTargetImpl(renderTarget) { return new WebglRenderTarget(); } pushMarker(name) { if (platform.browser && window.spector) { const label = DebugGraphics.toString(); window.spector.setMarker(`${label} #`); } } popMarker() { if (platform.browser && window.spector) { const label = DebugGraphics.toString(); if (label.length) { window.spector.setMarker(`${label} #`); } else { window.spector.clearMarker(); } } } /** * Query the precision supported by ints and floats in vertex and fragment shaders. Note that * getShaderPrecisionFormat is not guaranteed to be present (such as some instances of the * default Android browser). In this case, assume highp is available. * * @returns {string} "highp", "mediump" or "lowp" * @ignore */ getPrecision() { const gl = this.gl; let precision = 'highp'; if (gl.getShaderPrecisionFormat) { const vertexShaderPrecisionHighpFloat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_FLOAT); const vertexShaderPrecisionMediumpFloat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.MEDIUM_FLOAT); const fragmentShaderPrecisionHighpFloat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT); const fragmentShaderPrecisionMediumpFloat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT); if (vertexShaderPrecisionHighpFloat && vertexShaderPrecisionMediumpFloat && fragmentShaderPrecisionHighpFloat && fragmentShaderPrecisionMediumpFloat) { const highpAvailable = vertexShaderPrecisionHighpFloat.precision > 0 && fragmentShaderPrecisionHighpFloat.precision > 0; const mediumpAvailable = vertexShaderPrecisionMediumpFloat.precision > 0 && fragmentShaderPrecisionMediumpFloat.precision > 0; if (!highpAvailable) { if (mediumpAvailable) { precision = 'mediump'; Debug.warn('WARNING: highp not supported, using mediump'); } else { precision = 'lowp'; Debug.warn('WARNING: highp and mediump not supported, using lowp'); } } } } return precision; } getExtension() { for (let i = 0; i < arguments.length; i++) { if (this.supportedExtensions.indexOf(arguments[i]) !== -1) { return this.gl.getExtension(arguments[i]); } } return null; } get extDisjointTimerQuery() { // lazy evaluation as this is not typically used if (!this._extDisjointTimerQuery) { if (this.isWebGL2) { // Note that Firefox exposes EXT_disjoint_timer_query under WebGL2 rather than EXT_disjoint_timer_query_webgl2 this._extDisjointTimerQuery = this.getExtension('EXT_disjoint_timer_query_webgl2', 'EXT_disjoint_timer_query'); } } return this._extDisjointTimerQuery; } /** * Initialize the extensions provided by the WebGL context. * * @ignore */ initializeExtensions() { var _gl$getSupportedExten; const gl = this.gl; this.supportedExtensions = (_gl$getSupportedExten = gl.getSupportedExtensions()) != null ? _gl$getSupportedExten : []; this._extDisjointTimerQuery = null; if (this.isWebGL2) { this.extBlendMinmax = true; this.extDrawBuffers = true; this.drawBuffers = gl.drawBuffers.bind(gl); this.extInstancing = true; this.extStandardDerivatives = true; this.extTextureFloat = true; this.extTextureHalfFloat = true; this.textureHalfFloatFilterable = true; this.extTextureLod = true; this.extUintElement = true; this.extVertexArrayObject = true; this.extColorBufferFloat = this.getExtension('EXT_color_buffer_float'); this.extDepthTexture = true; this.textureRG11B10Renderable = true; } else { var _this$extDrawBuffers; this.extBlendMinmax = this.getExtension('EXT_blend_minmax'); this.extDrawBuffers = this.getExtension('WEBGL_draw_buffers'); this.extInstancing = this.getExtension('ANGLE_instanced_arrays'); this.drawBuffers = (_this$extDrawBuffers = this.extDrawBuffers) == null ? void 0 : _this$extDrawBuffers.drawBuffersWEBGL.bind(this.extDrawBuffers); if (this.extInstancing) { // Install the WebGL 2 Instancing API for WebGL 1.0 const ext = this.extInstancing; gl.drawArraysInstanced = ext.drawArraysInstancedANGLE.bind(ext); gl.drawElementsInstanced = ext.drawElementsInstancedANGLE.bind(ext); gl.vertexAttribDivisor = ext.vertexAttribDivisorANGLE.bind(ext); } this.extStandardDerivatives = this.getExtension('OES_standard_derivatives'); this.extTextureFloat = this.getExtension('OES_texture_float'); this.extTextureLod = this.getExtension('EXT_shader_texture_lod'); this.extUintElement = this.getExtension('OES_element_index_uint'); this.extVertexArrayObject = this.getExtension('OES_vertex_array_object'); if (this.extVertexArrayObject) { // Install the WebGL 2 VAO API for WebGL 1.0 const ext = this.extVertexArrayObject; gl.createVertexArray = ext.createVertexArrayOES.bind(ext); gl.deleteVertexArray = ext.deleteVertexArrayOES.bind(ext); gl.isVertexArray = ext.isVertexArrayOES.bind(ext); gl.bindVertexArray = ext.bindVertexArrayOES.bind(ext); } this.extColorBufferFloat = null; this.extDepthTexture = gl.getExtension('WEBGL_depth_texture'); this.extTextureHalfFloat = this.getExtension('OES_texture_half_float'); this.extTextureHalfFloatLinear = this.getExtension('OES_texture_half_float_linear'); this.textureHalfFloatFilterable = !!this.extTextureHalfFloatLinear; } this.extDebugRendererInfo = this.getExtension('WEBGL_debug_renderer_info'); this.extTextureFloatLinear = this.getExtension('OES_texture_float_linear'); this.textureFloatFilterable = !!this.extTextureFloatLinear; this.extFloatBlend = this.getExtension('EXT_float_blend'); this.extTextureFilterAnisotropic = this.getExtension('EXT_texture_filter_anisotropic', 'WEBKIT_EXT_texture_filter_anisotropic'); this.extCompressedTextureETC1 = this.getExtension('WEBGL_compressed_texture_etc1'); this.extCompressedTextureETC = this.getExtension('WEBGL_compressed_texture_etc'); this.extCompressedTexturePVRTC = this.getExtension('WEBGL_compressed_texture_pvrtc', 'WEBKIT_WEBGL_compressed_texture_pvrtc'); this.extCompressedTextureS3TC = this.getExtension('WEBGL_compressed_texture_s3tc', 'WEBKIT_WEBGL_compressed_texture_s3tc'); this.extCompressedTextureATC = this.getExtension('WEBGL_compressed_texture_atc'); this.extCompressedTextureASTC = this.getExtension('WEBGL_compressed_texture_astc'); this.extParallelShaderCompile = this.getExtension('KHR_parallel_shader_compile'); // iOS exposes this for half precision render targets on both Webgl1 and 2 from iOS v 14.5beta this.extColorBufferHalfFloat = this.getExtension('EXT_color_buffer_half_float'); } /** * Query the capabilities of the WebGL context. * * @ignore */ initializeCapabilities() { var _contextAttribs$antia, _contextAttribs$stenc; const gl = this.gl; let ext; const userAgent = typeof navigator !== 'undefined' ? navigator.userAgent : ''; this.maxPrecision = this.precision = this.getPrecision(); const contextAttribs = gl.getContextAttributes(); this.supportsMsaa = (_contextAttribs$antia = contextAttribs == null ? void 0 : contextAttribs.antialias) != null ? _contextAttribs$antia : false; this.supportsStencil = (_contextAttribs$stenc = contextAttribs == null ? void 0 : contextAttribs.stencil) != null ? _contextAttribs$stenc : false; this.supportsInstancing = !!this.extInstancing; // Query parameter values from the WebGL context this.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE); this.maxCubeMapSize = gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE); this.maxRenderBufferSize = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE); this.maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); this.maxCombinedTextures = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS); this.maxVertexTextures = gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS); this.vertexUniformsCount = gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS); this.fragmentUniformsCount = gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS); if (this.isWebGL2) { this.maxDrawBuffers = gl.getParameter(gl.MAX_DRAW_BUFFERS); this.maxColorAttachments = gl.getParameter(gl.MAX_COLOR_ATTACHMENTS); this.maxVolumeSize = gl.getParameter(gl.MAX_3D_TEXTURE_SIZE); this.supportsMrt = true; this.supportsVolumeTextures = true; } else { ext = this.extDrawBuffers; this.supportsMrt = !!ext; this.maxDrawBuffers = ext ? gl.getParameter(ext.MAX_DRAW_BUFFERS_WEBGL) : 1; this.maxColorAttachments = ext ? gl.getParameter(ext.MAX_COLOR_ATTACHMENTS_WEBGL) : 1; this.maxVolumeSize = 1; } ext = this.extDebugRendererInfo; this.unmaskedRenderer = ext ? gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) : ''; this.unmaskedVendor = ext ? gl.getParameter(ext.UNMASKED_VENDOR_WEBGL) : ''; // Mali-G52 has rendering issues with GPU particles including // SM-A225M, M2003J15SC and KFRAWI (Amazon Fire HD 8 2022) const maliRendererRegex = /\bMali-G52+/; // Samsung devices with Exynos (ARM) either crash or render incorrectly when using GPU for particles. See: // https://github.com/playcanvas/engine/issues/3967 // https://github.com/playcanvas/engine/issues/3415 // https://github.com/playcanvas/engine/issues/4514 // Example UA matches: Starting 'SM' and any combination of letters or numbers: // Mozilla/5.0 (Linux, Android 12; SM-G970F Build/SP1A.210812.016; wv) const samsungModelRegex = /SM-[a-zA-Z0-9]+/; this.supportsGpuParticles = !(this.unmaskedVendor === 'ARM' && userAgent.match(samsungModelRegex)) && !this.unmaskedRenderer.match(maliRendererRegex); ext = this.extTextureFilterAnisotropic; this.maxAnisotropy = ext ? gl.getParameter(ext.MAX_TEXTURE_MAX_ANISOTROPY_EXT) : 1; const antialiasSupported = this.isWebGL2 && !this.forceDisableMultisampling; this.maxSamples = antialiasSupported ? gl.getParameter(gl.MAX_SAMPLES) : 1; // some devices incorrectly report max samples larger than 4 this.maxSamples = Math.min(this.maxSamples, 4); // we handle anti-aliasing internally by allocating multi-sampled backbuffer this.samples = antialiasSupported && this.backBufferAntialias ? this.maxSamples : 1; // Don't allow area lights on old android devices, they often fail to compile the shader, run it incorrectly or are very slow. this.supportsAreaLights = this.isWebGL2 || !platform.android; // supports texture fetch instruction this.supportsTextureFetch = this.isWebGL2; // Also do not allow them when we only have small number of texture units if (this.maxTextures <= 8) { this.supportsAreaLights = false; } } /** * Set the initial render state on the WebGL context. * * @ignore */ initializeRenderState() { super.initializeRenderState(); const gl = this.gl; // Initialize render state to a known start state // default blend state gl.disable(gl.BLEND); gl.blendFunc(gl.ONE, gl.ZERO); gl.blendEquation(gl.FUNC_ADD); gl.colorMask(true, true, true, true); gl.blendColor(0, 0, 0, 0); gl.enable(gl.CULL_FACE); this.cullFace = gl.BACK; gl.cullFace(gl.BACK); // default depth state gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.LEQUAL); gl.depthMask(true); this.stencil = false; gl.disable(gl.STENCIL_TEST); this.stencilFuncFront = this.stencilFuncBack = FUNC_ALWAYS; this.stencilRefFront = this.stencilRefBack = 0; this.stencilMaskFront = this.stencilMaskBack = 0xFF; gl.stencilFunc(gl.ALWAYS, 0, 0xFF); this.stencilFailFront = this.stencilFailBack = STENCILOP_KEEP; this.stencilZfailFront = this.stencilZfailBack = STENCILOP_KEEP; this.stencilZpassFront = this.stencilZpassBack = STENCILOP_KEEP; this.stencilWriteMaskFront = 0xFF; this.stencilWriteMaskBack = 0xFF; gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); gl.stencilMask(0xFF); this.alphaToCoverage = false; this.raster = true; if (this.isWebGL2) { gl.disable(gl.SAMPLE_ALPHA_TO_COVERAGE); gl.disable(gl.RASTERIZER_DISCARD); } this.depthBiasEnabled = false; gl.disable(gl.POLYGON_OFFSET_FILL); this.clearDepth = 1; gl.clearDepth(1); this.clearColor = new Color(0, 0, 0, 0); gl.clearColor(0, 0, 0, 0); this.clearStencil = 0; gl.clearStencil(0); if (this.isWebGL2) { gl.hint(gl.FRAGMENT_SHADER_DERIVATIVE_HINT, gl.NICEST); } else { if (this.extStandardDerivatives) { gl.hint(this.extStandardDerivatives.FRAGMENT_SHADER_DERIVATIVE_HINT_OES, gl.NICEST); } } gl.enable(gl.SCISSOR_TEST); gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE); this.unpackFlipY = false; gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); this.unpackPremultiplyAlpha = false; gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); } initTextureUnits(count = 16) { this.textureUnits = []; for (let i = 0; i < count; i++) { this.textureUnits.push([null, null, null]); } } initializeContextCaches() { super.initializeContextCaches(); // cache of VAOs this._vaoMap = new Map(); this.boundVao = null; this.activeFramebuffer = null; this.feedback = null; this.transformFeedbackBuffer = null; this.textureUnit = 0; this.initTextureUnits(this.maxCombinedTextures); } /** * Called when the WebGL context was lost. It releases all context related resources. * * @ignore */ loseContext() { super.loseContext(); // release shaders for (const shader of this.shaders) { shader.loseContext(); } } /** * Called when the WebGL context is restored. It reinitializes all context related resources. * * @ignore */ restoreContext() { this.initializeExtensions(); this.initializeCapabilities(); super.restoreContext(); // Recompile all shaders for (const shader of this.shaders) { shader.restoreContext(); } } /** * Set the active rectangle for rendering on the specified device. * * @param {number} x - The pixel space x-coordinate of the bottom left corner of the viewport. * @param {number} y - The pixel space y-coordinate of the bottom left corner of the viewport. * @param {number} w - The width of the viewport in pixels. * @param {number} h - The height of the viewport in pixels. */ setViewport(x, y, w, h) { if (this.vx !== x || this.vy !== y || this.vw !== w || this.vh !== h) { this.gl.viewport(x, y, w, h); this.vx = x; this.vy = y; this.vw = w; this.vh = h; } } /** * Set the active scissor rectangle on the specified device. * * @param {number} x - The pixel space x-coordinate of the bottom left corner of the scissor rectangle. * @param {number} y - The pixel space y-coordinate of the bottom left corner of the scissor rectangle. * @param {number} w - The width of the scissor rectangle in pixels. * @param {number} h - The height of the scissor rectangle in pixels. */ setScissor(x, y, w, h) { if (this.sx !== x || this.sy !== y || this.sw !== w || this.sh !== h) { this.gl.scissor(x, y, w, h); this.sx = x; this.sy = y; this.