@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
1,037 lines (1,036 loc) • 166 kB
JavaScript
import { createPipelineContext, createRawShaderProgram, createShaderProgram, _finalizePipelineContext, _preparePipelineContext, _setProgram, _executeWhenRenderingStateIsCompiled, getStateObject, _createShaderProgram, deleteStateObject, _isRenderingStateCompiled, } from "./thinEngine.functions.js";
import { IsWrapper } from "../Materials/drawWrapper.functions.js";
import { Logger } from "../Misc/logger.js";
import { IsWindowObjectExist } from "../Misc/domManagement.js";
import { WebGLShaderProcessor } from "./WebGL/webGLShaderProcessors.js";
import { WebGL2ShaderProcessor } from "./WebGL/webGL2ShaderProcessors.js";
import { WebGLDataBuffer } from "../Meshes/WebGL/webGLDataBuffer.js";
import { GetExponentOfTwo } from "../Misc/tools.functions.js";
import { AbstractEngine } from "./abstractEngine.js";
import { WebGLHardwareTexture } from "./WebGL/webGLHardwareTexture.js";
import { InternalTexture } from "../Materials/Textures/internalTexture.js";
import { Effect } from "../Materials/effect.js";
import { _ConcatenateShader, _GetGlobalDefines } from "./abstractEngine.functions.js";
import { resetCachedPipeline } from "../Materials/effect.functions.js";
import { HasStencilAspect, IsDepthTexture } from "../Materials/Textures/textureHelper.functions.js";
/**
* Keeps track of all the buffer info used in engine.
*/
class BufferPointer {
}
/**
* The base engine class (root of all engines)
*/
export class ThinEngine extends AbstractEngine {
/**
* Gets or sets the name of the engine
*/
get name() {
return this._name;
}
set name(value) {
this._name = value;
}
/**
* Returns the version of the engine
*/
get version() {
return this._webGLVersion;
}
/**
* Gets or sets the relative url used to load shaders if using the engine in non-minified mode
*/
static get ShadersRepository() {
return Effect.ShadersRepository;
}
static set ShadersRepository(value) {
Effect.ShadersRepository = value;
}
/**
* Gets a boolean indicating that the engine supports uniform buffers
* @see https://doc.babylonjs.com/setup/support/webGL2#uniform-buffer-objets
*/
get supportsUniformBuffers() {
return this.webGLVersion > 1 && !this.disableUniformBuffers;
}
/**
* Gets a boolean indicating that only power of 2 textures are supported
* Please note that you can still use non power of 2 textures but in this case the engine will forcefully convert them
*/
get needPOTTextures() {
return this._webGLVersion < 2 || this.forcePOTTextures;
}
get _supportsHardwareTextureRescaling() {
return false;
}
/**
* sets the object from which width and height will be taken from when getting render width and height
* Will fallback to the gl object
* @param dimensions the framebuffer width and height that will be used.
*/
set framebufferDimensionsObject(dimensions) {
this._framebufferDimensionsObject = dimensions;
}
/**
* Creates a new snapshot at the next frame using the current snapshotRenderingMode
*/
snapshotRenderingReset() {
this.snapshotRendering = false;
}
/**
* Creates a new engine
* @param canvasOrContext defines the canvas or WebGL context to use for rendering. If you provide a WebGL context, Babylon.js will not hook events on the canvas (like pointers, keyboards, etc...) so no event observables will be available. This is mostly used when Babylon.js is used as a plugin on a system which already used the WebGL context
* @param antialias defines whether anti-aliasing should be enabled (default value is "undefined", meaning that the browser may or may not enable it)
* @param options defines further options to be sent to the getContext() function
* @param adaptToDeviceRatio defines whether to adapt to the device's viewport characteristics (default: false)
*/
constructor(canvasOrContext, antialias, options, adaptToDeviceRatio) {
options = options || {};
super(antialias ?? options.antialias, options, adaptToDeviceRatio);
/** @internal */
this._name = "WebGL";
/**
* Gets or sets a boolean that indicates if textures must be forced to power of 2 size even if not required
*/
this.forcePOTTextures = false;
/** Gets or sets a boolean indicating if the engine should validate programs after compilation */
this.validateShaderPrograms = false;
/**
* Gets or sets a boolean indicating that uniform buffers must be disabled even if they are supported
*/
this.disableUniformBuffers = false;
/** @internal */
this._webGLVersion = 1.0;
this._vertexAttribArraysEnabled = [];
this._uintIndicesCurrentlySet = false;
this._currentBoundBuffer = new Array();
/** @internal */
this._currentFramebuffer = null;
/** @internal */
this._dummyFramebuffer = null;
this._currentBufferPointers = new Array();
this._currentInstanceLocations = new Array();
this._currentInstanceBuffers = new Array();
this._vaoRecordInProgress = false;
this._mustWipeVertexAttributes = false;
this._nextFreeTextureSlots = new Array();
this._maxSimultaneousTextures = 0;
this._maxMSAASamplesOverride = null;
// eslint-disable-next-line @typescript-eslint/naming-convention
this._unpackFlipYCached = null;
/**
* In case you are sharing the context with other applications, it might
* be interested to not cache the unpack flip y state to ensure a consistent
* value would be set.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
this.enableUnpackFlipYCached = true;
/**
* @internal
*/
this._boundUniforms = {};
if (!canvasOrContext) {
return;
}
let canvas = null;
if (canvasOrContext.getContext) {
canvas = canvasOrContext;
if (options.preserveDrawingBuffer === undefined) {
options.preserveDrawingBuffer = false;
}
if (options.xrCompatible === undefined) {
options.xrCompatible = false;
}
// Exceptions
if (navigator && navigator.userAgent) {
this._setupMobileChecks();
const ua = navigator.userAgent;
for (const exception of ThinEngine.ExceptionList) {
const key = exception.key;
const targets = exception.targets;
const check = new RegExp(key);
if (check.test(ua)) {
if (exception.capture && exception.captureConstraint) {
const capture = exception.capture;
const constraint = exception.captureConstraint;
const regex = new RegExp(capture);
const matches = regex.exec(ua);
if (matches && matches.length > 0) {
const capturedValue = parseInt(matches[matches.length - 1]);
if (capturedValue >= constraint) {
continue;
}
}
}
for (const target of targets) {
switch (target) {
case "uniformBuffer":
this.disableUniformBuffers = true;
break;
case "vao":
this.disableVertexArrayObjects = true;
break;
case "antialias":
options.antialias = false;
break;
case "maxMSAASamples":
this._maxMSAASamplesOverride = 1;
break;
}
}
}
}
}
// Context lost
if (!this._doNotHandleContextLost) {
this._onContextLost = (evt) => {
evt.preventDefault();
this._contextWasLost = true;
deleteStateObject(this._gl);
Logger.Warn("WebGL context lost.");
this.onContextLostObservable.notifyObservers(this);
};
this._onContextRestored = () => {
this._restoreEngineAfterContextLost(() => this._initGLContext());
};
canvas.addEventListener("webglcontextrestored", this._onContextRestored, false);
options.powerPreference = options.powerPreference || "high-performance";
}
else {
this._onContextLost = () => {
deleteStateObject(this._gl);
};
}
canvas.addEventListener("webglcontextlost", this._onContextLost, false);
if (this._badDesktopOS) {
options.xrCompatible = false;
}
// GL
if (!options.disableWebGL2Support) {
try {
this._gl = (canvas.getContext("webgl2", options) || canvas.getContext("experimental-webgl2", options));
if (this._gl) {
this._webGLVersion = 2.0;
this._shaderPlatformName = "WEBGL2";
// Prevent weird browsers to lie (yeah that happens!)
if (!this._gl.deleteQuery) {
this._webGLVersion = 1.0;
this._shaderPlatformName = "WEBGL1";
}
}
}
catch (e) {
// Do nothing
}
}
if (!this._gl) {
if (!canvas) {
throw new Error("The provided canvas is null or undefined.");
}
try {
this._gl = (canvas.getContext("webgl", options) || canvas.getContext("experimental-webgl", options));
}
catch (e) {
throw new Error("WebGL not supported");
}
}
if (!this._gl) {
throw new Error("WebGL not supported");
}
}
else {
this._gl = canvasOrContext;
canvas = this._gl.canvas;
if (this._gl.renderbufferStorageMultisample) {
this._webGLVersion = 2.0;
this._shaderPlatformName = "WEBGL2";
}
else {
this._shaderPlatformName = "WEBGL1";
}
const attributes = this._gl.getContextAttributes();
if (attributes) {
options.stencil = attributes.stencil;
}
}
this._sharedInit(canvas);
// Ensures a consistent color space unpacking of textures cross browser.
this._gl.pixelStorei(this._gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, this._gl.NONE);
if (options.useHighPrecisionFloats !== undefined) {
this._highPrecisionShadersAllowed = options.useHighPrecisionFloats;
}
this.resize();
this._initGLContext();
this._initFeatures();
// Prepare buffer pointers
for (let i = 0; i < this._caps.maxVertexAttribs; i++) {
this._currentBufferPointers[i] = new BufferPointer();
}
// Shader processor
this._shaderProcessor = this.webGLVersion > 1 ? new WebGL2ShaderProcessor() : new WebGLShaderProcessor();
// Starting with iOS 14, we can trust the browser
// let matches = navigator.userAgent.match(/Version\/(\d+)/);
// if (matches && matches.length === 2) {
// if (parseInt(matches[1]) >= 14) {
// this._badOS = false;
// }
// }
const versionToLog = `Babylon.js v${ThinEngine.Version}`;
Logger.Log(versionToLog + ` - ${this.description}`);
// Check setAttribute in case of workers
if (this._renderingCanvas && this._renderingCanvas.setAttribute) {
this._renderingCanvas.setAttribute("data-engine", versionToLog);
}
const stateObject = getStateObject(this._gl);
// update state object with the current engine state
stateObject.validateShaderPrograms = this.validateShaderPrograms;
stateObject.parallelShaderCompile = this._caps.parallelShaderCompile;
}
_clearEmptyResources() {
this._dummyFramebuffer = null;
super._clearEmptyResources();
}
/**
* @internal
*/
_getShaderProcessingContext(shaderLanguage) {
return null;
}
/**
* Gets a boolean indicating if all created effects are ready
* @returns true if all effects are ready
*/
areAllEffectsReady() {
for (const key in this._compiledEffects) {
const effect = this._compiledEffects[key];
if (!effect.isReady()) {
return false;
}
}
return true;
}
_initGLContext() {
// Caps
this._caps = {
maxTexturesImageUnits: this._gl.getParameter(this._gl.MAX_TEXTURE_IMAGE_UNITS),
maxCombinedTexturesImageUnits: this._gl.getParameter(this._gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS),
maxVertexTextureImageUnits: this._gl.getParameter(this._gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS),
maxTextureSize: this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE),
maxSamples: this._webGLVersion > 1 ? this._gl.getParameter(this._gl.MAX_SAMPLES) : 1,
maxCubemapTextureSize: this._gl.getParameter(this._gl.MAX_CUBE_MAP_TEXTURE_SIZE),
maxRenderTextureSize: this._gl.getParameter(this._gl.MAX_RENDERBUFFER_SIZE),
maxVertexAttribs: this._gl.getParameter(this._gl.MAX_VERTEX_ATTRIBS),
maxVaryingVectors: this._gl.getParameter(this._gl.MAX_VARYING_VECTORS),
maxFragmentUniformVectors: this._gl.getParameter(this._gl.MAX_FRAGMENT_UNIFORM_VECTORS),
maxVertexUniformVectors: this._gl.getParameter(this._gl.MAX_VERTEX_UNIFORM_VECTORS),
parallelShaderCompile: this._gl.getExtension("KHR_parallel_shader_compile") || undefined,
standardDerivatives: this._webGLVersion > 1 || this._gl.getExtension("OES_standard_derivatives") !== null,
maxAnisotropy: 1,
astc: this._gl.getExtension("WEBGL_compressed_texture_astc") || this._gl.getExtension("WEBKIT_WEBGL_compressed_texture_astc"),
bptc: this._gl.getExtension("EXT_texture_compression_bptc") || this._gl.getExtension("WEBKIT_EXT_texture_compression_bptc"),
s3tc: this._gl.getExtension("WEBGL_compressed_texture_s3tc") || this._gl.getExtension("WEBKIT_WEBGL_compressed_texture_s3tc"),
// eslint-disable-next-line @typescript-eslint/naming-convention
s3tc_srgb: this._gl.getExtension("WEBGL_compressed_texture_s3tc_srgb") || this._gl.getExtension("WEBKIT_WEBGL_compressed_texture_s3tc_srgb"),
pvrtc: this._gl.getExtension("WEBGL_compressed_texture_pvrtc") || this._gl.getExtension("WEBKIT_WEBGL_compressed_texture_pvrtc"),
etc1: this._gl.getExtension("WEBGL_compressed_texture_etc1") || this._gl.getExtension("WEBKIT_WEBGL_compressed_texture_etc1"),
etc2: this._gl.getExtension("WEBGL_compressed_texture_etc") ||
this._gl.getExtension("WEBKIT_WEBGL_compressed_texture_etc") ||
this._gl.getExtension("WEBGL_compressed_texture_es3_0"), // also a requirement of OpenGL ES 3
textureAnisotropicFilterExtension: this._gl.getExtension("EXT_texture_filter_anisotropic") ||
this._gl.getExtension("WEBKIT_EXT_texture_filter_anisotropic") ||
this._gl.getExtension("MOZ_EXT_texture_filter_anisotropic"),
uintIndices: this._webGLVersion > 1 || this._gl.getExtension("OES_element_index_uint") !== null,
fragmentDepthSupported: this._webGLVersion > 1 || this._gl.getExtension("EXT_frag_depth") !== null,
highPrecisionShaderSupported: false,
timerQuery: this._gl.getExtension("EXT_disjoint_timer_query_webgl2") || this._gl.getExtension("EXT_disjoint_timer_query"),
supportOcclusionQuery: this._webGLVersion > 1,
canUseTimestampForTimerQuery: false,
drawBuffersExtension: false,
maxMSAASamples: 1,
colorBufferFloat: !!(this._webGLVersion > 1 && this._gl.getExtension("EXT_color_buffer_float")),
supportFloatTexturesResolve: false,
rg11b10ufColorRenderable: false,
colorBufferHalfFloat: !!(this._webGLVersion > 1 && this._gl.getExtension("EXT_color_buffer_half_float")),
textureFloat: this._webGLVersion > 1 || this._gl.getExtension("OES_texture_float") ? true : false,
textureHalfFloat: this._webGLVersion > 1 || this._gl.getExtension("OES_texture_half_float") ? true : false,
textureHalfFloatRender: false,
textureFloatLinearFiltering: false,
textureFloatRender: false,
textureHalfFloatLinearFiltering: false,
vertexArrayObject: false,
instancedArrays: false,
textureLOD: this._webGLVersion > 1 || this._gl.getExtension("EXT_shader_texture_lod") ? true : false,
texelFetch: this._webGLVersion !== 1,
blendMinMax: false,
multiview: this._gl.getExtension("OVR_multiview2"),
oculusMultiview: this._gl.getExtension("OCULUS_multiview"),
depthTextureExtension: false,
canUseGLInstanceID: this._webGLVersion > 1,
canUseGLVertexID: this._webGLVersion > 1,
supportComputeShaders: false,
supportSRGBBuffers: false,
supportTransformFeedbacks: this._webGLVersion > 1,
textureMaxLevel: this._webGLVersion > 1,
texture2DArrayMaxLayerCount: this._webGLVersion > 1 ? this._gl.getParameter(this._gl.MAX_ARRAY_TEXTURE_LAYERS) : 128,
disableMorphTargetTexture: false,
textureNorm16: this._gl.getExtension("EXT_texture_norm16") ? true : false,
blendParametersPerTarget: false,
dualSourceBlending: false,
};
this._caps.supportFloatTexturesResolve = this._caps.colorBufferFloat;
this._caps.rg11b10ufColorRenderable = this._caps.colorBufferFloat;
// Infos
this._glVersion = this._gl.getParameter(this._gl.VERSION);
const rendererInfo = this._gl.getExtension("WEBGL_debug_renderer_info");
if (rendererInfo != null) {
this._glRenderer = this._gl.getParameter(rendererInfo.UNMASKED_RENDERER_WEBGL);
this._glVendor = this._gl.getParameter(rendererInfo.UNMASKED_VENDOR_WEBGL);
}
if (!this._glVendor) {
this._glVendor = this._gl.getParameter(this._gl.VENDOR) || "Unknown vendor";
}
if (!this._glRenderer) {
this._glRenderer = this._gl.getParameter(this._gl.RENDERER) || "Unknown renderer";
}
// Constants
if (this._gl.HALF_FLOAT_OES !== 0x8d61) {
this._gl.HALF_FLOAT_OES = 0x8d61; // Half floating-point type (16-bit).
}
if (this._gl.RGBA16F !== 0x881a) {
this._gl.RGBA16F = 0x881a; // RGBA 16-bit floating-point color-renderable internal sized format.
}
if (this._gl.RGBA32F !== 0x8814) {
this._gl.RGBA32F = 0x8814; // RGBA 32-bit floating-point color-renderable internal sized format.
}
if (this._gl.DEPTH24_STENCIL8 !== 35056) {
this._gl.DEPTH24_STENCIL8 = 35056;
}
// Extensions
if (this._caps.timerQuery) {
if (this._webGLVersion === 1) {
this._gl.getQuery = this._caps.timerQuery.getQueryEXT.bind(this._caps.timerQuery);
}
// WebGLQuery casted to number to avoid TS error
this._caps.canUseTimestampForTimerQuery = (this._gl.getQuery(this._caps.timerQuery.TIMESTAMP_EXT, this._caps.timerQuery.QUERY_COUNTER_BITS_EXT) ?? 0) > 0;
}
this._caps.maxAnisotropy = this._caps.textureAnisotropicFilterExtension
? this._gl.getParameter(this._caps.textureAnisotropicFilterExtension.MAX_TEXTURE_MAX_ANISOTROPY_EXT)
: 0;
this._caps.textureFloatLinearFiltering = this._caps.textureFloat && this._gl.getExtension("OES_texture_float_linear") ? true : false;
this._caps.textureFloatRender = this._caps.textureFloat && this._canRenderToFloatFramebuffer() ? true : false;
this._caps.textureHalfFloatLinearFiltering =
this._webGLVersion > 1 || (this._caps.textureHalfFloat && this._gl.getExtension("OES_texture_half_float_linear")) ? true : false;
if (this._caps.textureNorm16) {
this._gl.R16_EXT = 0x822a;
this._gl.RG16_EXT = 0x822c;
this._gl.RGB16_EXT = 0x8054;
this._gl.RGBA16_EXT = 0x805b;
this._gl.R16_SNORM_EXT = 0x8f98;
this._gl.RG16_SNORM_EXT = 0x8f99;
this._gl.RGB16_SNORM_EXT = 0x8f9a;
this._gl.RGBA16_SNORM_EXT = 0x8f9b;
}
const oesDrawBuffersIndexed = this._gl.getExtension("OES_draw_buffers_indexed");
this._caps.blendParametersPerTarget = oesDrawBuffersIndexed ? true : false;
if (oesDrawBuffersIndexed) {
this._gl.blendEquationSeparateIndexed = oesDrawBuffersIndexed.blendEquationSeparateiOES.bind(oesDrawBuffersIndexed);
this._gl.blendEquationIndexed = oesDrawBuffersIndexed.blendEquationiOES.bind(oesDrawBuffersIndexed);
this._gl.blendFuncSeparateIndexed = oesDrawBuffersIndexed.blendFuncSeparateiOES.bind(oesDrawBuffersIndexed);
this._gl.blendFuncIndexed = oesDrawBuffersIndexed.blendFunciOES.bind(oesDrawBuffersIndexed);
this._gl.colorMaskIndexed = oesDrawBuffersIndexed.colorMaskiOES.bind(oesDrawBuffersIndexed);
this._gl.disableIndexed = oesDrawBuffersIndexed.disableiOES.bind(oesDrawBuffersIndexed);
this._gl.enableIndexed = oesDrawBuffersIndexed.enableiOES.bind(oesDrawBuffersIndexed);
}
this._caps.dualSourceBlending = this._gl.getExtension("WEBGL_blend_func_extended") ? true : false;
// Compressed formats
if (this._caps.astc) {
this._gl.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR = this._caps.astc.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR;
}
if (this._caps.bptc) {
this._gl.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT = this._caps.bptc.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT;
}
if (this._caps.s3tc_srgb) {
this._gl.COMPRESSED_SRGB_S3TC_DXT1_EXT = this._caps.s3tc_srgb.COMPRESSED_SRGB_S3TC_DXT1_EXT;
this._gl.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT = this._caps.s3tc_srgb.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
this._gl.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT = this._caps.s3tc_srgb.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
}
if (this._caps.etc2) {
this._gl.COMPRESSED_SRGB8_ETC2 = this._caps.etc2.COMPRESSED_SRGB8_ETC2;
this._gl.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC = this._caps.etc2.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC;
}
// Checks if some of the format renders first to allow the use of webgl inspector.
if (this._webGLVersion > 1) {
if (this._gl.HALF_FLOAT_OES !== 0x140b) {
this._gl.HALF_FLOAT_OES = 0x140b;
}
}
this._caps.textureHalfFloatRender = this._caps.textureHalfFloat && this._canRenderToHalfFloatFramebuffer();
// Draw buffers
if (this._webGLVersion > 1) {
this._caps.drawBuffersExtension = true;
this._caps.maxMSAASamples = this._maxMSAASamplesOverride !== null ? this._maxMSAASamplesOverride : this._gl.getParameter(this._gl.MAX_SAMPLES);
this._caps.maxDrawBuffers = this._gl.getParameter(this._gl.MAX_DRAW_BUFFERS);
}
else {
const drawBuffersExtension = this._gl.getExtension("WEBGL_draw_buffers");
if (drawBuffersExtension !== null) {
this._caps.drawBuffersExtension = true;
this._gl.drawBuffers = drawBuffersExtension.drawBuffersWEBGL.bind(drawBuffersExtension);
this._caps.maxDrawBuffers = this._gl.getParameter(drawBuffersExtension.MAX_DRAW_BUFFERS_WEBGL);
this._gl.DRAW_FRAMEBUFFER = this._gl.FRAMEBUFFER;
for (let i = 0; i < 16; i++) {
this._gl["COLOR_ATTACHMENT" + i + "_WEBGL"] = drawBuffersExtension["COLOR_ATTACHMENT" + i + "_WEBGL"];
}
}
}
// Depth Texture
if (this._webGLVersion > 1) {
this._caps.depthTextureExtension = true;
}
else {
const depthTextureExtension = this._gl.getExtension("WEBGL_depth_texture");
if (depthTextureExtension != null) {
this._caps.depthTextureExtension = true;
this._gl.UNSIGNED_INT_24_8 = depthTextureExtension.UNSIGNED_INT_24_8_WEBGL;
}
}
// Vertex array object
if (this.disableVertexArrayObjects) {
this._caps.vertexArrayObject = false;
}
else if (this._webGLVersion > 1) {
this._caps.vertexArrayObject = true;
}
else {
const vertexArrayObjectExtension = this._gl.getExtension("OES_vertex_array_object");
if (vertexArrayObjectExtension != null) {
this._caps.vertexArrayObject = true;
this._gl.createVertexArray = vertexArrayObjectExtension.createVertexArrayOES.bind(vertexArrayObjectExtension);
this._gl.bindVertexArray = vertexArrayObjectExtension.bindVertexArrayOES.bind(vertexArrayObjectExtension);
this._gl.deleteVertexArray = vertexArrayObjectExtension.deleteVertexArrayOES.bind(vertexArrayObjectExtension);
}
}
// Instances count
if (this._webGLVersion > 1) {
this._caps.instancedArrays = true;
}
else {
const instanceExtension = this._gl.getExtension("ANGLE_instanced_arrays");
if (instanceExtension != null) {
this._caps.instancedArrays = true;
this._gl.drawArraysInstanced = instanceExtension.drawArraysInstancedANGLE.bind(instanceExtension);
this._gl.drawElementsInstanced = instanceExtension.drawElementsInstancedANGLE.bind(instanceExtension);
this._gl.vertexAttribDivisor = instanceExtension.vertexAttribDivisorANGLE.bind(instanceExtension);
}
else {
this._caps.instancedArrays = false;
}
}
if (this._gl.getShaderPrecisionFormat) {
const vertexhighp = this._gl.getShaderPrecisionFormat(this._gl.VERTEX_SHADER, this._gl.HIGH_FLOAT);
const fragmenthighp = this._gl.getShaderPrecisionFormat(this._gl.FRAGMENT_SHADER, this._gl.HIGH_FLOAT);
if (vertexhighp && fragmenthighp) {
this._caps.highPrecisionShaderSupported = vertexhighp.precision !== 0 && fragmenthighp.precision !== 0;
}
}
if (this._webGLVersion > 1) {
this._caps.blendMinMax = true;
}
else {
const blendMinMaxExtension = this._gl.getExtension("EXT_blend_minmax");
if (blendMinMaxExtension != null) {
this._caps.blendMinMax = true;
this._gl.MAX = blendMinMaxExtension.MAX_EXT;
this._gl.MIN = blendMinMaxExtension.MIN_EXT;
}
}
// sRGB buffers
// only run this if not already set to true (in the constructor, for example)
if (!this._caps.supportSRGBBuffers) {
if (this._webGLVersion > 1) {
this._caps.supportSRGBBuffers = true;
this._glSRGBExtensionValues = {
SRGB: WebGL2RenderingContext.SRGB,
SRGB8: WebGL2RenderingContext.SRGB8,
SRGB8_ALPHA8: WebGL2RenderingContext.SRGB8_ALPHA8,
};
}
else {
const sRGBExtension = this._gl.getExtension("EXT_sRGB");
if (sRGBExtension != null) {
this._caps.supportSRGBBuffers = true;
this._glSRGBExtensionValues = {
SRGB: sRGBExtension.SRGB_EXT,
SRGB8: sRGBExtension.SRGB_ALPHA_EXT,
SRGB8_ALPHA8: sRGBExtension.SRGB_ALPHA_EXT,
};
}
}
// take into account the forced state that was provided in options
if (this._creationOptions) {
const forceSRGBBufferSupportState = this._creationOptions.forceSRGBBufferSupportState;
if (forceSRGBBufferSupportState !== undefined) {
this._caps.supportSRGBBuffers = this._caps.supportSRGBBuffers && forceSRGBBufferSupportState;
}
}
}
// Depth buffer
this._depthCullingState.depthTest = true;
this._depthCullingState.depthFunc = this._gl.LEQUAL;
this._depthCullingState.depthMask = true;
// Texture maps
this._maxSimultaneousTextures = this._caps.maxCombinedTexturesImageUnits;
for (let slot = 0; slot < this._maxSimultaneousTextures; slot++) {
this._nextFreeTextureSlots.push(slot);
}
if (this._glRenderer === "Mali-G72") {
// Overcome a bug when using a texture to store morph targets on Mali-G72
this._caps.disableMorphTargetTexture = true;
}
}
_initFeatures() {
this._features = {
forceBitmapOverHTMLImageElement: typeof HTMLImageElement === "undefined",
supportRenderAndCopyToLodForFloatTextures: this._webGLVersion !== 1,
supportDepthStencilTexture: this._webGLVersion !== 1,
supportShadowSamplers: this._webGLVersion !== 1,
uniformBufferHardCheckMatrix: false,
allowTexturePrefiltering: this._webGLVersion !== 1,
trackUbosInFrame: false,
checkUbosContentBeforeUpload: false,
supportCSM: this._webGLVersion !== 1,
basisNeedsPOT: this._webGLVersion === 1,
support3DTextures: this._webGLVersion !== 1,
needTypeSuffixInShaderConstants: this._webGLVersion !== 1,
supportMSAA: this._webGLVersion !== 1,
supportSSAO2: this._webGLVersion !== 1,
supportIBLShadows: this._webGLVersion !== 1,
supportExtendedTextureFormats: this._webGLVersion !== 1,
supportSwitchCaseInShader: this._webGLVersion !== 1,
supportSyncTextureRead: true,
needsInvertingBitmap: true,
useUBOBindingCache: true,
needShaderCodeInlining: false,
needToAlwaysBindUniformBuffers: false,
supportRenderPasses: false,
supportSpriteInstancing: true,
forceVertexBufferStrideAndOffsetMultiple4Bytes: false,
_checkNonFloatVertexBuffersDontRecreatePipelineContext: false,
_collectUbosUpdatedInFrame: false,
};
}
/**
* Gets version of the current webGL context
* Keep it for back compat - use version instead
*/
get webGLVersion() {
return this._webGLVersion;
}
/**
* Gets a string identifying the name of the class
* @returns "Engine" string
*/
getClassName() {
return "ThinEngine";
}
/** @internal */
_prepareWorkingCanvas() {
if (this._workingCanvas) {
return;
}
this._workingCanvas = this.createCanvas(1, 1);
const context = this._workingCanvas.getContext("2d");
if (context) {
this._workingContext = context;
}
}
/**
* Gets an object containing information about the current engine context
* @returns an object containing the vendor, the renderer and the version of the current engine context
*/
getInfo() {
return this.getGlInfo();
}
/**
* Gets an object containing information about the current webGL context
* @returns an object containing the vendor, the renderer and the version of the current webGL context
*/
getGlInfo() {
return {
vendor: this._glVendor,
renderer: this._glRenderer,
version: this._glVersion,
};
}
/**Gets driver info if available */
extractDriverInfo() {
const glInfo = this.getGlInfo();
if (glInfo && glInfo.renderer) {
return glInfo.renderer;
}
return "";
}
/**
* Gets the current render width
* @param useScreen defines if screen size must be used (or the current render target if any)
* @returns a number defining the current render width
*/
getRenderWidth(useScreen = false) {
if (!useScreen && this._currentRenderTarget) {
return this._currentRenderTarget.width;
}
return this._framebufferDimensionsObject ? this._framebufferDimensionsObject.framebufferWidth : this._gl.drawingBufferWidth;
}
/**
* Gets the current render height
* @param useScreen defines if screen size must be used (or the current render target if any)
* @returns a number defining the current render height
*/
getRenderHeight(useScreen = false) {
if (!useScreen && this._currentRenderTarget) {
return this._currentRenderTarget.height;
}
return this._framebufferDimensionsObject ? this._framebufferDimensionsObject.framebufferHeight : this._gl.drawingBufferHeight;
}
/**
* Clear the current render buffer or the current render target (if any is set up)
* @param color defines the color to use
* @param backBuffer defines if the back buffer must be cleared
* @param depth defines if the depth buffer must be cleared
* @param stencil defines if the stencil buffer must be cleared
* @param stencilClearValue defines the value to use to clear the stencil buffer (default is 0)
*/
clear(color, backBuffer, depth, stencil = false, stencilClearValue = 0) {
const useStencilGlobalOnly = this.stencilStateComposer.useStencilGlobalOnly;
this.stencilStateComposer.useStencilGlobalOnly = true; // make sure the stencil mask is coming from the global stencil and not from a material (effect) which would currently be in effect
this.applyStates();
this.stencilStateComposer.useStencilGlobalOnly = useStencilGlobalOnly;
let mode = 0;
if (backBuffer && color) {
let setBackBufferColor = true;
if (this._currentRenderTarget) {
const textureFormat = this._currentRenderTarget.texture?.format;
if (textureFormat === 8 ||
textureFormat === 9 ||
textureFormat === 10 ||
textureFormat === 11) {
const textureType = this._currentRenderTarget.texture?.type;
if (textureType === 7 || textureType === 5) {
ThinEngine._TempClearColorUint32[0] = color.r * 255;
ThinEngine._TempClearColorUint32[1] = color.g * 255;
ThinEngine._TempClearColorUint32[2] = color.b * 255;
ThinEngine._TempClearColorUint32[3] = color.a * 255;
this._gl.clearBufferuiv(this._gl.COLOR, 0, ThinEngine._TempClearColorUint32);
setBackBufferColor = false;
}
else {
ThinEngine._TempClearColorInt32[0] = color.r * 255;
ThinEngine._TempClearColorInt32[1] = color.g * 255;
ThinEngine._TempClearColorInt32[2] = color.b * 255;
ThinEngine._TempClearColorInt32[3] = color.a * 255;
this._gl.clearBufferiv(this._gl.COLOR, 0, ThinEngine._TempClearColorInt32);
setBackBufferColor = false;
}
}
}
if (setBackBufferColor) {
this._gl.clearColor(color.r, color.g, color.b, color.a !== undefined ? color.a : 1.0);
mode |= this._gl.COLOR_BUFFER_BIT;
}
}
if (depth) {
if (this.useReverseDepthBuffer) {
this._depthCullingState.depthFunc = this._gl.GEQUAL;
this._gl.clearDepth(0.0);
}
else {
this._gl.clearDepth(1.0);
}
mode |= this._gl.DEPTH_BUFFER_BIT;
}
if (stencil) {
this._gl.clearStencil(stencilClearValue);
mode |= this._gl.STENCIL_BUFFER_BIT;
}
this._gl.clear(mode);
}
/**
* @internal
*/
_viewport(x, y, width, height) {
if (x !== this._viewportCached.x || y !== this._viewportCached.y || width !== this._viewportCached.z || height !== this._viewportCached.w) {
this._viewportCached.x = x;
this._viewportCached.y = y;
this._viewportCached.z = width;
this._viewportCached.w = height;
this._gl.viewport(x, y, width, height);
}
}
/**
* End the current frame
*/
endFrame() {
super.endFrame();
// Force a flush in case we are using a bad OS.
if (this._badOS) {
this.flushFramebuffer();
}
}
/**
* Gets the performance monitor attached to this engine
* @see https://doc.babylonjs.com/features/featuresDeepDive/scene/optimize_your_scene#engineinstrumentation
*/
get performanceMonitor() {
throw new Error("Not Supported by ThinEngine");
}
/**
* Binds the frame buffer to the specified texture.
* @param rtWrapper The render target wrapper to render to
* @param faceIndex The face of the texture to render to in case of cube texture and if the render target wrapper is not a multi render target
* @param requiredWidth The width of the target to render to
* @param requiredHeight The height of the target to render to
* @param forceFullscreenViewport Forces the viewport to be the entire texture/screen if true
* @param lodLevel Defines the lod level to bind to the frame buffer
* @param layer Defines the 2d array index to bind to the frame buffer if the render target wrapper is not a multi render target
*/
bindFramebuffer(rtWrapper, faceIndex = 0, requiredWidth, requiredHeight, forceFullscreenViewport, lodLevel = 0, layer = 0) {
const webglRtWrapper = rtWrapper;
if (this._currentRenderTarget) {
this._resolveAndGenerateMipMapsFramebuffer(this._currentRenderTarget);
}
this._currentRenderTarget = rtWrapper;
this._bindUnboundFramebuffer(webglRtWrapper._framebuffer);
const gl = this._gl;
if (!rtWrapper.isMulti) {
if (rtWrapper.is2DArray || rtWrapper.is3D) {
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, rtWrapper.texture._hardwareTexture?.underlyingResource, lodLevel, layer);
webglRtWrapper._currentLOD = lodLevel;
}
else if (rtWrapper.isCube) {
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, rtWrapper.texture._hardwareTexture?.underlyingResource, lodLevel);
}
else if (webglRtWrapper._currentLOD !== lodLevel) {
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, rtWrapper.texture._hardwareTexture?.underlyingResource, lodLevel);
webglRtWrapper._currentLOD = lodLevel;
}
}
const depthStencilTexture = rtWrapper._depthStencilTexture;
if (depthStencilTexture) {
if (rtWrapper.is3D) {
if (rtWrapper.texture.width !== depthStencilTexture.width ||
rtWrapper.texture.height !== depthStencilTexture.height ||
rtWrapper.texture.depth !== depthStencilTexture.depth) {
Logger.Warn("Depth/Stencil attachment for 3D target must have same dimensions as color attachment");
}
}
const attachment = rtWrapper._depthStencilTextureWithStencil ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT;
if (rtWrapper.is2DArray || rtWrapper.is3D) {
gl.framebufferTextureLayer(gl.FRAMEBUFFER, attachment, depthStencilTexture._hardwareTexture?.underlyingResource, lodLevel, layer);
}
else if (rtWrapper.isCube) {
gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, depthStencilTexture._hardwareTexture?.underlyingResource, lodLevel);
}
else {
gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, depthStencilTexture._hardwareTexture?.underlyingResource, lodLevel);
}
}
if (webglRtWrapper._MSAAFramebuffer) {
this._bindUnboundFramebuffer(webglRtWrapper._MSAAFramebuffer);
}
if (this._cachedViewport && !forceFullscreenViewport) {
this.setViewport(this._cachedViewport, requiredWidth, requiredHeight);
}
else {
if (!requiredWidth) {
requiredWidth = rtWrapper.width;
if (lodLevel) {
requiredWidth = requiredWidth / Math.pow(2, lodLevel);
}
}
if (!requiredHeight) {
requiredHeight = rtWrapper.height;
if (lodLevel) {
requiredHeight = requiredHeight / Math.pow(2, lodLevel);
}
}
this._viewport(0, 0, requiredWidth, requiredHeight);
}
this.wipeCaches();
}
setStateCullFaceType(cullBackFaces, force) {
const cullFace = (this.cullBackFaces ?? cullBackFaces ?? true) ? this._gl.BACK : this._gl.FRONT;
if (this._depthCullingState.cullFace !== cullFace || force) {
this._depthCullingState.cullFace = cullFace;
}
}
/**
* Set various states to the webGL context
* @param culling defines culling state: true to enable culling, false to disable it
* @param zOffset defines the value to apply to zOffset (0 by default)
* @param force defines if states must be applied even if cache is up to date
* @param reverseSide defines if culling must be reversed (CCW if false, CW if true)
* @param cullBackFaces true to cull back faces, false to cull front faces (if culling is enabled)
* @param stencil stencil states to set
* @param zOffsetUnits defines the value to apply to zOffsetUnits (0 by default)
*/
setState(culling, zOffset = 0, force, reverseSide = false, cullBackFaces, stencil, zOffsetUnits = 0) {
// Culling
if (this._depthCullingState.cull !== culling || force) {
this._depthCullingState.cull = culling;
}
// Cull face
this.setStateCullFaceType(cullBackFaces, force);
// Z offset
this.setZOffset(zOffset);
this.setZOffsetUnits(zOffsetUnits);
// Front face
const frontFace = reverseSide ? this._gl.CW : this._gl.CCW;
if (this._depthCullingState.frontFace !== frontFace || force) {
this._depthCullingState.frontFace = frontFace;
}
this._stencilStateComposer.stencilMaterial = stencil;
}
_resolveAndGenerateMipMapsFramebuffer(texture, disableGenerateMipMaps = false) {
const webglRtWrapper = texture;
if (!webglRtWrapper.disableAutomaticMSAAResolve) {
if (texture.isMulti) {
this.resolveMultiFramebuffer(texture);
}
else {
this.resolveFramebuffer(texture);
}
}
if (!disableGenerateMipMaps) {
if (texture.isMulti) {
this.generateMipMapsMultiFramebuffer(texture);
}
else {
this.generateMipMapsFramebuffer(texture);
}
}
}
/**
* @internal
*/
_bindUnboundFramebuffer(framebuffer) {
if (this._currentFramebuffer !== framebuffer) {
this._gl.bindFramebuffer(this._gl.FRAMEBUFFER, framebuffer);
this._currentFramebuffer = framebuffer;
}
}
/** @internal */
_currentFrameBufferIsDefaultFrameBuffer() {
return this._currentFramebuffer === null;
}
/**
* Generates the mipmaps for a texture
* @param texture texture to generate the mipmaps for
*/
generateMipmaps(texture) {
const target = this._getTextureTarget(texture);
this._bindTextureDirectly(target, texture, true);
this._gl.generateMipmap(target);
this._bindTextureDirectly(target, null);
}
/**
* Unbind the current render target texture from the webGL context
* @param texture defines the render target wrapper to unbind
* @param disableGenerateMipMaps defines a boolean indicating that mipmaps must not be generated
* @param onBeforeUnbind defines a function which will be called before the effective unbind
*/
unBindFramebuffer(texture, disableGenerateMipMaps, onBeforeUnbind) {
const webglRtWrapper = texture;
this._currentRenderTarget = null;
this._resolveAndGenerateMipMapsFramebuffer(texture, disableGenerateMipMaps);
if (onBeforeUnbind) {
if (webglRtWrapper._MSAAFramebuffer) {
// Bind the correct framebuffer
this._bindUnboundFramebuffer(webglRtWrapper._framebuffer);
}
onBeforeUnbind();
}
this._bindUnboundFramebuffer(null);
}
/**
* Generates mipmaps for the texture of the (single) render target
* @param texture The render target containing the texture to generate the mipmaps for
*/
generateMipMapsFramebuffer(texture) {
if (!texture.isMulti && texture.texture?.generateMipMaps && !texture.isCube) {
this.generateMipmaps(texture.texture);
}
}
/**
* Resolves the MSAA texture of the (single) render target into its non-MSAA version.
* Note that if "texture" is not a MSAA render target, no resolve is performed.
* @param texture The render target texture containing the MSAA textures to resolve
*/
resolveFramebuffer(texture) {
const rtWrapper = texture;
const gl = this._gl;
if (!rtWrapper._MSAAFramebuffer || rtWrapper.isMulti) {
return;
}
let bufferBits = rtWrapper.resolveMSAAColors ? gl.COLOR_BUFFER_BIT : 0;
bufferBits |= rtWrapper._generateDepthBuffer && rtWrapper.resolveMSAADepth ? gl.DEPTH_BUFFER_BIT : 0;
bufferBits |= rtWrapper._generateStencilBuffer && rtWrapper.resolveMSAAStencil ? gl.STENCIL_BUFFER_BIT : 0;
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, rtWrapper._MSAAFramebuffer);
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, rtWrapper._framebuffer);
gl.blitFramebuffer(0, 0, texture.width, texture.height, 0, 0, texture.width, texture.height, bufferBits, gl.NEAREST);
}
/**
* Force a webGL flush (ie. a flush of all waiting webGL commands)
*/
flushFramebuffer() {
this._gl.flush();
}
/**
* Unbind the current render target and bind the default framebuffer
*/
restoreDefaultFramebuffer() {
if (this._currentRenderTarget) {
this.unBindFramebuffer(this._currentRenderTarget);
}
else {
this._bindUnboundFramebuffer(null);
}
if (this._cachedViewport) {
this.setViewport(this._cachedViewport);
}
this.wipeCaches();
}
// VBOs
/** @internal */
_resetVertexBufferBinding() {
this.bindArrayBuffer(null);
this._cachedVertexBuffers = null;
}
/**
* Creates a vertex buffer
* @param data the data or size for the vertex buffer
* @param _updatable whether the buffer should be created as updatable
* @param _label defines the label of the buffer (for debug purpose)
* @returns the new WebGL static buffer
*/
createVertexBuffer(data, _updatable, _label) {
return this._createVertexBuffer(data, this._gl.STATIC_DRAW);
}
_createVertexBuffer(data, usage) {
const vbo = this._gl.createBuffer();
if (!vbo) {
throw new Error("Unable to create vertex buffer");
}
const dataBuffer = new WebGLDataBuffer(vbo);
this.bindArrayBuffer(dataBuffer);
if (typeof data !== "number") {
if (data instanceof Array) {
this._gl.bufferData(this._gl.ARRAY_BUFFER, new Float32Array(data), usage);
dataBuffer.capacity = data.length * 4;
}
else {
this._gl.bufferData(this._gl.ARRAY_BUFFER, data, usage);
dataBuffer.capacity = data.byteLength;
}
}
else {
this.