UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

1,308 lines 83.6 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); 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 { CLEARFLAG_COLOR, CLEARFLAG_DEPTH, CLEARFLAG_STENCIL, CULLFACE_NONE, FILTER_NEAREST, FILTER_LINEAR, FILTER_NEAREST_MIPMAP_NEAREST, FILTER_NEAREST_MIPMAP_LINEAR, FILTER_LINEAR_MIPMAP_NEAREST, FILTER_LINEAR_MIPMAP_LINEAR, FUNC_ALWAYS, PIXELFORMAT_R8, PIXELFORMAT_RG8, PIXELFORMAT_RGB8, PIXELFORMAT_RGBA8, STENCILOP_KEEP, 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_FLOATARRAY, UNIFORMTYPE_TEXTURE2D_SHADOW, UNIFORMTYPE_TEXTURECUBE_SHADOW, UNIFORMTYPE_TEXTURE3D, UNIFORMTYPE_VEC2ARRAY, UNIFORMTYPE_VEC3ARRAY, UNIFORMTYPE_VEC4ARRAY, UNIFORMTYPE_UINT, UNIFORMTYPE_UVEC2, UNIFORMTYPE_UVEC3, UNIFORMTYPE_UVEC4, UNIFORMTYPE_ITEXTURE2D, UNIFORMTYPE_UTEXTURE2D, UNIFORMTYPE_ITEXTURECUBE, UNIFORMTYPE_UTEXTURECUBE, UNIFORMTYPE_ITEXTURE3D, UNIFORMTYPE_UTEXTURE3D, UNIFORMTYPE_ITEXTURE2D_ARRAY, UNIFORMTYPE_UTEXTURE2D_ARRAY, UNIFORMTYPE_INTARRAY, UNIFORMTYPE_UINTARRAY, UNIFORMTYPE_BOOLARRAY, UNIFORMTYPE_IVEC2ARRAY, UNIFORMTYPE_BVEC2ARRAY, UNIFORMTYPE_UVEC2ARRAY, UNIFORMTYPE_IVEC3ARRAY, UNIFORMTYPE_BVEC3ARRAY, UNIFORMTYPE_UVEC3ARRAY, UNIFORMTYPE_IVEC4ARRAY, UNIFORMTYPE_BVEC4ARRAY, UNIFORMTYPE_UVEC4ARRAY, UNIFORMTYPE_MAT4ARRAY, semanticToLocation, getPixelFormatArrayType, UNIFORMTYPE_TEXTURE2D_ARRAY, DEVICETYPE_WEBGL2, TEXPROPERTY_MIN_FILTER, TEXPROPERTY_MAG_FILTER, TEXPROPERTY_ADDRESS_U, TEXPROPERTY_ADDRESS_V, TEXPROPERTY_ADDRESS_W, TEXPROPERTY_COMPARE_ON_READ, TEXPROPERTY_COMPARE_FUNC, TEXPROPERTY_ANISOTROPY } 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 { WebglDrawCommands } from "./webgl-draw-commands.js"; import { WebglTexture } from "./webgl-texture.js"; import { WebglRenderTarget } from "./webgl-render-target.js"; import { WebglUploadStream } from "./webgl-upload-stream.js"; import { WebglXrBridge } from "./webgl-xr-bridge.js"; import { WebglXrMsaaCopy } from "./webgl-xr-msaa-copy.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"; import { TextureUtils } from "../texture-utils.js"; import { getBuiltInTexture } from "../built-in-textures.js"; const getPixelFormatChannelsForRgbaReadback = (format) => { switch (format) { case PIXELFORMAT_R8: return 1; case PIXELFORMAT_RG8: return 2; default: return 0; } }; const invalidateAttachments = []; 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.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 {WebGL2RenderingContext} [options.gl] - The rendering context * to use. If not specified, a new context will be created. */ constructor(canvas, options = {}) { super(canvas, options); /** * The WebGL2 context managed by the graphics device. * * @type {WebGL2RenderingContext} * @ignore */ __publicField(this, "gl"); /** * 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 */ __publicField(this, "_defaultFramebuffer", null); /** * True if the default framebuffer has changed since the last frame. * * @ignore */ __publicField(this, "_defaultFramebufferChanged", false); /** * Helper for resolving MSAA color into the XR framebuffer via a blit-to-scratch + fullscreen * quad copy on visionOS. Created lazily; null on all other platforms. * * @type {import('./webgl-xr-msaa-copy.js').WebglXrMsaaCopy|null} * @private */ __publicField(this, "_xrMsaaCopy", null); options = this.initOptions; this.updateClientRect(); this.initTextureUnits(); this.contextLost = false; this._contextLostHandler = (event) => { event.preventDefault(); this.loseContext(); }; this._contextRestoredHandler = () => { this.restoreContext(); }; 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"); } if (platform.browserName === "firefox") { const ua2 = typeof navigator !== "undefined" ? navigator.userAgent : ""; const match = ua2.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}`); } } } this.backBufferAntialias = options.antialias ?? false; options.antialias = false; const gl = options.gl ?? canvas.getContext("webgl2", options); if (!gl) { throw new Error("WebGL not supported"); } this.gl = gl; this.isWebGL2 = true; this._deviceType = DEVICETYPE_WEBGL2; this.updateBackbufferFormat(null); const isChrome = platform.browserName === "chrome"; const isSafari = platform.browserName === "safari"; const isMac = platform.browser && navigator.appVersion.indexOf("Mac") !== -1; this._tempEnableSafariTextureUnitWorkaround = isSafari; 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); this.supportsImageBitmap = !isSafari && typeof ImageBitmap !== "undefined"; this._samplerTypes = /* @__PURE__ */ new Set([ gl.SAMPLER_2D, gl.SAMPLER_CUBE, 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, gl.MIN, gl.MAX ]; 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.glFrontFace = [ gl.CCW, gl.CW ]; 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; 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; 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.constantTexSource = this.scope.resolve("source"); this.postInit(); } postInit() { super.postInit(); this.gpuProfiler = new WebglGpuProfiler(this); } /** * Destroy the graphics device. */ destroy() { super.destroy(); const gl = this.gl; if (this.feedback) { gl.deleteTransformFeedback(this.feedback); } this.clearVertexArrayObjectCache(); this._xrMsaaCopy?.destroy(); this._xrMsaaCopy = null; 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 }); 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 (this._defaultFramebufferChanged) { this.updateBackbufferFormat(this._defaultFramebuffer); } this._defaultFramebufferChanged = false; this.backBufferSize.set(this.canvas.width, this.canvas.height); 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); } createDrawCommandImpl(drawCommands) { return new WebglDrawCommands(drawCommands.indexSizeBytes); } createTextureImpl(texture) { this.textures.add(texture); return new WebglTexture(texture); } createXrBridgeImpl(xrBridge) { return new WebglXrBridge(xrBridge); } createRenderTargetImpl(renderTarget) { return new WebglRenderTarget(); } createUploadStreamImpl(uploadStream) { return new WebglUploadStream(uploadStream); } 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 {"highp"|"mediump"|"lowp"} The highest precision supported by the WebGL context. * @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() { if (!this._extDisjointTimerQuery) { 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() { const gl = this.gl; this.supportedExtensions = gl.getSupportedExtensions() ?? []; this._extDisjointTimerQuery = null; this.textureRG11B10Renderable = true; this.extColorBufferFloat = this.getExtension("EXT_color_buffer_float"); this.textureFloatRenderable = !!this.extColorBufferFloat; this.extColorBufferHalfFloat = this.getExtension("EXT_color_buffer_half_float"); this.textureHalfFloatRenderable = !!this.extColorBufferHalfFloat || !!this.extColorBufferFloat; 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.extParallelShaderCompile = this.getExtension("KHR_parallel_shader_compile"); this.extMultiDraw = this.getExtension("WEBGL_multi_draw"); this.supportsMultiDraw = !!this.extMultiDraw; 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.extCompressedTextureS3TC_SRGB = this.getExtension("WEBGL_compressed_texture_s3tc_srgb"); this.extCompressedTextureATC = this.getExtension("WEBGL_compressed_texture_atc"); this.extCompressedTextureASTC = this.getExtension("WEBGL_compressed_texture_astc"); this.extTextureCompressionBPTC = this.getExtension("EXT_texture_compression_bptc"); this.supportsHtmlTextures = typeof gl.texElementImage2D === "function"; } /** * Query the capabilities of the WebGL context. * * @ignore */ initializeCapabilities() { 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?.antialias ?? false; this.supportsStencil = contextAttribs?.stencil ?? false; 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); this.maxColorAttachments = gl.getParameter(gl.MAX_COLOR_ATTACHMENTS); this.maxVolumeSize = gl.getParameter(gl.MAX_3D_TEXTURE_SIZE); ext = this.extDebugRendererInfo; this.unmaskedRenderer = ext ? gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) : ""; this.unmaskedVendor = ext ? gl.getParameter(ext.UNMASKED_VENDOR_WEBGL) : ""; const maliRendererRegex = /\bMali-G52+/; 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.forceDisableMultisampling; this.maxSamples = antialiasSupported ? gl.getParameter(gl.MAX_SAMPLES) : 1; this.maxSamples = Math.min(this.maxSamples, 4); this.samples = antialiasSupported && this.backBufferAntialias ? this.maxSamples : 1; this.supportsAreaLights = !platform.android; if (this.maxTextures <= 8) { this.supportsAreaLights = false; } this.initCapsDefines(); } /** * Set the initial render state on the WebGL context. * * @ignore */ initializeRenderState() { super.initializeRenderState(); const gl = this.gl; 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); 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 = 255; gl.stencilFunc(gl.ALWAYS, 0, 255); this.stencilFailFront = this.stencilFailBack = STENCILOP_KEEP; this.stencilZfailFront = this.stencilZfailBack = STENCILOP_KEEP; this.stencilZpassFront = this.stencilZpassBack = STENCILOP_KEEP; this.stencilWriteMaskFront = 255; this.stencilWriteMaskBack = 255; gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); gl.stencilMask(255); this.alphaToCoverage = false; this.raster = true; 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); gl.hint(gl.FRAGMENT_SHADER_DERIVATIVE_HINT, 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); this.unpackAlignment = 1; 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(); this._vaoMap = /* @__PURE__ */ 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(); for (const shader of this.shaders) { shader.loseContext(); } this.fire("devicelost"); } /** * Called when the WebGL context is restored. It reinitializes all context related resources. * * @ignore */ restoreContext() { this.initializeExtensions(); this.initializeCapabilities(); super.restoreContext(); for (const shader of this.shaders) { shader.restoreContext(); } this.fire("devicerestored"); } /** * 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.sw = w; this.sh = h; } } /** * Binds the specified framebuffer object. * * @param {WebGLFramebuffer | null} fb - The framebuffer to bind. * @ignore */ setFramebuffer(fb) { if (this.activeFramebuffer !== fb) { const gl = this.gl; gl.bindFramebuffer(gl.FRAMEBUFFER, fb); this.activeFramebuffer = fb; } } /** * Resolve multisampled color into the WebXR session framebuffer by first blitting MSAA into * an internal scratch texture, then copying that texture into the XR FBO with a single * fullscreen textured quad. Used on visionOS / Apple Vision Pro where direct * `blitFramebuffer` into the XR opaque framebuffer does not produce correct results. * * @param {WebGLFramebuffer} msaaReadFbo - Multisampled source framebuffer. * @param {WebGLFramebuffer} xrDrawFbo - XR base layer framebuffer. * @param {number} width - Full SBS framebuffer width in pixels. * @param {number} height - Framebuffer height in pixels. * @ignore */ resolveMsaaColorToXrFramebufferViaQuads(msaaReadFbo, xrDrawFbo, width, height) { this._xrMsaaCopy ?? (this._xrMsaaCopy = new WebglXrMsaaCopy(this)); this._xrMsaaCopy.copy(msaaReadFbo, xrDrawFbo, width, height); } /** * Copies source render target into destination render target. Mostly used by post-effects. * * @param {RenderTarget} [source] - The source render target. Defaults to frame buffer. * @param {RenderTarget} [dest] - The destination render target. Defaults to frame buffer. * @param {boolean} [color] - If true, will copy the color buffer. Defaults to false. * @param {boolean} [depth] - If true, will copy the depth buffer. Defaults to false. * @returns {boolean} True if the copy was successful, false otherwise. */ copyRenderTarget(source, dest, color, depth) { const gl = this.gl; if (source === this.backBuffer) { source = null; } if (color) { if (!dest) { if (!source._colorBuffer) { Debug.error("Can't copy empty color buffer to backbuffer"); return false; } } else if (source) { if (!source._colorBuffer || !dest._colorBuffer) { Debug.error("Can't copy color buffer, because one of the render targets doesn't have it"); return false; } if (source._colorBuffer._format !== dest._colorBuffer._format) { Debug.error("Can't copy render targets of different color formats"); return false; } } } if (depth && source) { if (!source._depth) { if (!source._depthBuffer || !dest._depthBuffer) { Debug.error("Can't copy depth buffer, because one of the render targets doesn't have it"); return false; } if (source._depthBuffer._format !== dest._depthBuffer._format) { Debug.error("Can't copy render targets of different depth formats"); return false; } } } DebugGraphics.pushGpuMarker(this, "COPY-RT"); const prevRt = this.renderTarget; this.renderTarget = dest; this.updateBegin(); const src = source ? source.impl._glFrameBuffer : this.backBuffer?.impl._glFrameBuffer; const dst = dest ? dest.impl._glFrameBuffer : this.backBuffer?.impl._glFrameBuffer; Debug.assert(src !== dst, "Source and destination framebuffers must be different when blitting."); gl.bindFramebuffer(gl.READ_FRAMEBUFFER, src); gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, dst); const w = source ? source.width : dest ? dest.width : this.width; const h = source ? source.height : dest ? dest.height : this.height; gl.blitFramebuffer( 0, 0, w, h, 0, 0, w, h, (color ? gl.COLOR_BUFFER_BIT : 0) | (depth ? gl.DEPTH_BUFFER_BIT : 0), gl.NEAREST ); this.renderTarget = prevRt; gl.bindFramebuffer(gl.FRAMEBUFFER, prevRt ? prevRt.impl._glFrameBuffer : null); DebugGraphics.popGpuMarker(this); return true; } frameStart() { super.frameStart(); this.updateBackbuffer(); this.gpuProfiler.frameStart(); } frameEnd() { super.frameEnd(); this.gpuProfiler.frameEnd(); this.gpuProfiler.request(); } /** * Start a render pass. * * @param {RenderPass} renderPass - The render pass to start. * @ignore */ startRenderPass(renderPass) { const rt = renderPass.renderTarget ?? this.backBuffer; this.renderTarget = rt; Debug.assert(rt); DebugGraphics.pushGpuMarker(this, `Pass:${renderPass.name} RT:${rt.name}`); DebugGraphics.pushGpuMarker(this, "START-PASS"); this.updateBegin(); const { width, height } = rt; this.setViewport(0, 0, width, height); this.setScissor(0, 0, width, height); const colorOps = renderPass.colorOps; const depthStencilOps = renderPass.depthStencilOps; if (colorOps?.clear || depthStencilOps.clearDepth || depthStencilOps.clearStencil) { let clearFlags = 0; const clearOptions = {}; if (colorOps?.clear) { clearFlags |= CLEARFLAG_COLOR; clearOptions.color = [colorOps.clearValue.r, colorOps.clearValue.g, colorOps.clearValue.b, colorOps.clearValue.a]; } if (depthStencilOps.clearDepth) { clearFlags |= CLEARFLAG_DEPTH; clearOptions.depth = depthStencilOps.clearDepthValue; } if (depthStencilOps.clearStencil) { clearFlags |= CLEARFLAG_STENCIL; clearOptions.stencil = depthStencilOps.clearStencilValue; } clearOptions.flags = clearFlags; this.clear(clearOptions); } Debug.call(() => { if (this.insideRenderPass) { Debug.errorOnce("RenderPass cannot be started while inside another render pass."); } }); this.insideRenderPass = true; DebugGraphics.popGpuMarker(this); } /** * End a render pass. * * @param {RenderPass} renderPass - The render pass to end. * @ignore */ endRenderPass(renderPass) { DebugGraphics.pushGpuMarker(this, "END-PASS"); this.unbindVertexArray(); const target = this.renderTarget; const colorBufferCount = renderPass.colorArrayOps.length; if (target) { invalidateAttachments.length = 0; const gl = this.gl; for (let i = 0; i < colorBufferCount; i++) { const colorOps = renderPass.colorArrayOps[i]; if (!(colorOps.store || colorOps.resolve)) { invalidateAttachments.push(gl.COLOR_ATTACHMENT0 + i); } } if (target !== this.backBuffer) { if (!renderPass.depthStencilOps.storeDepth) { invalidateAttachments.push(gl.DEPTH_ATTACHMENT); } if (!renderPass.depthStencilOps.storeStencil) { invalidateAttachments.push(gl.STENCIL_ATTACHMENT); } } if (invalidateAttachments.length > 0) { if (renderPass.fullSizeClearRect) { gl.invalidateFramebuffer(gl.DRAW_FRAMEBUFFER, invalidateAttachments); } } if (colorBufferCount && renderPass.colorOps?.resolve) { if (renderPass.samples > 1 && target.autoResolve) { target.resolve(true, false); } } if (target.depthBuffer && renderPass.depthStencilOps.resolveDepth) { if (renderPass.samples > 1 && target.autoResolve) { target.resolve(false, true); } } for (let i = 0; i < colorBufferCount; i++) { const colorOps = renderPass.colorArrayOps[i]; if (colorOps.genMipmaps) { const colorBuffer = target._colorBuffers[i]; if (colorBuffer && colorBuffer.impl._glTexture && colorBuffer.mipmaps) { DebugGraphics.pushGpuMarker(this, `MIPS${i}`); this.activeTexture(this.maxCombinedTextures - 1); this.bindTexture(colorBuffer); this.gl.generateMipmap(colorBuffer.impl._glTarget); DebugGraphics.popGpuMarker(this); } } } } this.insideRenderPass = false; DebugGraphics.popGpuMarker(this); DebugGraphics.popGpuMarker(this); } set defaultFramebuffer(value) { if (this._defaultFramebuffer !== value) { this._defaultFramebuffer = value; this._defaultFramebufferChanged = true; } } get defaultFramebuffer() { return this._defaultFramebuffer; } /** * Marks the beginning of a block of rendering. Internally, this function binds the render * target currently set on the device. This function should be matched with a call to * {@link GraphicsDevice#updateEnd}. Calls to {@link GraphicsDevice#updateBegin} and * {@link GraphicsDevice#updateEnd} must not be nested. * * @ignore */ updateBegin() { DebugGraphics.pushGpuMarker(this, "UPDATE-BEGIN"); this.boundVao = null; if (this._tempEnableSafariTextureUnitWorkaround) { for (let unit = 0; unit < this.textureUnits.length; ++unit) { for (let slot = 0; slot < 3; ++slot) { this.textureUnits[unit][slot] = null; } } } const target = this.renderTarget ?? this.backBuffer; Debug.assert(target); const targetImpl = target.impl; if (!targetImpl.initialized) { this.initRenderTarget(target); } this.setFramebuffer(targetImpl._glFrameBuffer); DebugGraphics.popGpuMarker(this); } /** * Marks the end of a block of rendering. This function should be called after a matching call * to {@link GraphicsDevice#updateBegin}. Calls to {@link GraphicsDevice#updateBegin} and * {@link GraphicsDevice#updateEnd} must not be nested. * * @ignore */ updateEnd() { DebugGraphics.pushGpuMarker(this, "UPDATE-END"); this.unbindVertexArray(); const target = this.renderTarget; if (target && target !== this.backBuffer) { if (target._samples > 1 && target.autoResolve) { target.resolve(); } const colorBuffer = target._colorBuffer; if (colorBuffer && colorBuffer.impl._glTexture && colorBuffer.mipmaps) { this.activeTexture(this.maxCombinedTextures - 1); this.bindTexture(colorBuffer); this.gl.generateMipmap(colorBuffer.impl._glTarget); } } DebugGraphics.popGpuMarker(this); } /** * Updates a texture's vertical flip. * * @param {boolean} flipY - True to flip the texture vertically. * @ignore */ setUnpackFlipY(flipY) { if (this.unpackFlipY !== flipY) { this.unpackFlipY = flipY; const gl = this.gl; gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY); } } /** * Updates a texture to have its RGB channels premultiplied by its alpha channel or not. * * @param {boolean} premultiplyAlpha - True to premultiply the alpha channel against the RGB * channels. * @ignore */ setUnpackPremultiplyAlpha(premultiplyAlpha) { if (this.unpackPremultiplyAlpha !== premultiplyAlpha) { this.unpackPremultiplyAlpha = premultiplyAlpha; const gl = this.gl; gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, premultiplyAlpha); } } /** * Sets the byte alignment for unpacking pixel data during texture uploads. * * @param {number} alignment - The alignment in bytes. Must be 1, 2, 4, or 8. * @ignore */ setUnpackAlignment(alignment) { if (this.unpackAlignment !== alignment) { this.unpackAlignment = alignment; this.gl.pixelStorei(this.gl.UNPACK_ALIGNMENT, alignment); } } /** * Activate the specified texture unit. * * @param {number} textureUnit - The texture unit to activate. * @ignore */ activeTexture(textureUnit) { if (this.textureUnit !== textureUnit) { this.gl.activeTexture(this.gl.TEXTURE0 + textureUnit); this.textureUnit = textureUnit; } } /** * If the texture is not already bound on the currently active texture unit, bind it. * * @param {Texture} texture - The texture to bind. * @ignore */ bindTexture(texture) { const impl = texture.impl; const textureTarget = impl._glTarget; const textureObject = impl._glTexture; const textureUnit = this.textureUnit; const slot = this.targetToSlot[textureTarget]; if (this.textureUnits[textureUnit][slot] !== textureObject) { this.gl.bindTexture(textureTarget, textureObject); this.textureUnits[textureUnit][slot] = textureObject; } } /** * If the texture is not bound on the specified texture unit, active the texture unit and bind * the texture to it. * * @param {Texture} texture - The texture to bind. * @param {number} textureUnit - The texture unit to activate and bind the texture to. * @ignore */ bindTextureOnUnit(texture, textureUnit) { const impl = texture.impl; const textureTarget = impl._glTarget; const textureObject = impl._glTexture; const slot = this.targetToSlot[textureTarget]; if (this.textureUnits[textureUnit][slot] !== textureObject) { this.activeTexture(textureUnit); this.gl.bindTexture(textureTarget, textureObject); this.textureUnits[textureUnit][slot] = textureObject; } } /** * Update the texture parameters for a given texture if they have changed. * * @param {Texture} texture - The texture to update. * @ignore */ setTextureParameters(texture) { const gl = this.gl; const flags = texture.impl.dirtyParameterFlags; const target = texture.impl._glTarget; if (flags & TEXPROPERTY_MIN_FILTER) { let