playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
1,446 lines • 60.1 kB
JavaScript
import { math } from "../../../core/math/math.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 { 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 {
gl;
_defaultFramebuffer = null;
_defaultFramebufferChanged = false;
_xrMsaaCopy = null;
constructor(canvas, options = {}) {
super(canvas, options);
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;
}
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;
}
}
}
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() {
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);
}
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";
} else {
precision = "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;
}
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";
}
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();
}
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);
}
loseContext() {
super.loseContext();
for (const shader of this.shaders) {
shader.loseContext();
}
this.fire("devicelost");
}
restoreContext() {
this.initializeExtensions();
this.initializeCapabilities();
super.restoreContext();
for (const shader of this.shaders) {
shader.restoreContext();
}
this.fire("devicerestored");
}
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;
}
}
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;
}
}
setFramebuffer(fb) {
if (this.activeFramebuffer !== fb) {
const gl = this.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
this.activeFramebuffer = fb;
}
}
resolveMsaaColorToXrFramebufferViaQuads(msaaReadFbo, xrDrawFbo, width, height) {
this._xrMsaaCopy ?? (this._xrMsaaCopy = new WebglXrMsaaCopy(this));
this._xrMsaaCopy.copy(msaaReadFbo, xrDrawFbo, width, height);
}
copyRenderTarget(source, dest, color, depth) {
const gl = this.gl;
if (source === this.backBuffer) {
source = null;
}
if (color) {
if (!dest) {
if (!source._colorBuffer) {
return false;
}
} else if (source) {
if (!source._colorBuffer || !dest._colorBuffer) {
return false;
}
if (source._colorBuffer._format !== dest._colorBuffer._format) {
return false;
}
}
}
if (depth && source) {
if (!source._depth) {
if (!source._depthBuffer || !dest._depthBuffer) {
return false;
}
if (source._depthBuffer._format !== dest._depthBuffer._format) {
return false;
}
}
}
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;
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);
return true;
}
frameStart() {
super.frameStart();
this.updateBackbuffer();
this.gpuProfiler.frameStart();
}
frameEnd() {
super.frameEnd();
this.gpuProfiler.frameEnd();
this.gpuProfiler.request();
}
startRenderPass(renderPass) {
const rt = renderPass.renderTarget ?? this.backBuffer;
this.renderTarget = rt;
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);
}
this.insideRenderPass = true;
}
endRenderPass(renderPass) {
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) {
this.activeTexture(this.maxCombinedTextures - 1);
this.bindTexture(colorBuffer);
this.gl.generateMipmap(colorBuffer.impl._glTarget);
}
}
}
}
this.insideRenderPass = false;
}
set defaultFramebuffer(value) {
if (this._defaultFramebuffer !== value) {
this._defaultFramebuffer = value;
this._defaultFramebufferChanged = true;
}
}
get defaultFramebuffer() {
return this._defaultFramebuffer;
}
updateBegin() {
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;
const targetImpl = target.impl;
if (!targetImpl.initialized) {
this.initRenderTarget(target);
}
this.setFramebuffer(targetImpl._glFrameBuffer);
}
updateEnd() {
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);
}
}
}
setUnpackFlipY(flipY) {
if (this.unpackFlipY !== flipY) {
this.unpackFlipY = flipY;
const gl = this.gl;
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
}
}
setUnpackPremultiplyAlpha(premultiplyAlpha) {
if (this.unpackPremultiplyAlpha !== premultiplyAlpha) {
this.unpackPremultiplyAlpha = premultiplyAlpha;
const gl = this.gl;
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, premultiplyAlpha);
}
}
setUnpackAlignment(alignment) {
if (this.unpackAlignment !== alignment) {
this.unpackAlignment = alignment;
this.gl.pixelStorei(this.gl.UNPACK_ALIGNMENT, alignment);
}
}
activeTexture(textureUnit) {
if (this.textureUnit !== textureUnit) {
this.gl.activeTexture(this.gl.TEXTURE0 + textureUnit);
this.textureUnit = textureUnit;
}
}
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;
}
}
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;
}
}
setTextureParameters(texture) {
const gl = this.gl;
const flags = texture.impl.dirtyParameterFlags;
const target = texture.impl._glTarget;
if (flags & TEXPROPERTY_MIN_FILTER) {
let filter = texture._minFilter;
if (!texture._mipmaps || texture._compressed && texture._levels.length === 1) {
if (filter === FILTER_NEAREST_MIPMAP_NEAREST || filter === FILTER_NEAREST_MIPMAP_LINEAR) {
filter = FILTER_NEAREST;
} else if (filter === FILTER_LINEAR_MIPMAP_NEAREST || filter === FILTER_LINEAR_MIPMAP_LINEAR) {
filter = FILTER_LINEAR;
}
}
gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, this.glFilter[filter]);
}
if (flags & TEXPROPERTY_MAG_FILTER) {
gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, this.glFilter[texture._magFilter]);
}
if (flags & TEXPROPERTY_ADDRESS_U) {
gl.texParameteri(target, gl.TEXTURE_WRAP_S, this.glAddress[texture._addressU]);
}
if (flags & TEXPROPERTY_ADDRESS_V) {
gl.texParameteri(target, gl.TEXTURE_WRAP_T, this.glAddress[texture._addressV]);
}
if (flags & TEXPROPERTY_ADDRESS_W) {
gl.texParameteri(target, gl.TEXTURE_WRAP_R, this.glAddress[texture._addressW]);
}
if (flags & TEXPROPERTY_COMPARE_ON_READ) {
gl.texParameteri(target, gl.TEXTURE_COMPARE_MODE, texture._compareOnRead ? gl.COMPARE_REF_TO_TEXTURE : gl.NONE);
}
if (flags & TEXPROPERTY_COMPARE_FUNC) {
gl.texParameteri(target, gl.TEXTURE_COMPARE_FUNC, this.glComparison[texture._compareFunc]);
}
if (flags & TEXPROPERTY_ANISOTROPY) {
const ext = this.extTextureFilterAnisotropic;
if (ext) {
gl.texParameterf(target, ext.TEXTURE_MAX_ANISOTROPY_EXT, math.clamp(Math.round(texture._anisotropy), 1, this.maxAnisotropy));
}
}
}
setTexture(texture, textureUnit) {
const impl = texture.impl;
if (!impl._glTexture) {
impl.initialize(this, texture);
}
if (impl.dirtyParameterFlags > 0 || texture._needsUpload || texture._needsMipmapsUpload) {
this.activeTexture(textureUnit);
this.bindTexture(texture);
if (impl.dirtyParameterFlags) {
this.setTextureParameters(texture);
impl.dirtyParameterFlags = 0;
}
if (texture._needsUpload || texture._needsMipmapsUpload) {
impl.upload(this, texture);
texture._needsUpload = false;
texture._needsMipmapsUpload = false;
}
} else {
this.bindTextureOnUnit(texture, textureUnit);
}
}
// function creates VertexArrayObject from list of vertex buffers
createVertexArray(vertexBuffers) {
let key, vao;
const useCache = vertexBuffers.length > 1;
if (useCache) {
key = "";
for (let i = 0; i < vertexBuffers.length; i++) {
const vertexBuffer = vertexBuffers[i];
key += vertexBuffer.id + vertexBuffer.format.renderingHash;
}
vao = this._vaoMap.get(key);
}
if (!vao) {
const gl = this.gl;
vao = gl.createVertexArray();
gl.bindVertexArray(vao);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
let locZero = false;
for (let i = 0; i < vertexBuffers.length; i++) {
const vertexBuffer = vertexBuffers[i];
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer.impl.bufferId);
const elements = vertexBuffer.format.elements;
for (let j = 0; j < elements.length; j++) {
const e = elements[j];
const loc = semanticToLocation[e.name];
if (loc === 0) {
locZero = true;
}
if (e.asInt) {
gl.vertexAttribIPointer(loc, e.numComponents, this.glType[e.dataType], e.stride, e.offset);
} else {
gl.vertexAttribPointer(loc, e.numComponents, this.glType[e.dataType], e.normalize, e.stride, e.offset);
}
gl.enableVertexAttribArray(loc);
if (vertexBuffer.format.instancing) {
gl.vertexAttribDivisor(loc, 1);
}
}
}
gl.bindVertexArray(null);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
if (useCache) {
this._vaoMap.set(key, vao);
}
if (!locZero) {
}
}
return vao;
}
unbindVertexArray() {
if (this.boundVao) {
this.boundVao = null;
this.gl.bindVertexArray(null);
}
}
setBuffers(indexBuffer) {
const gl = this.gl;
let vao;
if (this.vertexBuffers.length === 1) {
const vertexBuffer = this.vertexBuffers[0];
if (!vertexBuffer.impl.vao) {
vertexBuffer.impl.vao = this.createVertexArray(this.vertexBuffers);
}
vao = vertexBuffer.impl.vao;
} else {
vao = this.createVertexArray(this.vertexBuffers);
}
if (this.boundVao !== vao) {
this.boundVao = vao;
gl.bindVertexArray(vao);
}
const bufferId = indexBuffer ? indexBuffer.impl.bufferId : null;
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufferId);
}
_multiDrawLoopFallback(mode, primitive, indexBuffer, numInstances, drawCommands) {
const gl = this.gl;
if (primitive.indexed) {
const format = indexBuffer.impl.glFormat;
const { glCounts, glOffsetsBytes, glInstanceCounts, count } = drawCommands.impl;
if (numInstances > 0) {
for (let i = 0; i < count; i++) {
gl.drawElementsInstanced(mode, glCounts[i], format, glOffsetsBytes[i], glInstanceCounts[i]);
}
} else {
for (let i = 0; i < count; i++) {
gl.drawElements(mode, glCounts[i], format, glOffsetsBytes[i]);
}
}
} else {
const { glCounts, glOffsetsBytes, glInstanceCounts, count } = drawCommands.impl;
if (numInstances > 0) {
for (let i = 0; i < count; i++) {
gl.drawArraysInstanced(mode, glOffsetsBytes[i], glCounts[i], glInstanceCounts[i]);
}
} else {
for (let i = 0; i < count; i++) {
gl.drawArrays(mode, glOffsetsBytes[i], glCounts[i]);
}
}
}
}
draw(primitive, indexBuffer, numInstances, drawCommands, first = true, last = true) {
const shader = this.shader;
if (shader) {
this.activateShader();
if (this.shaderValid) {
const gl = this.gl;
if (first) {
this.setBuffers(indexBuffer);
}
let textureUnit = 0;
const samplers = shader.impl.samplers;
for (let i = 0, len = samplers.length; i < len; i++) {
const sampler = samplers[i];
let samplerValue = sampler.scopeId.value;
if (!samplerValue) {
const samplerName = sampler.scopeId.name;
if (samplerName === "uSceneDepthMap") {
samplerValue = getBuiltInTexture(this, "white");
}
if (samplerName === "uSceneColorMap") {
samplerValue = getBuiltInTexture(this, "pink");
}
if (!samplerValue) {
samplerValue = getBuiltInTexture(this, "pink");
}
}
if (samplerValue instanceof Texture) {
const texture = samplerValue;
this.setTexture(texture, textureUnit);
if (sampler.slot !== textureUnit) {
gl.uniform1i(sampler.locationId, textureUnit);
sampler.slot = textureUnit;
}
textureUnit++;
} else {
sampler.array.length = 0;
const numTextures = samplerValue.length;
for (let j = 0; j < numTextures; j++) {
const texture = samplerValue[j];
this.setTexture(texture, textureUnit);
sampler.array[j] = textureUnit;
textureUnit++;
}
gl.uniform1iv(sampler.locationId, sampler.array);
}
}
const uniforms = shader.impl.uniforms;
for (let i = 0, len = uniforms.length; i < len; i++) {
const uniform = uniforms[i];
const scopeId = uniform.scopeId;
const uniformVersion = uniform.version;
const programVersion = scopeId.versionObject.version;
if (uniformVersion.globalId !== programVersion.globalId || uniformVersion.revision !== programVersion.revision) {
uniformVersion.globalId = programVersion.globalId;
uniformVersion.revision = programVersion.revision;
const value = scopeId.value;
if (value !== null && value !== void 0) {
this.commitFunction[uniform.dataType](uniform, value);
} else {
}
}
}
if (this.transformFeedbackBuffer) {
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, this.transformFeedbackBuffer.impl.bufferId);
gl.beginTransformFeedback(gl.POINTS);
}
const mode = this.glPrimitive[primitive.type];
const count = primitive.count;
if (drawCommands) {
if (this.extMultiDraw) {
const impl = drawCommands.impl;
if (primitive.indexed) {
const format = indexBuffer.impl.glFormat;
if (numInstances > 0) {
this.extMultiDraw.multiDrawElementsInstancedWEBGL(mode, impl.glCounts, 0, format, impl.glOffsetsBytes, 0, impl.glInstanceCounts, 0, drawCommands.count);
} else {
this.extMultiDraw.multiDrawElementsWEBGL(mode, impl.glCounts, 0, format, impl.glOffsetsBytes, 0, drawCommands.count);
}
} else {
if (numInstances > 0) {
this.extMultiDraw.multiDrawArraysInstancedWEBGL(mode, impl.glOffsetsBytes, 0, impl.glCounts, 0, impl.glInstanceCounts, 0, drawCommands.count);
} else {
this.extMultiDraw.multiDrawArraysWEBGL(mode, impl.glOffsetsBytes, 0, impl.glCounts, 0, drawCommands.count);
}
}
} else {
this._multiDrawLoopFallback(mode, primitive, indexBuffer, numInstances, drawCommands);
}
} else {
if (primitive.indexed) {
const format = indexBuffer.impl.glFormat;
const offset = primitive.base * indexBuffer.bytesPerIndex;
if (numInstances > 0) {
gl.drawElementsInstanced(mode, count, format, offset, numInstances);
} else {
gl.drawElements(mode, count, format, offset);
}
} else {
const first2 = primitive.base;
if (numInstances > 0) {
gl.drawArraysInstanced(mode, first2, count, numInstances);
} else {
gl.drawArrays(mode, first2, count);
}
}
}
if (this.transformFeedbackBuffer) {
gl.endTransformFeedback();
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null);
}
this._drawCallsPerFrame++;
}
}
if (last) {
this.clearVertexBuffer();
}
}
clear(options) {
const defaultOptions = this.defaultClearOptions;
options = options || defaultOptions;
const flags = options.flags ?? defaultOptions.flags;
if (flags !== 0) {
const gl = this.gl;
if (flags & CLEARFLAG_COLOR) {
const color = options.color ?? defaultOptions.color;
const r = color[0];
const g = color[1];
const b = color[2];
const a = color[3];
const c = this.clearColor;
if (r !== c.r || g !== c.g || b !== c.b || a !== c.a) {
this.gl.clearColor(r, g, b, a);
this.clearColor.set(r, g, b, a);
}
this.setBlendState(BlendState.NOBLEND);
}
if (flags & CLEARFLAG_DEPTH) {
const depth = options.depth ?? defaultOptions.depth;
if (depth !== this.clearDepth) {
this.gl.clearDepth(depth);
this.clearDepth = depth;
}
this.setDepthState(DepthState.WRITEDEPTH);
}
if (flags & CLEARFLAG_STENCIL) {
const stencil = options.stencil ?? defaultOptions.stencil;
if (stencil !== this.clearStencil) {
this.gl.clearStencil(stencil);
this.clearStencil = stencil;
}
gl.stencilMask(255);
this.stencilWriteMaskFront = 255;
this.stencilWriteMaskBack = 255;
}
gl.clear(this.glClearFlag[flags]);
}
}
submit() {
this.gl.flush();
}
readPixels(x, y, w, h, pixels) {
const gl = this.gl;
gl.readPixels(x, y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
}
clientWaitAsync(flags, interval_ms) {
const gl = this.gl;
const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
this.submit();
return new Promise((resolve, reject) => {
function test() {
const res = gl.clientWaitSync(sync, flags, 0);
if (res === gl.TIMEOUT_EXPIRED) {
setTimeout(test, interval_ms);
} else {
gl.deleteSync(sync);
if (res === gl.WAIT_FAILED) {
reject(new Error("webgl clientWaitSync sync failed"));
} else {
resolve();
}
}
}
test();
});
}
async readPixelsAsync(x, y, w, h, pixels, forceRgba = false) {
const gl = this.gl;
let format, pixelType;
if (forceRgba) {
format = gl.RGBA;
pixelType = gl.UNSIGNED_BYTE;
} else {
const impl = this.renderTarget.colorBuffer?.impl;
format = impl?._glFormat ?? gl.RGBA;
pixelType = impl?._glPixelType ?? gl.UNSIGNED_BYTE;
}
const buf = gl.createBuffer();
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buf);
gl.bufferData(gl.PIXEL_PACK_BUFFER, pixels.byteLength, gl.STREAM_READ);
gl.readPixels(x, y, w, h, format, pixelType, 0);
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
await this.clientWaitAsync(0, 16);
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buf);
gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, pixels);
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
gl.deleteBuffer(buf);
return pixels;
}
readTextureAsync(texture, x, y, width, height, options) {
const face = options.face ?? 0;
const mipLevel = options.mipLevel ?? 0;
const renderTarget = options.renderTarget ?? new RenderTarget({
colorBuffer: texture,
depth: false,
face,
mipLevel
});
const rgbaChannels = getPixelFormatChannelsForRgbaReadback(texture._format);
const needsRgbaReadback = rgbaChannels > 0;
const ArrayType = getPixelFormatArrayType(texture._format);
const outputData = options.data ?? new ArrayType(
TextureUtils.calcLevelGpuSize(width, height, 1, texture._format) / ArrayType.BYTES_PER_ELEMENT
);
const readBuffer = needsRgbaReadback ? new Uint8Array(width * height * 4) : outputData;
this.setRenderTarget(renderTarget);
this.initRenderTarget(renderTarget);
this.setFramebuffer(renderTarget.impl._glFrameBuffer);
if (options.immediate) {
this.gl.flush();
}
return new Promise((resolve, reject) => {
const readPromise = this.readPixelsAsync(x, y, width, height, readBuffer, needsRgbaReadback);
readPromise.then((data) => {
if (this._destroyed) return;
if (!options.renderTarget) {
renderTarget.destroy();
}
if (needsRgbaReadback) {
const pixelCount = width * height;
for (let i = 0; i < pixelCount; i++) {
for (let c = 0; c < rgbaChannels; c++) {
outputData[i * rgbaChannels + c] = data[i * 4 + c];
}
}
resolve(outputData);
} else {
resolve(data);
}
}).