UNPKG

@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
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.