diff --git a/packages/dev/core/src/Engines/AbstractEngine/abstractEngine.loadFile.ts b/packages/dev/core/src/Engines/AbstractEngine/abstractEngine.loadFile.ts new file mode 100644 index 00000000000..2f64b989cb1 --- /dev/null +++ b/packages/dev/core/src/Engines/AbstractEngine/abstractEngine.loadFile.ts @@ -0,0 +1,32 @@ +import { AbstractEngine } from "../../Engines/abstractEngine"; +import type { IOfflineProvider } from "../../Offline/IOfflineProvider"; + +declare module "../abstractEngine" { + // eslint-disable-next-line @typescript-eslint/naming-convention + export interface AbstractEngine { + /** + * @internal + */ + _loadFileAsync(url: string, offlineProvider?: IOfflineProvider, useArrayBuffer?: false): Promise; + _loadFileAsync(url: string, offlineProvider?: IOfflineProvider, useArrayBuffer?: true): Promise; + _loadFileAsync(url: string, offlineProvider?: IOfflineProvider, useArrayBuffer?: boolean): Promise; + } +} + +AbstractEngine.prototype._loadFileAsync = async function (url: string, offlineProvider?: IOfflineProvider, useArrayBuffer?: boolean): Promise { + return await new Promise((resolve, reject) => { + this._loadFile( + url, + (data) => { + resolve(data); + }, + undefined, + offlineProvider, + useArrayBuffer, + (request, exception) => { + // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors + reject(exception); + } + ); + }); +}; diff --git a/packages/dev/core/src/Engines/AbstractEngine/abstractEngine.textureLoaders.ts b/packages/dev/core/src/Engines/AbstractEngine/abstractEngine.textureLoaders.ts new file mode 100644 index 00000000000..fddca4274f9 --- /dev/null +++ b/packages/dev/core/src/Engines/AbstractEngine/abstractEngine.textureLoaders.ts @@ -0,0 +1,4 @@ +import { _GetCompatibleTextureLoader } from "core/Materials/Textures/Loaders/textureLoaderManager"; +import { AbstractEngine } from "../abstractEngine"; + +AbstractEngine.GetCompatibleTextureLoader = _GetCompatibleTextureLoader; diff --git a/packages/dev/core/src/Engines/AbstractEngine/index.ts b/packages/dev/core/src/Engines/AbstractEngine/index.ts index 83e72d0c804..d4d1568aea8 100644 --- a/packages/dev/core/src/Engines/AbstractEngine/index.ts +++ b/packages/dev/core/src/Engines/AbstractEngine/index.ts @@ -10,3 +10,5 @@ export * from "./abstractEngine.renderPass"; export * from "./abstractEngine.texture"; export * from "./abstractEngine.alpha"; export * from "./abstractEngine.views"; +export * from "./abstractEngine.loadFile"; +export * from "./abstractEngine.textureLoaders"; diff --git a/packages/dev/core/src/Engines/Native/Extensions/index.ts b/packages/dev/core/src/Engines/Native/Extensions/index.ts new file mode 100644 index 00000000000..3e3f2a7df3a --- /dev/null +++ b/packages/dev/core/src/Engines/Native/Extensions/index.ts @@ -0,0 +1 @@ +export * from "./nativeEngine.cubeTexture"; diff --git a/packages/dev/core/src/Engines/Native/Extensions/nativeEngine.cubeTexture.ts b/packages/dev/core/src/Engines/Native/Extensions/nativeEngine.cubeTexture.ts new file mode 100644 index 00000000000..6a957576064 --- /dev/null +++ b/packages/dev/core/src/Engines/Native/Extensions/nativeEngine.cubeTexture.ts @@ -0,0 +1,184 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { InternalTexture, InternalTextureSource } from "../../../Materials/Textures/internalTexture"; +import { Texture } from "../../../Materials/Textures/texture"; +import { CreateRadianceImageDataArrayBufferViews, GetEnvInfo, UploadEnvSpherical } from "../../../Misc/environmentTextureTools"; +import type { IWebRequest } from "../../../Misc/interfaces/iWebRequest"; +import type { Scene } from "../../../scene"; +import type { Nullable } from "../../../types"; +import { Constants } from "../../constants"; +import { ThinNativeEngine } from "../../thinNativeEngine"; + +declare module "../../../Engines/thinNativeEngine" { + // eslint-disable-next-line @typescript-eslint/naming-convention + export interface ThinNativeEngine { + /** + * Creates a cube texture + * @param rootUrl defines the url where the files to load is located + * @param scene defines the current scene + * @param files defines the list of files to load (1 per face) + * @param noMipmap defines a boolean indicating that no mipmaps shall be generated (false by default) + * @param onLoad defines an optional callback raised when the texture is loaded + * @param onError defines an optional callback raised if there is an issue to load the texture + * @param format defines the format of the data + * @param forcedExtension defines the extension to use to pick the right loader + * @param createPolynomials if a polynomial sphere should be created for the cube texture + * @param lodScale defines the scale applied to environment texture. This manages the range of LOD level used for IBL according to the roughness + * @param lodOffset defines the offset applied to environment texture. This manages first LOD level used for IBL according to the roughness + * @param fallback defines texture to use while falling back when (compressed) texture file not found. + * @param loaderOptions options to be passed to the loader + * @param useSRGBBuffer defines if the texture must be loaded in a sRGB GPU buffer (if supported by the GPU). + * @param buffer defines the data buffer to load instead of loading the rootUrl + * @returns the cube texture as an InternalTexture + */ + createCubeTexture( + rootUrl: string, + scene: Nullable, + files: Nullable, + noMipmap?: boolean, + onLoad?: Nullable<(data?: any) => void>, + onError?: Nullable<(message?: string, exception?: any) => void>, + format?: number, + forcedExtension?: any, + createPolynomials?: boolean, + lodScale?: number, + lodOffset?: number, + fallback?: Nullable, + loaderOptions?: any, + useSRGBBuffer?: boolean, + buffer?: Nullable + ): InternalTexture; + } +} + +ThinNativeEngine.prototype.createCubeTexture = function ( + rootUrl: string, + scene: Nullable, + files: Nullable, + noMipmap?: boolean, + onLoad: Nullable<(data?: any) => void> = null, + onError: Nullable<(message?: string, exception?: any) => void> = null, + format?: number, + forcedExtension: any = null, + createPolynomials = false, + lodScale: number = 0, + lodOffset: number = 0, + fallback: Nullable = null, + loaderOptions?: any, + useSRGBBuffer = false, + buffer: Nullable = null +): InternalTexture { + const texture = fallback ? fallback : new InternalTexture(this, InternalTextureSource.Cube); + texture.isCube = true; + texture.url = rootUrl; + texture.generateMipMaps = !noMipmap; + texture._lodGenerationScale = lodScale; + texture._lodGenerationOffset = lodOffset; + texture._useSRGBBuffer = this._getUseSRGBBuffer(useSRGBBuffer, !!noMipmap); + + if (!this._doNotHandleContextLost) { + texture._extension = forcedExtension; + texture._files = files; + texture._buffer = buffer; + } + + const lastDot = rootUrl.lastIndexOf("."); + const extension = forcedExtension ? forcedExtension : lastDot > -1 ? rootUrl.substring(lastDot).toLowerCase() : ""; + + // TODO: use texture loader to load env files? + if (extension === ".env") { + const onloaddata = (data: ArrayBufferView) => { + const info = GetEnvInfo(data)!; + texture.width = info.width; + texture.height = info.width; + + UploadEnvSpherical(texture, info); + + const specularInfo = info.specular; + if (!specularInfo) { + throw new Error(`Nothing else parsed so far`); + } + + texture._lodGenerationScale = specularInfo.lodGenerationScale; + const imageData = CreateRadianceImageDataArrayBufferViews(data, info); + + texture.format = Constants.TEXTUREFORMAT_RGBA; + texture.type = Constants.TEXTURETYPE_UNSIGNED_BYTE; + texture.generateMipMaps = true; + texture.getEngine().updateTextureSamplingMode(Texture.TRILINEAR_SAMPLINGMODE, texture); + texture._isRGBD = true; + texture.invertY = true; + + this._engine.loadCubeTextureWithMips( + texture._hardwareTexture!.underlyingResource, + imageData, + false, + texture._useSRGBBuffer, + () => { + texture.isReady = true; + if (onLoad) { + onLoad(); + } + }, + () => { + throw new Error("Could not load a native cube texture."); + } + ); + }; + + if (buffer) { + onloaddata(buffer); + } else if (files && files.length === 6) { + throw new Error(`Multi-file loading not allowed on env files.`); + } else { + const onInternalError = (request?: IWebRequest, exception?: any) => { + if (onError && request) { + onError(request.status + " " + request.statusText, exception); + } + }; + + this._loadFile( + rootUrl, + (data) => { + onloaddata(new Uint8Array(data as ArrayBuffer, 0, (data as ArrayBuffer).byteLength)); + }, + undefined, + undefined, + true, + onInternalError + ); + } + } else { + if (!files || files.length !== 6) { + throw new Error("Cannot load cubemap because 6 files were not defined"); + } + + // Reorder from [+X, +Y, +Z, -X, -Y, -Z] to [+X, -X, +Y, -Y, +Z, -Z]. + const reorderedFiles = [files[0], files[3], files[1], files[4], files[2], files[5]]; + // eslint-disable-next-line github/no-then + Promise.all(reorderedFiles.map(async (file) => await this._loadFileAsync(file, undefined, true).then((data) => new Uint8Array(data, 0, data.byteLength)))) + // eslint-disable-next-line github/no-then + .then(async (data) => { + return await new Promise((resolve, reject) => { + this._engine.loadCubeTexture(texture._hardwareTexture!.underlyingResource, data, !noMipmap, true, texture._useSRGBBuffer, resolve, reject); + }); + }) + // eslint-disable-next-line github/no-then + .then( + () => { + texture.isReady = true; + if (onLoad) { + onLoad(); + } + }, + (error) => { + if (onError) { + onError(`Failed to load cubemap: ${error.message}`, error); + } + } + ); + } + + this._internalTexturesCache.push(texture); + + return texture; +}; diff --git a/packages/dev/core/src/Engines/Native/nativePipelineContext.ts b/packages/dev/core/src/Engines/Native/nativePipelineContext.ts index b28b1660444..aca87e586ca 100644 --- a/packages/dev/core/src/Engines/Native/nativePipelineContext.ts +++ b/packages/dev/core/src/Engines/Native/nativePipelineContext.ts @@ -2,10 +2,10 @@ import type { Nullable } from "../../types"; import type { Effect } from "../../Materials/effect"; import type { IMatrixLike, IVector2Like, IVector3Like, IVector4Like, IColor3Like, IColor4Like, IQuaternionLike } from "../../Maths/math.like"; import type { IPipelineContext } from "../IPipelineContext"; -import type { NativeEngine } from "../nativeEngine"; import type { NativeProgram } from "./nativeInterfaces"; import type { AbstractEngine } from "../abstractEngine"; import type { NativeShaderProcessingContext } from "./nativeShaderProcessingContext"; +import type { ThinNativeEngine } from "../thinNativeEngine"; export class NativePipelineContext implements IPipelineContext { public isCompiled: boolean = false; @@ -36,11 +36,11 @@ export class NativePipelineContext implements IPipelineContext { return null; } - private _engine: NativeEngine; + private _engine: ThinNativeEngine; private _valueCache: { [key: string]: any } = {}; private _uniforms: { [key: string]: Nullable }; - constructor(engine: NativeEngine, isAsync: boolean, shaderProcessingContext: Nullable) { + constructor(engine: ThinNativeEngine, isAsync: boolean, shaderProcessingContext: Nullable) { this._engine = engine; this.isAsync = isAsync; this.shaderProcessingContext = shaderProcessingContext; @@ -86,7 +86,7 @@ export class NativePipelineContext implements IPipelineContext { } public setEngine(engine: AbstractEngine): void { - this._engine = engine as NativeEngine; + this._engine = engine as ThinNativeEngine; } /** diff --git a/packages/dev/core/src/Engines/Native/nativeRenderTargetWrapper.ts b/packages/dev/core/src/Engines/Native/nativeRenderTargetWrapper.ts index 52a021f23d3..4142ab6b033 100644 --- a/packages/dev/core/src/Engines/Native/nativeRenderTargetWrapper.ts +++ b/packages/dev/core/src/Engines/Native/nativeRenderTargetWrapper.ts @@ -1,11 +1,11 @@ import type { Nullable } from "../../types"; import type { TextureSize } from "../../Materials/Textures/textureCreationOptions"; import { RenderTargetWrapper } from "../renderTargetWrapper"; -import type { NativeEngine } from "../nativeEngine"; import type { NativeFramebuffer } from "./nativeInterfaces"; +import type { ThinNativeEngine } from "../thinNativeEngine"; export class NativeRenderTargetWrapper extends RenderTargetWrapper { - public override readonly _engine: NativeEngine; + public override readonly _engine: ThinNativeEngine; // eslint-disable-next-line @typescript-eslint/naming-convention private __framebuffer: Nullable = null; @@ -34,7 +34,7 @@ export class NativeRenderTargetWrapper extends RenderTargetWrapper { this.__framebufferDepthStencil = framebufferDepthStencil; } - constructor(isMulti: boolean, isCube: boolean, size: TextureSize, engine: NativeEngine) { + constructor(isMulti: boolean, isCube: boolean, size: TextureSize, engine: ThinNativeEngine) { super(isMulti, isCube, size, engine); this._engine = engine; } diff --git a/packages/dev/core/src/Engines/Native/validatedNativeDataStream.ts b/packages/dev/core/src/Engines/Native/validatedNativeDataStream.ts index 0824e675150..17864d34bcf 100644 --- a/packages/dev/core/src/Engines/Native/validatedNativeDataStream.ts +++ b/packages/dev/core/src/Engines/Native/validatedNativeDataStream.ts @@ -1,4 +1,4 @@ -import { NativeEngine } from "../nativeEngine"; +import { ThinNativeEngine } from "../thinNativeEngine"; import type { NativeData } from "./nativeDataStream"; import { NativeDataStream } from "./nativeDataStream"; import type { INative } from "./nativeInterfaces"; @@ -6,7 +6,7 @@ import type { INative } from "./nativeInterfaces"; // eslint-disable-next-line @typescript-eslint/naming-convention declare const _native: INative; -NativeEngine._createNativeDataStream = function () { +ThinNativeEngine._createNativeDataStream = function () { if (_native.NativeDataStream.VALIDATION_ENABLED) { return new ValidatedNativeDataStream(); } else { diff --git a/packages/dev/core/src/Engines/abstractEngine.ts b/packages/dev/core/src/Engines/abstractEngine.ts index 92cbf0b9931..d45a3fd3c8d 100644 --- a/packages/dev/core/src/Engines/abstractEngine.ts +++ b/packages/dev/core/src/Engines/abstractEngine.ts @@ -51,7 +51,7 @@ import { Constants } from "./constants"; import { Observable } from "../Misc/observable"; import { EngineFunctionContext, _LoadFile } from "./abstractEngine.functions"; import type { Material } from "core/Materials/material"; -import { _GetCompatibleTextureLoader } from "core/Materials/Textures/Loaders/textureLoaderManager"; +import type { IInternalTextureLoader } from "../Materials/Textures/Loaders/internalTextureLoader"; /** * Defines the interface used by objects working like Scene @@ -1589,7 +1589,7 @@ export abstract class AbstractEngine { extension = extension.split("?")[0]; } - const loaderPromise = _GetCompatibleTextureLoader(extension, mimeType); + const loaderPromise = AbstractEngine.GetCompatibleTextureLoader(extension, mimeType); if (scene) { scene.addPendingData(texture); @@ -2823,4 +2823,15 @@ export abstract class AbstractEngine { * @returns frame number */ public static QueueNewFrame: (func: () => void, requester?: any) => number = QueueNewFrame; + + /** + * @internal + * Function used to get the correct texture loader for a specific extension. + * @param extension defines the file extension of the file being loaded + * @param mimeType defines the optional mime type of the file being loaded + * @returns the IInternalTextureLoader or null if it wasn't found + */ + public static GetCompatibleTextureLoader(_extension: string, _mimeType?: string): Nullable> { + return null; + } } diff --git a/packages/dev/core/src/Engines/engine.ts b/packages/dev/core/src/Engines/engine.ts index 853bd570493..0d56018f316 100755 --- a/packages/dev/core/src/Engines/engine.ts +++ b/packages/dev/core/src/Engines/engine.ts @@ -1,7 +1,6 @@ import type { Nullable } from "../types"; import type { Scene } from "../scene"; import { InternalTexture, InternalTextureSource } from "../Materials/Textures/internalTexture"; -import type { IOfflineProvider } from "../Offline/IOfflineProvider"; import type { ILoadingScreen } from "../Loading/loadingScreen"; import { EngineStore } from "./engineStore"; import type { WebGLPipelineContext } from "./WebGL/webGLPipelineContext"; @@ -34,6 +33,8 @@ import "./AbstractEngine/abstractEngine.states"; import "./AbstractEngine/abstractEngine.stencil"; import "./AbstractEngine/abstractEngine.renderPass"; import "./AbstractEngine/abstractEngine.texture"; +import "./AbstractEngine/abstractEngine.loadFile"; +import "./AbstractEngine/abstractEngine.textureLoaders"; import type { PostProcess } from "../PostProcesses/postProcess"; import { AbstractEngine } from "./abstractEngine"; @@ -542,33 +543,6 @@ export class Engine extends ThinEngine { gl.disable(gl.SCISSOR_TEST); } - /** - * @internal - */ - public async _loadFileAsync(url: string, offlineProvider?: IOfflineProvider, useArrayBuffer?: false): Promise; - public async _loadFileAsync(url: string, offlineProvider?: IOfflineProvider, useArrayBuffer?: true): Promise; - - /** - * @internal - */ - public async _loadFileAsync(url: string, offlineProvider?: IOfflineProvider, useArrayBuffer?: boolean): Promise { - return await new Promise((resolve, reject) => { - this._loadFile( - url, - (data) => { - resolve(data); - }, - undefined, - offlineProvider, - useArrayBuffer, - (request, exception) => { - // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors - reject(exception); - } - ); - }); - } - /** * Gets the source code of the vertex shader associated with a specific webGL program * @param program defines the program to use diff --git a/packages/dev/core/src/Engines/index.ts b/packages/dev/core/src/Engines/index.ts index 37a4b7ae015..757987351e5 100644 --- a/packages/dev/core/src/Engines/index.ts +++ b/packages/dev/core/src/Engines/index.ts @@ -4,6 +4,7 @@ export * from "./engineCapabilities"; export * from "./instancingAttributeInfo"; export * from "./abstractEngine"; export * from "./thinEngine"; +export * from "./thinNativeEngine"; export * from "./engine"; export * from "./engineStore"; export * from "./nullEngine"; diff --git a/packages/dev/core/src/Engines/nativeEngine.ts b/packages/dev/core/src/Engines/nativeEngine.ts index c050c9e06e3..4bd902400a9 100644 --- a/packages/dev/core/src/Engines/nativeEngine.ts +++ b/packages/dev/core/src/Engines/nativeEngine.ts @@ -1,2765 +1,71 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import type { Nullable, IndicesArray, DataArray, FloatArray, DeepImmutable, int } from "../types"; -import { Engine } from "../Engines/engine"; -import type { VertexBuffer } from "../Buffers/buffer"; -import { InternalTexture, InternalTextureSource } from "../Materials/Textures/internalTexture"; -import { Texture } from "../Materials/Textures/texture"; -import type { BaseTexture } from "../Materials/Textures/baseTexture"; -import type { VideoTexture } from "../Materials/Textures/videoTexture"; -import type { RenderTargetTexture } from "../Materials/Textures/renderTargetTexture"; -import type { Effect } from "../Materials/effect"; -import { DataBuffer } from "../Buffers/dataBuffer"; -import { Tools } from "../Misc/tools"; -import type { Observer } from "../Misc/observable"; -import { Observable } from "../Misc/observable"; -import { CreateRadianceImageDataArrayBufferViews, GetEnvInfo, UploadEnvSpherical } from "../Misc/environmentTextureTools"; -import type { Scene } from "../scene"; -import type { RenderTargetCreationOptions, TextureSize, DepthTextureCreationOptions, InternalTextureCreationOptions } from "../Materials/Textures/textureCreationOptions"; -import type { IPipelineContext } from "./IPipelineContext"; -import type { IColor3Like, IColor4Like, IViewportLike } from "../Maths/math.like"; -import { Logger } from "../Misc/logger"; -import { Constants } from "./constants"; -import type { ISceneLike } from "./abstractEngine"; -import { ThinEngine } from "./thinEngine"; -import type { IWebRequest } from "../Misc/interfaces/iWebRequest"; -import { EngineStore } from "./engineStore"; -import { ShaderCodeInliner } from "./Processors/shaderCodeInliner"; -import { NativeShaderProcessor } from "./Native/nativeShaderProcessors"; -import type { IMaterialContext } from "./IMaterialContext"; -import type { IDrawContext } from "./IDrawContext"; -import type { ICanvas, IImage, IPath2D } from "./ICanvas"; -import type { IStencilState } from "../States/IStencilState"; -import type { RenderTargetWrapper } from "./renderTargetWrapper"; -import type { NativeData } from "./Native/nativeDataStream"; -import { NativeDataStream } from "./Native/nativeDataStream"; -import type { - INative, - INativeCamera, - INativeEngine, - NativeFramebuffer, - NativeFrameStats, - NativeProgram, - NativeTexture, - NativeUniform, - NativeVertexArrayObject, -} from "./Native/nativeInterfaces"; -import { NativePipelineContext } from "./Native/nativePipelineContext"; -import { NativeRenderTargetWrapper } from "./Native/nativeRenderTargetWrapper"; -import { NativeHardwareTexture } from "./Native/nativeHardwareTexture"; -import type { IHardwareTextureWrapper } from "../Materials/Textures/hardwareTextureWrapper"; -import { - getNativeAlphaMode, - getNativeAttribType, - getNativeSamplingMode, - getNativeTextureFormat, - getNativeStencilDepthFail, - getNativeStencilDepthPass, - getNativeStencilFunc, - getNativeStencilOpFail, - getNativeAddressMode, -} from "./Native/nativeHelpers"; -import { checkNonFloatVertexBuffers } from "../Buffers/buffer.nonFloatVertexBuffers"; -import type { _IShaderProcessingContext } from "./Processors/shaderProcessingOptions"; -import { NativeShaderProcessingContext } from "./Native/nativeShaderProcessingContext"; -import type { ShaderLanguage } from "../Materials/shaderLanguage"; -import type { WebGLHardwareTexture } from "./WebGL/webGLHardwareTexture"; -import "../Buffers/buffer.align"; -import { _GetCompatibleTextureLoader } from "core/Materials/Textures/Loaders/textureLoaderManager"; -import { _TimeToken } from "../Instrumentation/timeToken"; +import type { InternalTexture } from "../Materials/Textures/internalTexture"; +import { Engine } from "./engine"; +import type { ThinNativeEngineOptions } from "./thinNativeEngine"; +import { ThinNativeEngine } from "./thinNativeEngine"; -// REVIEW: add a flag to effect to prevent multiple compilations of the same shader. -declare module "../Materials/effect" { - /** internal */ - export interface Effect { - /** internal */ - _checkedNonFloatVertexBuffers?: boolean; - } -} - -declare const _native: INative; - -const onNativeObjectInitialized = new Observable(); -if (typeof self !== "undefined" && !Object.prototype.hasOwnProperty.call(self, "_native")) { - let __native: INative; - Object.defineProperty(self, "_native", { - get: () => __native, - set: (value: INative) => { - __native = value; - if (__native) { - onNativeObjectInitialized.notifyObservers(__native); - } - }, - }); -} - -/** - * Returns _native only after it has been defined by BabylonNative. - * @internal - */ -export async function AcquireNativeObjectAsync(): Promise { - return await new Promise((resolve) => { - if (typeof _native === "undefined") { - onNativeObjectInitialized.addOnce((nativeObject) => resolve(nativeObject)); - } else { - resolve(_native); - } - }); -} - -/** - * Registers a constructor on the _native object. See NativeXRFrame for an example. - * @internal - */ -export async function RegisterNativeTypeAsync(typeName: string, constructor: Type) { - ((await AcquireNativeObjectAsync()) as any)[typeName] = constructor; -} - -/** - * Container for accessors for natively-stored mesh data buffers. - */ -class NativeDataBuffer extends DataBuffer { - /** - * Accessor value used to identify/retrieve a natively-stored index buffer. - */ - public nativeIndexBuffer?: NativeData; - - /** - * Accessor value used to identify/retrieve a natively-stored vertex buffer. - */ - public nativeVertexBuffer?: NativeData; -} +import "./AbstractEngine/abstractEngine.loadFile"; +import "./AbstractEngine/abstractEngine.textureLoaders"; +import "./Native/Extensions/nativeEngine.cubeTexture"; /** * Options to create the Native engine */ -export interface NativeEngineOptions { - /** - * defines whether to adapt to the device's viewport characteristics (default: false) - */ - adaptToDeviceRatio?: boolean; -} - -/** @internal */ -class CommandBufferEncoder { - private readonly _commandStream: NativeDataStream; - private readonly _pending = new Array(); - private _isCommandBufferScopeActive = false; - - public constructor(private readonly _engine: INativeEngine) { - this._commandStream = NativeEngine._createNativeDataStream(); - this._engine.setCommandDataStream(this._commandStream); - } - - public beginCommandScope() { - if (this._isCommandBufferScopeActive) { - throw new Error("Command scope already active."); - } - - this._isCommandBufferScopeActive = true; - } - - public endCommandScope() { - if (!this._isCommandBufferScopeActive) { - throw new Error("Command scope is not active."); - } - - this._isCommandBufferScopeActive = false; - this._submit(); - } - - public startEncodingCommand(command: NativeData) { - this._commandStream.writeNativeData(command); - } - - public encodeCommandArgAsUInt32(commandArg: number) { - this._commandStream.writeUint32(commandArg); - } - - public encodeCommandArgAsUInt32s(commandArg: Uint32Array) { - this._commandStream.writeUint32Array(commandArg); - } - - public encodeCommandArgAsInt32(commandArg: number) { - this._commandStream.writeInt32(commandArg); - } - - public encodeCommandArgAsInt32s(commandArg: Int32Array) { - this._commandStream.writeInt32Array(commandArg); - } - - public encodeCommandArgAsFloat32(commandArg: number) { - this._commandStream.writeFloat32(commandArg); - } - - public encodeCommandArgAsFloat32s(commandArg: DeepImmutable) { - this._commandStream.writeFloat32Array(commandArg); - } - - public encodeCommandArgAsNativeData(commandArg: NativeData) { - this._commandStream.writeNativeData(commandArg); - this._pending.push(commandArg); - } - - public finishEncodingCommand() { - if (!this._isCommandBufferScopeActive) { - this._submit(); - } - } - - private _submit() { - this._engine.submitCommands(); - this._pending.length = 0; - } -} - -const remappedAttributesNames: string[] = []; +export interface NativeEngineOptions extends ThinNativeEngineOptions {} /** @internal */ export class NativeEngine extends Engine { - // This must match the protocol version in NativeEngine.cpp - private static readonly PROTOCOL_VERSION = 9; - - private readonly _engine: INativeEngine = new _native.Engine({ - version: Engine.Version, - nonFloatVertexBuffers: true, - }); - - private readonly _camera: Nullable = _native.Camera ? new _native.Camera() : null; - - private readonly _commandBufferEncoder = new CommandBufferEncoder(this._engine); - - private readonly _frameStats: NativeFrameStats = { gpuTimeNs: Number.NaN }; - - private _boundBuffersVertexArray: any = null; - private _currentDepthTest: number = _native.Engine.DEPTH_TEST_LEQUAL; - private _stencilTest = false; - private _stencilMask: number = 255; - private _stencilFunc: number = Constants.ALWAYS; - private _stencilFuncRef: number = 0; - private _stencilFuncMask: number = 255; - private _stencilOpStencilFail: number = Constants.KEEP; - private _stencilOpDepthFail: number = Constants.KEEP; - private _stencilOpStencilDepthPass: number = Constants.REPLACE; - private _zOffset: number = 0; - private _zOffsetUnits: number = 0; - private _depthWrite: boolean = true; - // warning for non supported fill mode has already been displayed - private _fillModeWarningDisplayed = false; - - public override setHardwareScalingLevel(level: number): void { - super.setHardwareScalingLevel(level); - this._engine.setHardwareScalingLevel(level); - } - - public constructor(options: NativeEngineOptions = {}) { - super(null, false, undefined, options.adaptToDeviceRatio); - - if (_native.Engine.PROTOCOL_VERSION !== NativeEngine.PROTOCOL_VERSION) { - throw new Error(`Protocol version mismatch: ${_native.Engine.PROTOCOL_VERSION} (Native) !== ${NativeEngine.PROTOCOL_VERSION} (JS)`); - } - - if (this._engine.setDeviceLostCallback) { - this._engine.setDeviceLostCallback(() => { - this.onContextLostObservable.notifyObservers(this); - this._contextWasLost = true; - this._restoreEngineAfterContextLost(); - }); - } - - this._webGLVersion = 2; - this.disableUniformBuffers = true; - this._shaderPlatformName = "NATIVE"; - - // TODO: Initialize this more correctly based on the hardware capabilities. - // Init caps - - this._caps = { - maxTexturesImageUnits: 16, - maxVertexTextureImageUnits: 16, - maxCombinedTexturesImageUnits: 32, - maxTextureSize: _native.Engine.CAPS_LIMITS_MAX_TEXTURE_SIZE, - maxCubemapTextureSize: 512, - maxRenderTextureSize: 512, - maxVertexAttribs: 16, - maxVaryingVectors: 16, - maxDrawBuffers: 8, - maxFragmentUniformVectors: 16, - maxVertexUniformVectors: 16, - shaderFloatPrecision: 23, // TODO: is this correct? - standardDerivatives: true, - astc: null, - pvrtc: null, - etc1: null, - etc2: null, - bptc: null, - maxAnisotropy: 16, // TODO: Retrieve this smartly. Currently set to D3D11 maximum allowable value. - uintIndices: true, - fragmentDepthSupported: false, - highPrecisionShaderSupported: true, - colorBufferFloat: false, - blendFloat: false, - supportFloatTexturesResolve: false, - rg11b10ufColorRenderable: false, - textureFloat: true, - textureFloatLinearFiltering: true, - textureFloatRender: true, - textureHalfFloat: true, - textureHalfFloatLinearFiltering: true, - textureHalfFloatRender: true, - textureLOD: true, - texelFetch: false, - drawBuffersExtension: false, - depthTextureExtension: false, - vertexArrayObject: true, - instancedArrays: true, - supportOcclusionQuery: false, - canUseTimestampForTimerQuery: false, - blendMinMax: false, - maxMSAASamples: 16, - canUseGLInstanceID: true, - canUseGLVertexID: true, - supportComputeShaders: false, - supportSRGBBuffers: true, - supportTransformFeedbacks: false, - textureMaxLevel: false, - texture2DArrayMaxLayerCount: _native.Engine.CAPS_LIMITS_MAX_TEXTURE_LAYERS, - disableMorphTargetTexture: false, - parallelShaderCompile: { COMPLETION_STATUS_KHR: 0 }, - textureNorm16: false, - blendParametersPerTarget: false, - dualSourceBlending: false, - }; - - this._features = { - forceBitmapOverHTMLImageElement: true, - supportRenderAndCopyToLodForFloatTextures: false, - supportDepthStencilTexture: false, - supportShadowSamplers: false, - uniformBufferHardCheckMatrix: false, - allowTexturePrefiltering: false, - trackUbosInFrame: false, - checkUbosContentBeforeUpload: false, - supportCSM: false, - basisNeedsPOT: false, - support3DTextures: false, - needTypeSuffixInShaderConstants: false, - supportMSAA: true, - supportSSAO2: false, - supportIBLShadows: false, - supportExtendedTextureFormats: false, - supportSwitchCaseInShader: false, - supportSyncTextureRead: false, - needsInvertingBitmap: true, - useUBOBindingCache: true, - needShaderCodeInlining: true, - needToAlwaysBindUniformBuffers: false, - supportRenderPasses: true, - supportSpriteInstancing: false, - forceVertexBufferStrideAndOffsetMultiple4Bytes: true, - _checkNonFloatVertexBuffersDontRecreatePipelineContext: false, - _collectUbosUpdatedInFrame: false, - }; - - Tools.Log("Babylon Native (v" + Engine.Version + ") launched"); - - Tools.LoadScript = function (scriptUrl, onSuccess, onError, scriptId) { - Tools.LoadFile( - scriptUrl, - (data) => { - Function(data as string).apply(null); - if (onSuccess) { - onSuccess(); - } - }, - undefined, - undefined, - false, - (request, exception) => { - if (onError) { - onError("LoadScript Error", exception); - } - } - ); - }; - - // Wrappers - if (typeof URL === "undefined") { - (window.URL as any) = { - createObjectURL: function () {}, - revokeObjectURL: function () {}, - }; - } - - // TODO: Remove in next protocol version update - if (typeof Blob === "undefined") { - (window.Blob as any) = function (v: any) { - return v; - }; - } - - // polyfill for Chakra - if (!Array.prototype.flat) { - Object.defineProperty(Array.prototype, "flat", { - configurable: true, - value: function flat() { - const depth = isNaN(arguments[0]) ? 1 : Number(arguments[0]); - - return depth - ? Array.prototype.reduce.call( - this, - function (acc: any, cur: any) { - if (Array.isArray(cur)) { - // eslint-disable-next-line prefer-spread - acc.push.apply(acc, flat.call(cur, depth - 1)); - } else { - acc.push(cur); - } - return acc; - }, - [] - ) - : Array.prototype.slice.call(this); - }, - writable: true, - }); - } - - // Currently we do not fully configure the ThinEngine on construction of NativeEngine. - // Setup resolution scaling based on display settings. - const devicePixelRatio = window ? window.devicePixelRatio || 1.0 : 1.0; - this._hardwareScalingLevel = options.adaptToDeviceRatio ? 1.0 / devicePixelRatio : 1.0; - this._engine.setHardwareScalingLevel(this._hardwareScalingLevel); - this._lastDevicePixelRatio = devicePixelRatio; - this.resize(); - - const currentDepthFunction = this.getDepthFunction(); - if (currentDepthFunction) { - this.setDepthFunction(currentDepthFunction); - } - - // Shader processor - this._shaderProcessor = new NativeShaderProcessor(); - - this.onNewSceneAddedObservable.add((scene) => { - const originalRender = scene.render; - scene.render = (...args: Parameters) => { - this._commandBufferEncoder.beginCommandScope(); - originalRender.apply(scene, args); - this._commandBufferEncoder.endCommandScope(); - }; - }); - } - - public override dispose(): void { - super.dispose(); - if (this._boundBuffersVertexArray) { - this._deleteVertexArray(this._boundBuffersVertexArray); - } - this._engine.dispose(); - } - - /** @internal */ - public static _createNativeDataStream(): NativeDataStream { - return new NativeDataStream(); - } - /** - * Can be used to override the current requestAnimationFrame requester. * @internal + * Will be overriden by the Thin Native engine implementation + * No code should be placed here */ - protected override _queueNewFrame(bindedRenderFunction: any, requester?: any): number { - // Use the provided requestAnimationFrame, unless the requester is the window. In that case, we will default to the Babylon Native version of requestAnimationFrame. - if (requester.requestAnimationFrame && requester !== window) { - requester.requestAnimationFrame(bindedRenderFunction); - } else { - this._engine.requestAnimationFrame(bindedRenderFunction); - } - return 0; - } - - protected override _restoreEngineAfterContextLost(): void { - this._clearEmptyResources(); - - const depthTest = this._depthCullingState.depthTest; // backup those values because the call to initEngine / wipeCaches will reset them - const depthFunc = this._depthCullingState.depthFunc; - const depthMask = this._depthCullingState.depthMask; - const stencilTest = this._stencilState.stencilTest; - - this._rebuildGraphicsResources(); - - this._depthCullingState.depthTest = depthTest; - this._depthCullingState.depthFunc = depthFunc; - this._depthCullingState.depthMask = depthMask; - this._stencilState.stencilTest = stencilTest; - - this._flagContextRestored(); - } - - /** - * Override default engine behavior. - * @param framebuffer - */ - public override _bindUnboundFramebuffer(framebuffer: Nullable) { - if (this._currentFramebuffer !== framebuffer) { - if (this._currentFramebuffer) { - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_UNBINDFRAMEBUFFER); - this._commandBufferEncoder.encodeCommandArgAsNativeData(this._currentFramebuffer as NativeFramebuffer); - this._commandBufferEncoder.finishEncodingCommand(); - } - - if (framebuffer) { - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_BINDFRAMEBUFFER); - this._commandBufferEncoder.encodeCommandArgAsNativeData(framebuffer as NativeFramebuffer); - this._commandBufferEncoder.finishEncodingCommand(); - } - - this._currentFramebuffer = framebuffer; - } - } - - /** - * Gets host document - * @returns the host document object - */ - public override getHostDocument(): Nullable { - return null; - } - - public override clear(color: Nullable, backBuffer: boolean, depth: boolean, stencil: boolean = false, stencilClearValue = 0): void { - if (this.useReverseDepthBuffer) { - throw new Error("reverse depth buffer is not currently implemented"); - } - - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_CLEAR); - this._commandBufferEncoder.encodeCommandArgAsUInt32(backBuffer && color ? 1 : 0); - this._commandBufferEncoder.encodeCommandArgAsFloat32(color ? color.r : 0); - this._commandBufferEncoder.encodeCommandArgAsFloat32(color ? color.g : 0); - this._commandBufferEncoder.encodeCommandArgAsFloat32(color ? color.b : 0); - this._commandBufferEncoder.encodeCommandArgAsFloat32(color ? color.a : 1); - this._commandBufferEncoder.encodeCommandArgAsUInt32(depth ? 1 : 0); - this._commandBufferEncoder.encodeCommandArgAsFloat32(1); - this._commandBufferEncoder.encodeCommandArgAsUInt32(stencil ? 1 : 0); - this._commandBufferEncoder.encodeCommandArgAsUInt32(stencilClearValue); - this._commandBufferEncoder.finishEncodingCommand(); - } - - public override createIndexBuffer(indices: IndicesArray, updateable?: boolean, _label?: string): NativeDataBuffer { - const data = this._normalizeIndexData(indices); - const buffer = new NativeDataBuffer(); - buffer.references = 1; - buffer.is32Bits = data.BYTES_PER_ELEMENT === 4; - if (data.byteLength) { - buffer.nativeIndexBuffer = this._engine.createIndexBuffer(data.buffer, data.byteOffset, data.byteLength, buffer.is32Bits, updateable ?? false); - } - return buffer; - } - - public override createVertexBuffer(vertices: DataArray, updateable?: boolean, _label?: string): NativeDataBuffer { - const data = ArrayBuffer.isView(vertices) ? vertices : new Float32Array(vertices); - const buffer = new NativeDataBuffer(); - buffer.references = 1; - if (data.byteLength) { - buffer.nativeVertexBuffer = this._engine.createVertexBuffer(data.buffer, data.byteOffset, data.byteLength, updateable ?? false); - } - return buffer; - } - - protected _recordVertexArrayObject( - vertexArray: any, - vertexBuffers: { [key: string]: VertexBuffer }, - indexBuffer: Nullable, - effect: Effect, - overrideVertexBuffers?: { [kind: string]: Nullable } - ): void { - if (!effect._checkedNonFloatVertexBuffers) { - checkNonFloatVertexBuffers(vertexBuffers, effect); - effect._checkedNonFloatVertexBuffers = true; - } - - if (indexBuffer) { - this._engine.recordIndexBuffer(vertexArray, indexBuffer.nativeIndexBuffer!); - } - - const attributes = effect.getAttributesNames(); - for (let index = 0; index < attributes.length; index++) { - const location = effect.getAttributeLocation(index); - if (location >= 0) { - const kind = attributes[index]; - let vertexBuffer: Nullable = null; - - if (overrideVertexBuffers) { - vertexBuffer = overrideVertexBuffers[kind]; - } - if (!vertexBuffer) { - vertexBuffer = vertexBuffers[kind]; - } - - if (vertexBuffer) { - const buffer = vertexBuffer.effectiveBuffer as Nullable; - if (buffer && buffer.nativeVertexBuffer) { - this._engine.recordVertexBuffer( - vertexArray, - buffer.nativeVertexBuffer, - location, - vertexBuffer.effectiveByteOffset, - vertexBuffer.effectiveByteStride, - vertexBuffer.getSize(), - getNativeAttribType(vertexBuffer.type), - vertexBuffer.normalized, - vertexBuffer.getInstanceDivisor() - ); - } - } - } - } - } - - public override bindBuffers(vertexBuffers: { [key: string]: VertexBuffer }, indexBuffer: Nullable, effect: Effect): void { - if (this._boundBuffersVertexArray) { - this._deleteVertexArray(this._boundBuffersVertexArray); - } - this._boundBuffersVertexArray = this._engine.createVertexArray(); - this._recordVertexArrayObject(this._boundBuffersVertexArray, vertexBuffers, indexBuffer, effect); - this.bindVertexArrayObject(this._boundBuffersVertexArray); - } - - public override recordVertexArrayObject( - vertexBuffers: { [key: string]: VertexBuffer }, - indexBuffer: Nullable, - effect: Effect, - overrideVertexBuffers?: { [kind: string]: Nullable } - ): WebGLVertexArrayObject { - const vertexArray = this._engine.createVertexArray(); - this._recordVertexArrayObject(vertexArray, vertexBuffers, indexBuffer, effect, overrideVertexBuffers); - return vertexArray; - } - - private _deleteVertexArray(vertexArray: NativeVertexArrayObject) { - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_DELETEVERTEXARRAY); - this._commandBufferEncoder.encodeCommandArgAsNativeData(vertexArray); - this._commandBufferEncoder.finishEncodingCommand(); - } - - public override bindVertexArrayObject(vertexArray: WebGLVertexArrayObject): void { - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_BINDVERTEXARRAY); - this._commandBufferEncoder.encodeCommandArgAsNativeData(vertexArray as NativeVertexArrayObject); - this._commandBufferEncoder.finishEncodingCommand(); - } - - public override releaseVertexArrayObject(vertexArray: WebGLVertexArrayObject) { - this._deleteVertexArray(vertexArray as NativeVertexArrayObject); - } - - public override getAttributes(pipelineContext: IPipelineContext, attributesNames: string[]): number[] { - const nativePipelineContext = pipelineContext as NativePipelineContext; - const nativeShaderProcessingContext = nativePipelineContext.shaderProcessingContext!; - - remappedAttributesNames.length = 0; - for (let index = 0; index < attributesNames.length; index++) { - const origAttributeName = attributesNames[index]; - const attributeName = nativeShaderProcessingContext.remappedAttributeNames[origAttributeName] ?? origAttributeName; - remappedAttributesNames[index] = attributeName; - } - return this._engine.getAttributes(nativePipelineContext.program, remappedAttributesNames); - } - - /** - * Triangle Fan and Line Loop are not supported by modern rendering API - * @param fillMode defines the primitive to use - * @returns true if supported - */ - private _checkSupportedFillMode(fillMode: number): boolean { - if (fillMode == Constants.MATERIAL_LineLoopDrawMode || fillMode == Constants.MATERIAL_TriangleFanDrawMode) { - if (!this._fillModeWarningDisplayed) { - Logger.Warn("Line Loop and Triangle Fan are not supported fill modes with Babylon Native. Elements with these fill mode will not be visible."); - this._fillModeWarningDisplayed = true; - } - return false; - } - return true; - } - - /** - * Draw a list of indexed primitives - * @param fillMode defines the primitive to use - * @param indexStart defines the starting index - * @param indexCount defines the number of index to draw - * @param instancesCount defines the number of instances to draw (if instantiation is enabled) - */ - public override drawElementsType(fillMode: number, indexStart: number, indexCount: number, instancesCount?: number): void { - if (!this._checkSupportedFillMode(fillMode)) { - return; - } - // Apply states - this._drawCalls.addCount(1, false); - - if (instancesCount) { - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_DRAWINDEXEDINSTANCED); - this._commandBufferEncoder.encodeCommandArgAsUInt32(fillMode); - this._commandBufferEncoder.encodeCommandArgAsUInt32(indexStart); - this._commandBufferEncoder.encodeCommandArgAsUInt32(indexCount); - this._commandBufferEncoder.encodeCommandArgAsUInt32(instancesCount); - } else { - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_DRAWINDEXED); - this._commandBufferEncoder.encodeCommandArgAsUInt32(fillMode); - this._commandBufferEncoder.encodeCommandArgAsUInt32(indexStart); - this._commandBufferEncoder.encodeCommandArgAsUInt32(indexCount); - } - - this._commandBufferEncoder.finishEncodingCommand(); - } - - /** - * Draw a list of unindexed primitives - * @param fillMode defines the primitive to use - * @param verticesStart defines the index of first vertex to draw - * @param verticesCount defines the count of vertices to draw - * @param instancesCount defines the number of instances to draw (if instantiation is enabled) - */ - public override drawArraysType(fillMode: number, verticesStart: number, verticesCount: number, instancesCount?: number): void { - if (!this._checkSupportedFillMode(fillMode)) { - return; - } - // Apply states - this._drawCalls.addCount(1, false); - - if (instancesCount) { - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_DRAWINSTANCED); - this._commandBufferEncoder.encodeCommandArgAsUInt32(fillMode); - this._commandBufferEncoder.encodeCommandArgAsUInt32(verticesStart); - this._commandBufferEncoder.encodeCommandArgAsUInt32(verticesCount); - this._commandBufferEncoder.encodeCommandArgAsUInt32(instancesCount); - } else { - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_DRAW); - this._commandBufferEncoder.encodeCommandArgAsUInt32(fillMode); - this._commandBufferEncoder.encodeCommandArgAsUInt32(verticesStart); - this._commandBufferEncoder.encodeCommandArgAsUInt32(verticesCount); - } - - this._commandBufferEncoder.finishEncodingCommand(); - } - - public override createPipelineContext(shaderProcessingContext: Nullable<_IShaderProcessingContext>): IPipelineContext { - const isAsync = !!this._caps.parallelShaderCompile; - return new NativePipelineContext(this, isAsync, shaderProcessingContext as Nullable); - } - - public override createMaterialContext(): IMaterialContext | undefined { - return undefined; - } - - public override createDrawContext(): IDrawContext | undefined { - return undefined; - } + protected _initializeNativeEngine(_adaptToDeviceRatio: boolean): void {} /** - * Function is not technically Async * @internal */ - // eslint-disable-next-line no-restricted-syntax - public override _preparePipelineContextAsync( - pipelineContext: IPipelineContext, - vertexSourceCode: string, - fragmentSourceCode: string, - createAsRaw: boolean, - _rawVertexSourceCode: string, - _rawFragmentSourceCode: string, - _rebuildRebind: any, - defines: Nullable, - _transformFeedbackVaryings: Nullable, - _key: string, - onReady: () => void - ) { - if (createAsRaw) { - this.createRawShaderProgram(); - } else { - this.createShaderProgram(pipelineContext, vertexSourceCode, fragmentSourceCode, defines); - } + public constructor(options: NativeEngineOptions = {}) { + super(null, false, undefined, options.adaptToDeviceRatio); - onReady(); + this._initializeNativeEngine(options.adaptToDeviceRatio ?? false); } /** * @internal */ - public override _getShaderProcessingContext(_shaderLanguage: ShaderLanguage): Nullable<_IShaderProcessingContext> { - return new NativeShaderProcessingContext(); + public override wrapWebGLTexture(): InternalTexture { + throw new Error("wrapWebGLTexture is not supported, use wrapNativeTexture instead."); } /** * @internal */ - public override _executeWhenRenderingStateIsCompiled(pipelineContext: IPipelineContext, action: () => void) { - const nativePipelineContext = pipelineContext as NativePipelineContext; - if (nativePipelineContext.isAsync) { - if (nativePipelineContext.onCompiled) { - const oldHandler = nativePipelineContext.onCompiled; - nativePipelineContext.onCompiled = () => { - oldHandler(); - action(); - }; - } else { - nativePipelineContext.onCompiled = action; - } - } else { - action(); - } - } - - public override createRawShaderProgram(): WebGLProgram { - throw new Error("Not Supported"); - } - - public override createShaderProgram(pipelineContext: IPipelineContext, vertexCode: string, fragmentCode: string, defines: Nullable): WebGLProgram { - const nativePipelineContext = pipelineContext as NativePipelineContext; - - this.onBeforeShaderCompilationObservable.notifyObservers(this); - - const vertexInliner = new ShaderCodeInliner(vertexCode); - vertexInliner.processCode(); - vertexCode = vertexInliner.code; - - const fragmentInliner = new ShaderCodeInliner(fragmentCode); - fragmentInliner.processCode(); - fragmentCode = fragmentInliner.code; - - vertexCode = ThinEngine._ConcatenateShader(vertexCode, defines); - fragmentCode = ThinEngine._ConcatenateShader(fragmentCode, defines); - - const onSuccess = () => { - nativePipelineContext.isCompiled = true; - nativePipelineContext.onCompiled?.(); - this.onAfterShaderCompilationObservable.notifyObservers(this); - }; - - if (pipelineContext.isAsync) { - nativePipelineContext.program = this._engine.createProgramAsync(vertexCode, fragmentCode, onSuccess, (error: Error) => { - nativePipelineContext.compilationError = error; - }); - } else { - try { - nativePipelineContext.program = this._engine.createProgram(vertexCode, fragmentCode); - onSuccess(); - } catch (e) { - const message = e?.message; - throw new Error("SHADER ERROR" + (typeof message === "string" ? "\n" + message : "")); - } - } - - return nativePipelineContext.program as WebGLProgram; - } - - /** - * Inline functions in shader code that are marked to be inlined - * @param code code to inline - * @returns inlined code - */ - public override inlineShaderCode(code: string): string { - const sci = new ShaderCodeInliner(code); - sci.debug = false; - sci.processCode(); - return sci.code; - } - - protected override _setProgram(program: WebGLProgram): void { - if (this._currentProgram !== program) { - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETPROGRAM); - this._commandBufferEncoder.encodeCommandArgAsNativeData(program as NativeProgram); - this._commandBufferEncoder.finishEncodingCommand(); - this._currentProgram = program; - } - } - - public override _deletePipelineContext(pipelineContext: IPipelineContext): void { - const nativePipelineContext = pipelineContext as NativePipelineContext; - if (nativePipelineContext && nativePipelineContext.program) { - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_DELETEPROGRAM); - this._commandBufferEncoder.encodeCommandArgAsNativeData(nativePipelineContext.program); - this._commandBufferEncoder.finishEncodingCommand(); - } - } - - public override getUniforms(pipelineContext: IPipelineContext, uniformsNames: string[]): WebGLUniformLocation[] { - const nativePipelineContext = pipelineContext as NativePipelineContext; - return this._engine.getUniforms(nativePipelineContext.program, uniformsNames); - } - - public override bindUniformBlock(pipelineContext: IPipelineContext, blockName: string, index: number): void { - // TODO - throw new Error("Not Implemented"); + public override _uploadImageToTexture(texture: InternalTexture, image: HTMLImageElement, _faceIndex: number = 0, _lod: number = 0) { + throw new Error("_uploadArrayBufferViewToTexture not implemented."); } +} - public override bindSamplers(effect: Effect): void { - const nativePipelineContext = effect.getPipelineContext() as NativePipelineContext; - this._setProgram(nativePipelineContext.program as WebGLProgram); - - // TODO: share this with engine? - const samplers = effect.getSamplers(); - for (let index = 0; index < samplers.length; index++) { - const uniform = effect.getUniform(samplers[index]); +/** + * @internal + * Augments the NativeEngine type to include ThinNativeEngine methods and preventing dupplicate TS errors + */ +export interface NativeEngine extends Omit {} - if (uniform) { - this._boundUniforms[index] = uniform; +/** + * @internal + * Applies the functionality of one or more base constructors to a derived constructor. + */ +function applyMixins(derivedCtor: any, constructors: any[]) { + constructors.forEach((baseCtor) => { + Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => { + if (name !== "constructor") { + derivedCtor.prototype[name] = baseCtor.prototype[name]; } - } - this._currentEffect = null; - } - - public override getRenderWidth(useScreen = false): number { - if (!useScreen && this._currentRenderTarget) { - return this._currentRenderTarget.width; - } - - return this._engine.getRenderWidth(); - } - - public override getRenderHeight(useScreen = false): number { - if (!useScreen && this._currentRenderTarget) { - return this._currentRenderTarget.height; - } - - return this._engine.getRenderHeight(); - } - - public override setViewport(viewport: IViewportLike, requiredWidth?: number, requiredHeight?: number): void { - this._cachedViewport = viewport; - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETVIEWPORT); - this._commandBufferEncoder.encodeCommandArgAsFloat32(viewport.x); - this._commandBufferEncoder.encodeCommandArgAsFloat32(viewport.y); - this._commandBufferEncoder.encodeCommandArgAsFloat32(viewport.width); - this._commandBufferEncoder.encodeCommandArgAsFloat32(viewport.height); - this._commandBufferEncoder.finishEncodingCommand(); - } - - public override enableScissor(x: number, y: number, width: number, height: number): void { - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETSCISSOR); - this._commandBufferEncoder.encodeCommandArgAsFloat32(x); - this._commandBufferEncoder.encodeCommandArgAsFloat32(y); - this._commandBufferEncoder.encodeCommandArgAsFloat32(width); - this._commandBufferEncoder.encodeCommandArgAsFloat32(height); - this._commandBufferEncoder.finishEncodingCommand(); - } - - public override disableScissor() { - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETSCISSOR); - this._commandBufferEncoder.encodeCommandArgAsFloat32(0); - this._commandBufferEncoder.encodeCommandArgAsFloat32(0); - this._commandBufferEncoder.encodeCommandArgAsFloat32(0); - this._commandBufferEncoder.encodeCommandArgAsFloat32(0); - this._commandBufferEncoder.finishEncodingCommand(); - } - - public override setStateCullFaceType(_cullBackFaces?: boolean, _force?: boolean): void { - throw new Error("setStateCullFaceType: Not Implemented"); - } - - public override setState( - culling: boolean, - zOffset: number = 0, - force?: boolean, - reverseSide = false, - cullBackFaces?: boolean, - stencil?: IStencilState, - zOffsetUnits: number = 0 - ): void { - this._zOffset = zOffset; - this._zOffsetUnits = zOffsetUnits; - if (this._zOffset !== 0) { - Tools.Warn("zOffset is not supported in Native engine."); - } - - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETSTATE); - this._commandBufferEncoder.encodeCommandArgAsUInt32(culling ? 1 : 0); - this._commandBufferEncoder.encodeCommandArgAsFloat32(zOffset); - this._commandBufferEncoder.encodeCommandArgAsFloat32(zOffsetUnits); - this._commandBufferEncoder.encodeCommandArgAsUInt32((this.cullBackFaces ?? cullBackFaces ?? true) ? 1 : 0); - this._commandBufferEncoder.encodeCommandArgAsUInt32(reverseSide ? 1 : 0); - this._commandBufferEncoder.finishEncodingCommand(); - } - - /** - * Gets the client rect of native canvas. Needed for InputManager. - * @returns a client rectangle - */ - public override getInputElementClientRect(): Nullable { - const rect = { - bottom: this.getRenderHeight(), - height: this.getRenderHeight(), - left: 0, - right: this.getRenderWidth(), - top: 0, - width: this.getRenderWidth(), - x: 0, - y: 0, - toJSON: () => {}, - }; - return rect; - } - - /** - * Set the z offset Factor to apply to current rendering - * @param value defines the offset to apply - */ - public override setZOffset(value: number): void { - if (value !== this._zOffset) { - this._zOffset = value; - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETZOFFSET); - this._commandBufferEncoder.encodeCommandArgAsFloat32(this.useReverseDepthBuffer ? -value : value); - this._commandBufferEncoder.finishEncodingCommand(); - } - } - - /** - * Gets the current value of the zOffset Factor - * @returns the current zOffset Factor state - */ - public override getZOffset(): number { - return this._zOffset; - } - - /** - * Set the z offset Units to apply to current rendering - * @param value defines the offset to apply - */ - public override setZOffsetUnits(value: number): void { - if (value !== this._zOffsetUnits) { - this._zOffsetUnits = value; - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETZOFFSETUNITS); - this._commandBufferEncoder.encodeCommandArgAsFloat32(this.useReverseDepthBuffer ? -value : value); - this._commandBufferEncoder.finishEncodingCommand(); - } - } - - /** - * Gets the current value of the zOffset Units - * @returns the current zOffset Units state - */ - public override getZOffsetUnits(): number { - return this._zOffsetUnits; - } - - /** - * Enable or disable depth buffering - * @param enable defines the state to set - */ - public override setDepthBuffer(enable: boolean): void { - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETDEPTHTEST); - this._commandBufferEncoder.encodeCommandArgAsUInt32(enable ? this._currentDepthTest : _native.Engine.DEPTH_TEST_ALWAYS); - this._commandBufferEncoder.finishEncodingCommand(); - } - - /** - * Gets a boolean indicating if depth writing is enabled - * @returns the current depth writing state - */ - public override getDepthWrite(): boolean { - return this._depthWrite; - } - - public override getDepthFunction(): Nullable { - switch (this._currentDepthTest) { - case _native.Engine.DEPTH_TEST_NEVER: - return Constants.NEVER; - case _native.Engine.DEPTH_TEST_ALWAYS: - return Constants.ALWAYS; - case _native.Engine.DEPTH_TEST_GREATER: - return Constants.GREATER; - case _native.Engine.DEPTH_TEST_GEQUAL: - return Constants.GEQUAL; - case _native.Engine.DEPTH_TEST_NOTEQUAL: - return Constants.NOTEQUAL; - case _native.Engine.DEPTH_TEST_EQUAL: - return Constants.EQUAL; - case _native.Engine.DEPTH_TEST_LESS: - return Constants.LESS; - case _native.Engine.DEPTH_TEST_LEQUAL: - return Constants.LEQUAL; - } - return null; - } - - public override setDepthFunction(depthFunc: number) { - let nativeDepthFunc = 0; - switch (depthFunc) { - case Constants.NEVER: - nativeDepthFunc = _native.Engine.DEPTH_TEST_NEVER; - break; - case Constants.ALWAYS: - nativeDepthFunc = _native.Engine.DEPTH_TEST_ALWAYS; - break; - case Constants.GREATER: - nativeDepthFunc = _native.Engine.DEPTH_TEST_GREATER; - break; - case Constants.GEQUAL: - nativeDepthFunc = _native.Engine.DEPTH_TEST_GEQUAL; - break; - case Constants.NOTEQUAL: - nativeDepthFunc = _native.Engine.DEPTH_TEST_NOTEQUAL; - break; - case Constants.EQUAL: - nativeDepthFunc = _native.Engine.DEPTH_TEST_EQUAL; - break; - case Constants.LESS: - nativeDepthFunc = _native.Engine.DEPTH_TEST_LESS; - break; - case Constants.LEQUAL: - nativeDepthFunc = _native.Engine.DEPTH_TEST_LEQUAL; - break; - } + }); + }); +} - this._currentDepthTest = nativeDepthFunc; - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETDEPTHTEST); - this._commandBufferEncoder.encodeCommandArgAsUInt32(this._currentDepthTest); - this._commandBufferEncoder.finishEncodingCommand(); - } - - /** - * Enable or disable depth writing - * @param enable defines the state to set - */ - public override setDepthWrite(enable: boolean): void { - this._depthWrite = enable; - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETDEPTHWRITE); - this._commandBufferEncoder.encodeCommandArgAsUInt32(Number(enable)); - this._commandBufferEncoder.finishEncodingCommand(); - } - - /** - * Enable or disable color writing - * @param enable defines the state to set - */ - public override setColorWrite(enable: boolean): void { - this._colorWrite = enable; - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETCOLORWRITE); - this._commandBufferEncoder.encodeCommandArgAsUInt32(Number(enable)); - this._commandBufferEncoder.finishEncodingCommand(); - } - - /** - * Gets a boolean indicating if color writing is enabled - * @returns the current color writing state - */ - public override getColorWrite(): boolean { - return this._colorWrite; - } - - private applyStencil(): void { - this._setStencil( - this._stencilMask, - getNativeStencilOpFail(this._stencilOpStencilFail), - getNativeStencilDepthFail(this._stencilOpDepthFail), - getNativeStencilDepthPass(this._stencilOpStencilDepthPass), - getNativeStencilFunc(this._stencilFunc), - this._stencilFuncRef - ); - } - - private _setStencil(mask: number, stencilOpFail: number, depthOpFail: number, depthOpPass: number, func: number, ref: number) { - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETSTENCIL); - this._commandBufferEncoder.encodeCommandArgAsUInt32(mask); - this._commandBufferEncoder.encodeCommandArgAsUInt32(stencilOpFail); - this._commandBufferEncoder.encodeCommandArgAsUInt32(depthOpFail); - this._commandBufferEncoder.encodeCommandArgAsUInt32(depthOpPass); - this._commandBufferEncoder.encodeCommandArgAsUInt32(func); - this._commandBufferEncoder.encodeCommandArgAsUInt32(ref); - this._commandBufferEncoder.finishEncodingCommand(); - } - - /** - * Enable or disable the stencil buffer - * @param enable defines if the stencil buffer must be enabled or disabled - */ - public override setStencilBuffer(enable: boolean): void { - this._stencilTest = enable; - if (enable) { - this.applyStencil(); - } else { - this._setStencil( - 255, - _native.Engine.STENCIL_OP_FAIL_S_KEEP, - _native.Engine.STENCIL_OP_FAIL_Z_KEEP, - _native.Engine.STENCIL_OP_PASS_Z_KEEP, - _native.Engine.STENCIL_TEST_ALWAYS, - 0 - ); - } - } - - /** - * Gets a boolean indicating if stencil buffer is enabled - * @returns the current stencil buffer state - */ - public override getStencilBuffer(): boolean { - return this._stencilTest; - } - - /** - * Gets the current stencil operation when stencil passes - * @returns a number defining stencil operation to use when stencil passes - */ - public override getStencilOperationPass(): number { - return this._stencilOpStencilDepthPass; - } - - /** - * Sets the stencil operation to use when stencil passes - * @param operation defines the stencil operation to use when stencil passes - */ - public override setStencilOperationPass(operation: number): void { - this._stencilOpStencilDepthPass = operation; - this.applyStencil(); - } - - /** - * Sets the current stencil mask - * @param mask defines the new stencil mask to use - */ - public override setStencilMask(mask: number): void { - this._stencilMask = mask; - this.applyStencil(); - } - - /** - * Sets the current stencil function - * @param stencilFunc defines the new stencil function to use - */ - public override setStencilFunction(stencilFunc: number) { - this._stencilFunc = stencilFunc; - this.applyStencil(); - } - - /** - * Sets the current stencil reference - * @param reference defines the new stencil reference to use - */ - public override setStencilFunctionReference(reference: number) { - this._stencilFuncRef = reference; - this.applyStencil(); - } - - /** - * Sets the current stencil mask - * @param mask defines the new stencil mask to use - */ - public override setStencilFunctionMask(mask: number) { - this._stencilFuncMask = mask; - } - - /** - * Sets the stencil operation to use when stencil fails - * @param operation defines the stencil operation to use when stencil fails - */ - public override setStencilOperationFail(operation: number): void { - this._stencilOpStencilFail = operation; - this.applyStencil(); - } - - /** - * Sets the stencil operation to use when depth fails - * @param operation defines the stencil operation to use when depth fails - */ - public override setStencilOperationDepthFail(operation: number): void { - this._stencilOpDepthFail = operation; - this.applyStencil(); - } - - /** - * Gets the current stencil mask - * @returns a number defining the new stencil mask to use - */ - public override getStencilMask(): number { - return this._stencilMask; - } - - /** - * Gets the current stencil function - * @returns a number defining the stencil function to use - */ - public override getStencilFunction(): number { - return this._stencilFunc; - } - - /** - * Gets the current stencil reference value - * @returns a number defining the stencil reference value to use - */ - public override getStencilFunctionReference(): number { - return this._stencilFuncRef; - } - - /** - * Gets the current stencil mask - * @returns a number defining the stencil mask to use - */ - public override getStencilFunctionMask(): number { - return this._stencilFuncMask; - } - - /** - * Gets the current stencil operation when stencil fails - * @returns a number defining stencil operation to use when stencil fails - */ - public override getStencilOperationFail(): number { - return this._stencilOpStencilFail; - } - - /** - * Gets the current stencil operation when depth fails - * @returns a number defining stencil operation to use when depth fails - */ - public override getStencilOperationDepthFail(): number { - return this._stencilOpDepthFail; - } - - /** - * Sets alpha constants used by some alpha blending modes - * @param r defines the red component - * @param g defines the green component - * @param b defines the blue component - * @param a defines the alpha component - */ - public override setAlphaConstants(r: number, g: number, b: number, a: number) { - throw new Error("Setting alpha blend constant color not yet implemented."); - } - - /** - * Sets the current alpha mode - * @param mode defines the mode to use (one of the BABYLON.Constants.ALPHA_XXX) - * @param noDepthWriteChange defines if depth writing state should remains unchanged (false by default) - * @param targetIndex defines the index of the target to set the alpha mode for (default is 0) - * @see https://doc.babylonjs.com/features/featuresDeepDive/materials/advanced/transparent_rendering - */ - public override setAlphaMode(mode: number, noDepthWriteChange: boolean = false, targetIndex = 0): void { - if (this._alphaMode[targetIndex] === mode) { - return; - } - - const nativeMode = getNativeAlphaMode(mode); - - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETBLENDMODE); - this._commandBufferEncoder.encodeCommandArgAsUInt32(nativeMode); - this._commandBufferEncoder.finishEncodingCommand(); - - if (!noDepthWriteChange) { - this.setDepthWrite(mode === Constants.ALPHA_DISABLE); - } - - this._alphaMode[targetIndex] = mode; - } - - public override setInt(uniform: WebGLUniformLocation, int: number): boolean { - if (!uniform) { - return false; - } - - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETINT); - this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); - this._commandBufferEncoder.encodeCommandArgAsInt32(int); - this._commandBufferEncoder.finishEncodingCommand(); - return true; - } - - public override setIntArray(uniform: WebGLUniformLocation, array: Int32Array): boolean { - if (!uniform) { - return false; - } - - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETINTARRAY); - this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); - this._commandBufferEncoder.encodeCommandArgAsInt32s(array); - this._commandBufferEncoder.finishEncodingCommand(); - return true; - } - - public override setIntArray2(uniform: WebGLUniformLocation, array: Int32Array): boolean { - if (!uniform) { - return false; - } - - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETINTARRAY2); - this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); - this._commandBufferEncoder.encodeCommandArgAsInt32s(array); - this._commandBufferEncoder.finishEncodingCommand(); - return true; - } - - public override setIntArray3(uniform: WebGLUniformLocation, array: Int32Array): boolean { - if (!uniform) { - return false; - } - - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETINTARRAY3); - this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); - this._commandBufferEncoder.encodeCommandArgAsInt32s(array); - this._commandBufferEncoder.finishEncodingCommand(); - return true; - } - - public override setIntArray4(uniform: WebGLUniformLocation, array: Int32Array): boolean { - if (!uniform) { - return false; - } - - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETINTARRAY4); - this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); - this._commandBufferEncoder.encodeCommandArgAsInt32s(array); - this._commandBufferEncoder.finishEncodingCommand(); - return true; - } - - public setFloatArray(uniform: WebGLUniformLocation, array: Float32Array): boolean { - if (!uniform) { - return false; - } - - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETFLOATARRAY); - this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); - this._commandBufferEncoder.encodeCommandArgAsFloat32s(array); - this._commandBufferEncoder.finishEncodingCommand(); - return true; - } - - public setFloatArray2(uniform: WebGLUniformLocation, array: Float32Array): boolean { - if (!uniform) { - return false; - } - - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETFLOATARRAY2); - this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); - this._commandBufferEncoder.encodeCommandArgAsFloat32s(array); - this._commandBufferEncoder.finishEncodingCommand(); - return true; - } - - public setFloatArray3(uniform: WebGLUniformLocation, array: Float32Array): boolean { - if (!uniform) { - return false; - } - - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETFLOATARRAY3); - this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); - this._commandBufferEncoder.encodeCommandArgAsFloat32s(array); - this._commandBufferEncoder.finishEncodingCommand(); - return true; - } - - public setFloatArray4(uniform: WebGLUniformLocation, array: Float32Array): boolean { - if (!uniform) { - return false; - } - - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETFLOATARRAY4); - this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); - this._commandBufferEncoder.encodeCommandArgAsFloat32s(array); - this._commandBufferEncoder.finishEncodingCommand(); - return true; - } - - public override setArray(uniform: WebGLUniformLocation, array: number[]): boolean { - if (!uniform) { - return false; - } - - return this.setFloatArray(uniform, new Float32Array(array)); - } - - public override setArray2(uniform: WebGLUniformLocation, array: number[]): boolean { - if (!uniform) { - return false; - } - - return this.setFloatArray2(uniform, new Float32Array(array)); - } - - public override setArray3(uniform: WebGLUniformLocation, array: number[]): boolean { - if (!uniform) { - return false; - } - - return this.setFloatArray3(uniform, new Float32Array(array)); - } - - public override setArray4(uniform: WebGLUniformLocation, array: number[]): boolean { - if (!uniform) { - return false; - } - - return this.setFloatArray4(uniform, new Float32Array(array)); - } - - public override setMatrices(uniform: WebGLUniformLocation, matrices: DeepImmutable): boolean { - if (!uniform) { - return false; - } - - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETMATRICES); - this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); - this._commandBufferEncoder.encodeCommandArgAsFloat32s(matrices); - this._commandBufferEncoder.finishEncodingCommand(); - - return true; - } - - public override setMatrix3x3(uniform: WebGLUniformLocation, matrix: Float32Array): boolean { - if (!uniform) { - return false; - } - - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETMATRIX3X3); - this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); - this._commandBufferEncoder.encodeCommandArgAsFloat32s(matrix); - this._commandBufferEncoder.finishEncodingCommand(); - return true; - } - - public override setMatrix2x2(uniform: WebGLUniformLocation, matrix: Float32Array): boolean { - if (!uniform) { - return false; - } - - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETMATRIX2X2); - this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); - this._commandBufferEncoder.encodeCommandArgAsFloat32s(matrix); - this._commandBufferEncoder.finishEncodingCommand(); - return true; - } - - public override setFloat(uniform: WebGLUniformLocation, value: number): boolean { - if (!uniform) { - return false; - } - - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETFLOAT); - this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); - this._commandBufferEncoder.encodeCommandArgAsFloat32(value); - this._commandBufferEncoder.finishEncodingCommand(); - return true; - } - - public override setFloat2(uniform: WebGLUniformLocation, x: number, y: number): boolean { - if (!uniform) { - return false; - } - - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETFLOAT2); - this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); - this._commandBufferEncoder.encodeCommandArgAsFloat32(x); - this._commandBufferEncoder.encodeCommandArgAsFloat32(y); - this._commandBufferEncoder.finishEncodingCommand(); - return true; - } - - public override setFloat3(uniform: WebGLUniformLocation, x: number, y: number, z: number): boolean { - if (!uniform) { - return false; - } - - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETFLOAT3); - this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); - this._commandBufferEncoder.encodeCommandArgAsFloat32(x); - this._commandBufferEncoder.encodeCommandArgAsFloat32(y); - this._commandBufferEncoder.encodeCommandArgAsFloat32(z); - this._commandBufferEncoder.finishEncodingCommand(); - return true; - } - - public override setFloat4(uniform: WebGLUniformLocation, x: number, y: number, z: number, w: number): boolean { - if (!uniform) { - return false; - } - - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETFLOAT4); - this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); - this._commandBufferEncoder.encodeCommandArgAsFloat32(x); - this._commandBufferEncoder.encodeCommandArgAsFloat32(y); - this._commandBufferEncoder.encodeCommandArgAsFloat32(z); - this._commandBufferEncoder.encodeCommandArgAsFloat32(w); - this._commandBufferEncoder.finishEncodingCommand(); - return true; - } - - public setColor3(uniform: WebGLUniformLocation, color3: IColor3Like): boolean { - if (!uniform) { - return false; - } - - this.setFloat3(uniform, color3.r, color3.g, color3.b); - return true; - } - - public setColor4(uniform: WebGLUniformLocation, color3: IColor3Like, alpha: number): boolean { - if (!uniform) { - return false; - } - - this.setFloat4(uniform, color3.r, color3.g, color3.b, alpha); - return true; - } - - public override wipeCaches(bruteForce?: boolean): void { - if (this.preventCacheWipeBetweenFrames) { - return; - } - this.resetTextureCache(); - this._currentEffect = null; - - if (bruteForce) { - this._currentProgram = null; - - this._stencilStateComposer.reset(); - this._depthCullingState.reset(); - this._alphaState.reset(); - } - - this._cachedVertexBuffers = null; - this._cachedIndexBuffer = null; - this._cachedEffectForVertexBuffers = null; - } - - protected override _createTexture(): WebGLTexture { - return this._engine.createTexture(); - } - - protected override _deleteTexture(texture: Nullable): void { - if (texture) { - this._engine.deleteTexture(texture.underlyingResource as NativeTexture); - } - } - - /** - * Update the content of a dynamic texture - * @param texture defines the texture to update - * @param canvas defines the canvas containing the source - * @param invertY defines if data must be stored with Y axis inverted - * @param premulAlpha defines if alpha is stored as premultiplied - * @param format defines the format of the data - */ - public override updateDynamicTexture(texture: Nullable, canvas: any, invertY: boolean, premulAlpha: boolean = false, format?: number): void { - if (premulAlpha === void 0) { - premulAlpha = false; - } - - if (!!texture && !!texture._hardwareTexture) { - const destination = texture._hardwareTexture.underlyingResource; - const context = canvas.getContext(); - // flush need to happen before getCanvasTexture: flush will create the render target synchronously (if it's not been created before) - context.flush(); - const source = canvas.getCanvasTexture(); - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_COPYTEXTURE); - this._commandBufferEncoder.encodeCommandArgAsNativeData(source as NativeData); - this._commandBufferEncoder.encodeCommandArgAsNativeData(destination as NativeData); - this._commandBufferEncoder.finishEncodingCommand(); - texture.isReady = true; - } - } - - public override createDynamicTexture(width: number, height: number, generateMipMaps: boolean, samplingMode: number): InternalTexture { - // it's not possible to create 0x0 texture sized. Many bgfx methods assume texture size is at least 1x1(best case). - // Worst case is getting a crash/assert. - width = Math.max(width, 1); - height = Math.max(height, 1); - return this.createRawTexture(new Uint8Array(width * height * 4), width, height, Constants.TEXTUREFORMAT_RGBA, false, false, samplingMode); - } - - public override createVideoElement(constraints: MediaTrackConstraints): any { - // create native object depending on stream. Only NativeCamera is supported for now. - if (this._camera) { - return this._camera.createVideo(constraints); - } - return null; - } - - public override updateVideoTexture(texture: Nullable, video: HTMLVideoElement, invertY: boolean): void { - if (texture && texture._hardwareTexture && this._camera) { - const webGLTexture = texture._hardwareTexture.underlyingResource; - this._camera.updateVideoTexture(webGLTexture, video, invertY); - } - } - - public override createRawTexture( - data: Nullable, - width: number, - height: number, - format: number, - generateMipMaps: boolean, - invertY: boolean, - samplingMode: number, - compression: Nullable = null, - type: number = Constants.TEXTURETYPE_UNSIGNED_BYTE, - creationFlags: number = 0, - useSRGBBuffer: boolean = false - ): InternalTexture { - const texture = new InternalTexture(this, InternalTextureSource.Raw); - - texture.format = format; - texture.generateMipMaps = generateMipMaps; - texture.samplingMode = samplingMode; - texture.invertY = invertY; - texture.baseWidth = width; - texture.baseHeight = height; - texture.width = texture.baseWidth; - texture.height = texture.baseHeight; - texture._compression = compression; - texture.type = type; - texture._useSRGBBuffer = this._getUseSRGBBuffer(useSRGBBuffer, !generateMipMaps); - - this.updateRawTexture(texture, data, format, invertY, compression, type, texture._useSRGBBuffer); - - if (texture._hardwareTexture) { - const webGLTexture = texture._hardwareTexture.underlyingResource; - const filter = getNativeSamplingMode(samplingMode); - this._setTextureSampling(webGLTexture, filter); - } - - this._internalTexturesCache.push(texture); - return texture; - } - - public override createRawTexture2DArray( - data: Nullable, - width: number, - height: number, - depth: number, - format: number, - generateMipMaps: boolean, - invertY: boolean, - samplingMode: number, - compression: Nullable = null, - textureType = Constants.TEXTURETYPE_UNSIGNED_BYTE - ): InternalTexture { - const texture = new InternalTexture(this, InternalTextureSource.Raw2DArray); - - texture.baseWidth = width; - texture.baseHeight = height; - texture.baseDepth = depth; - texture.width = width; - texture.height = height; - texture.depth = depth; - texture.format = format; - texture.type = textureType; - texture.generateMipMaps = generateMipMaps; - texture.samplingMode = samplingMode; - texture.is2DArray = true; - - if (texture._hardwareTexture) { - const nativeTexture = texture._hardwareTexture.underlyingResource; - this._engine.loadRawTexture2DArray(nativeTexture, data, width, height, depth, getNativeTextureFormat(format, textureType), generateMipMaps, invertY); - - const filter = getNativeSamplingMode(samplingMode); - this._setTextureSampling(nativeTexture, filter); - } - - texture.isReady = true; - - this._internalTexturesCache.push(texture); - return texture; - } - - public override updateRawTexture( - texture: Nullable, - bufferView: Nullable, - format: number, - invertY: boolean, - compression: Nullable = null, - type: number = Constants.TEXTURETYPE_UNSIGNED_BYTE, - useSRGBBuffer: boolean = false - ): void { - if (!texture) { - return; - } - - if (bufferView && texture._hardwareTexture) { - const underlyingResource = texture._hardwareTexture.underlyingResource; - this._engine.loadRawTexture( - underlyingResource, - bufferView, - texture.width, - texture.height, - getNativeTextureFormat(format, type), - texture.generateMipMaps, - texture.invertY - ); - } - - texture.isReady = true; - } - - // TODO: Refactor to share more logic with babylon.engine.ts version. - /** - * Usually called from Texture.ts. - * Passed information to create a NativeTexture - * @param url defines a value which contains one of the following: - * * A conventional http URL, e.g. 'http://...' or 'file://...' - * * A base64 string of in-line texture data, e.g. 'data:image/jpg;base64,/...' - * * An indicator that data being passed using the buffer parameter, e.g. 'data:mytexture.jpg' - * @param noMipmap defines a boolean indicating that no mipmaps shall be generated. Ignored for compressed textures. They must be in the file - * @param invertY when true, image is flipped when loaded. You probably want true. Certain compressed textures may invert this if their default is inverted (eg. ktx) - * @param scene needed for loading to the correct scene - * @param samplingMode mode with should be used sample / access the texture (Default: Texture.TRILINEAR_SAMPLINGMODE) - * @param onLoad optional callback to be called upon successful completion - * @param onError optional callback to be called upon failure - * @param buffer a source of a file previously fetched as either a base64 string, an ArrayBuffer (compressed or image format), HTMLImageElement (image format), or a Blob - * @param fallback an internal argument in case the function must be called again, due to etc1 not having alpha capabilities - * @param format internal format. Default: RGB when extension is '.jpg' else RGBA. Ignored for compressed textures - * @param forcedExtension defines the extension to use to pick the right loader - * @param mimeType defines an optional mime type - * @param loaderOptions options to be passed to the loader - * @param creationFlags specific flags to use when creating the texture (Constants.TEXTURE_CREATIONFLAG_STORAGE for storage textures, for eg) - * @param useSRGBBuffer defines if the texture must be loaded in a sRGB GPU buffer (if supported by the GPU). - * @returns a InternalTexture for assignment back into BABYLON.Texture - */ - public override createTexture( - url: Nullable, - noMipmap: boolean, - invertY: boolean, - scene: Nullable, - samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE, - onLoad: Nullable<(texture: InternalTexture) => void> = null, - onError: Nullable<(message: string, exception: any) => void> = null, - buffer: Nullable = null, - fallback: Nullable = null, - format: Nullable = null, - forcedExtension: Nullable = null, - mimeType?: string, - loaderOptions?: any, - creationFlags?: number, - useSRGBBuffer = false - ): InternalTexture { - url = url || ""; - const fromData = url.substring(0, 5) === "data:"; - //const fromBlob = url.substring(0, 5) === "blob:"; - const isBase64 = fromData && url.indexOf(";base64,") !== -1; - - const texture = fallback ? fallback : new InternalTexture(this, InternalTextureSource.Url); - - const originalUrl = url; - if (this._transformTextureUrl && !isBase64 && !fallback && !buffer) { - url = this._transformTextureUrl(url); - } - - // establish the file extension, if possible - const lastDot = url.lastIndexOf("."); - const extension = forcedExtension ? forcedExtension : lastDot > -1 ? url.substring(lastDot).toLowerCase() : ""; - - // some formats are already supported by bimg, no need to try to load them with JS - // leaving TextureLoader extension check for future use - let loaderPromise = null; - if (extension.endsWith(".basis") || extension.endsWith(".ktx") || extension.endsWith(".ktx2") || mimeType === "image/ktx" || mimeType === "image/ktx2") { - loaderPromise = _GetCompatibleTextureLoader(extension); - } - - if (scene) { - scene.addPendingData(texture); - } - texture.url = url; - texture.generateMipMaps = !noMipmap; - texture.samplingMode = samplingMode; - texture.invertY = invertY; - texture._useSRGBBuffer = this._getUseSRGBBuffer(useSRGBBuffer, noMipmap); - - if (!this.doNotHandleContextLost) { - // Keep a link to the buffer only if we plan to handle context lost - texture._buffer = buffer; - } - - let onLoadObserver: Nullable> = null; - if (onLoad && !fallback) { - onLoadObserver = texture.onLoadedObservable.add(onLoad); - } - - if (!fallback) { - this._internalTexturesCache.push(texture); - } - - const onInternalError = (message?: string, exception?: any) => { - if (scene) { - scene.removePendingData(texture); - } - - if (url === originalUrl) { - if (onLoadObserver) { - texture.onLoadedObservable.remove(onLoadObserver); - } - - if (EngineStore.UseFallbackTexture) { - this.createTexture(EngineStore.FallbackTexture, noMipmap, texture.invertY, scene, samplingMode, null, onError, buffer, texture); - } - - if (onError) { - onError((message || "Unknown error") + (EngineStore.UseFallbackTexture ? " - Fallback texture was used" : ""), exception); - } - } else { - // fall back to the original url if the transformed url fails to load - Logger.Warn(`Failed to load ${url}, falling back to ${originalUrl}`); - this.createTexture(originalUrl, noMipmap, texture.invertY, scene, samplingMode, onLoad, onError, buffer, texture, format, forcedExtension, mimeType, loaderOptions); - } - }; - - // processing for non-image formats - if (loaderPromise) { - throw new Error("Loading textures from IInternalTextureLoader not yet implemented."); - } else { - const onload = (data: ArrayBufferView) => { - if (!texture._hardwareTexture) { - if (scene) { - scene.removePendingData(texture); - } - - return; - } - - const underlyingResource = texture._hardwareTexture.underlyingResource; - - this._engine.loadTexture( - underlyingResource, - data, - !noMipmap, - invertY, - texture._useSRGBBuffer, - () => { - texture.baseWidth = this._engine.getTextureWidth(underlyingResource); - texture.baseHeight = this._engine.getTextureHeight(underlyingResource); - texture.width = texture.baseWidth; - texture.height = texture.baseHeight; - texture.isReady = true; - - const filter = getNativeSamplingMode(samplingMode); - this._setTextureSampling(underlyingResource, filter); - - if (scene) { - scene.removePendingData(texture); - } - - texture.onLoadedObservable.notifyObservers(texture); - texture.onLoadedObservable.clear(); - }, - () => { - throw new Error("Could not load a native texture."); - } - ); - }; - - if (fromData && buffer) { - if (buffer instanceof ArrayBuffer) { - onload(new Uint8Array(buffer)); - } else if (ArrayBuffer.isView(buffer)) { - onload(buffer); - } else if (typeof buffer === "string") { - onload(new Uint8Array(Tools.DecodeBase64(buffer))); - } else { - throw new Error("Unsupported buffer type"); - } - } else { - if (isBase64) { - onload(new Uint8Array(Tools.DecodeBase64(url))); - } else { - this._loadFile( - url, - (data) => onload(new Uint8Array(data as ArrayBuffer)), - undefined, - undefined, - true, - (request?: IWebRequest, exception?: any) => { - onInternalError("Unable to load " + (request ? request.responseURL : url, exception)); - } - ); - } - } - } - - return texture; - } - - /** - * Wraps an external native texture in a Babylon texture. - * @param texture defines the external texture - * @param hasMipMaps defines whether the external texture has mip maps - * @param samplingMode defines the sampling mode for the external texture (default: Constants.TEXTURE_TRILINEAR_SAMPLINGMODE) - * @returns the babylon internal texture - */ - public wrapNativeTexture(texture: NativeTexture, hasMipMaps: boolean = false, samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE): InternalTexture { - const hardwareTexture = new NativeHardwareTexture(texture, this._engine); - const internalTexture = new InternalTexture(this, InternalTextureSource.Unknown, true); - internalTexture._hardwareTexture = hardwareTexture; - internalTexture.baseWidth = this._engine.getTextureWidth(texture); - internalTexture.baseHeight = this._engine.getTextureHeight(texture); - internalTexture.width = internalTexture.baseWidth; - internalTexture.height = internalTexture.baseHeight; - internalTexture.isReady = true; - internalTexture.useMipMaps = hasMipMaps; - this.updateTextureSamplingMode(samplingMode, internalTexture); - return internalTexture; - } - - // eslint-disable-next-line jsdoc/require-returns-check - /** - * Wraps an external web gl texture in a Babylon texture. - * @returns the babylon internal texture - */ - public override wrapWebGLTexture(): InternalTexture { - throw new Error("wrapWebGLTexture is not supported, use wrapNativeTexture instead."); - } - - public override _createDepthStencilTexture(size: TextureSize, options: DepthTextureCreationOptions, rtWrapper: RenderTargetWrapper): InternalTexture { - // TODO: handle other options? - const generateStencil = options.generateStencil || false; - const samples = options.samples || 1; - - const nativeRTWrapper = rtWrapper as NativeRenderTargetWrapper; - const texture = new InternalTexture(this, InternalTextureSource.DepthStencil); - - const width = (<{ width: number; height: number; layers?: number }>size).width ?? size; - const height = (<{ width: number; height: number; layers?: number }>size).height ?? size; - - const framebuffer = this._engine.createFrameBuffer(texture._hardwareTexture!.underlyingResource, width, height, generateStencil, true, samples); - nativeRTWrapper._framebufferDepthStencil = framebuffer; - return texture; - } - - /** - * @internal - */ - public _releaseFramebufferObjects(framebuffer: Nullable): void { - if (framebuffer) { - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_DELETEFRAMEBUFFER); - this._commandBufferEncoder.encodeCommandArgAsNativeData(framebuffer); - this._commandBufferEncoder.finishEncodingCommand(); - } - } - - /** - * @internal Engine abstraction for loading and creating an image bitmap from a given source string. - * @param imageSource source to load the image from. - * @param _options An object that sets options for the image's extraction. - * @returns ImageBitmap - */ - public override async _createImageBitmapFromSource(imageSource: string, _options?: ImageBitmapOptions): Promise { - const promise = new Promise((resolve, reject) => { - const image = this.createCanvasImage(); - image.onload = () => { - try { - const imageBitmap = this._engine.createImageBitmap(image); - resolve(imageBitmap); - } catch (error) { - // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors - reject(`Error loading image ${image.src} with exception: ${error}`); - } - }; - image.onerror = (error) => { - // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors - reject(`Error loading image ${image.src} with exception: ${error}`); - }; - - image.src = imageSource; - }); - - return await promise; - } - - /** - * Engine abstraction for createImageBitmap - * @param image source for image - * @param options An object that sets options for the image's extraction. - * @returns ImageBitmap - */ - public override async createImageBitmap(image: ImageBitmapSource, options?: ImageBitmapOptions): Promise { - // Back-compat: Because of the previous Blob hack, this could be an array of BlobParts. - if (Array.isArray(image)) { - const arr = >image; - if (arr.length) { - return this._engine.createImageBitmap(arr[0]); - } - } - - if (image instanceof Blob) { - const data = await image.arrayBuffer(); - return this._engine.createImageBitmap(data); - } - - throw new Error("Unsupported data for createImageBitmap."); - } - - /** - * Resize an image and returns the image data as an uint8array - * @param image image to resize - * @param bufferWidth destination buffer width - * @param bufferHeight destination buffer height - * @returns an uint8array containing RGBA values of bufferWidth * bufferHeight size - */ - public override resizeImageBitmap(image: ImageBitmap, bufferWidth: number, bufferHeight: number): Uint8Array { - return this._engine.resizeImageBitmap(image, bufferWidth, bufferHeight); - } - - /** - * Creates a cube texture - * @param rootUrl defines the url where the files to load is located - * @param scene defines the current scene - * @param files defines the list of files to load (1 per face) - * @param noMipmap defines a boolean indicating that no mipmaps shall be generated (false by default) - * @param onLoad defines an optional callback raised when the texture is loaded - * @param onError defines an optional callback raised if there is an issue to load the texture - * @param format defines the format of the data - * @param forcedExtension defines the extension to use to pick the right loader - * @param createPolynomials if a polynomial sphere should be created for the cube texture - * @param lodScale defines the scale applied to environment texture. This manages the range of LOD level used for IBL according to the roughness - * @param lodOffset defines the offset applied to environment texture. This manages first LOD level used for IBL according to the roughness - * @param fallback defines texture to use while falling back when (compressed) texture file not found. - * @param loaderOptions options to be passed to the loader - * @param useSRGBBuffer defines if the texture must be loaded in a sRGB GPU buffer (if supported by the GPU). - * @param buffer defines the data buffer to load instead of loading the rootUrl - * @returns the cube texture as an InternalTexture - */ - public override createCubeTexture( - rootUrl: string, - scene: Nullable, - files: Nullable, - noMipmap?: boolean, - onLoad: Nullable<(data?: any) => void> = null, - onError: Nullable<(message?: string, exception?: any) => void> = null, - format?: number, - forcedExtension: any = null, - createPolynomials = false, - lodScale: number = 0, - lodOffset: number = 0, - fallback: Nullable = null, - loaderOptions?: any, - useSRGBBuffer = false, - buffer: Nullable = null - ): InternalTexture { - const texture = fallback ? fallback : new InternalTexture(this, InternalTextureSource.Cube); - texture.isCube = true; - texture.url = rootUrl; - texture.generateMipMaps = !noMipmap; - texture._lodGenerationScale = lodScale; - texture._lodGenerationOffset = lodOffset; - texture._useSRGBBuffer = this._getUseSRGBBuffer(useSRGBBuffer, !!noMipmap); - - if (!this._doNotHandleContextLost) { - texture._extension = forcedExtension; - texture._files = files; - texture._buffer = buffer; - } - - const lastDot = rootUrl.lastIndexOf("."); - const extension = forcedExtension ? forcedExtension : lastDot > -1 ? rootUrl.substring(lastDot).toLowerCase() : ""; - - // TODO: use texture loader to load env files? - if (extension === ".env") { - const onloaddata = (data: ArrayBufferView) => { - const info = GetEnvInfo(data)!; - texture.width = info.width; - texture.height = info.width; - - UploadEnvSpherical(texture, info); - - const specularInfo = info.specular; - if (!specularInfo) { - throw new Error(`Nothing else parsed so far`); - } - - texture._lodGenerationScale = specularInfo.lodGenerationScale; - const imageData = CreateRadianceImageDataArrayBufferViews(data, info); - - texture.format = Constants.TEXTUREFORMAT_RGBA; - texture.type = Constants.TEXTURETYPE_UNSIGNED_BYTE; - texture.generateMipMaps = true; - texture.getEngine().updateTextureSamplingMode(Texture.TRILINEAR_SAMPLINGMODE, texture); - texture._isRGBD = true; - texture.invertY = true; - - this._engine.loadCubeTextureWithMips( - texture._hardwareTexture!.underlyingResource, - imageData, - false, - texture._useSRGBBuffer, - () => { - texture.isReady = true; - if (onLoad) { - onLoad(); - } - }, - () => { - throw new Error("Could not load a native cube texture."); - } - ); - }; - - if (buffer) { - onloaddata(buffer); - } else if (files && files.length === 6) { - throw new Error(`Multi-file loading not allowed on env files.`); - } else { - const onInternalError = (request?: IWebRequest, exception?: any) => { - if (onError && request) { - onError(request.status + " " + request.statusText, exception); - } - }; - - this._loadFile( - rootUrl, - (data) => { - onloaddata(new Uint8Array(data as ArrayBuffer, 0, (data as ArrayBuffer).byteLength)); - }, - undefined, - undefined, - true, - onInternalError - ); - } - } else { - if (!files || files.length !== 6) { - throw new Error("Cannot load cubemap because 6 files were not defined"); - } - - // Reorder from [+X, +Y, +Z, -X, -Y, -Z] to [+X, -X, +Y, -Y, +Z, -Z]. - const reorderedFiles = [files[0], files[3], files[1], files[4], files[2], files[5]]; - // eslint-disable-next-line github/no-then - Promise.all(reorderedFiles.map(async (file) => await this._loadFileAsync(file, undefined, true).then((data) => new Uint8Array(data, 0, data.byteLength)))) - // eslint-disable-next-line github/no-then - .then(async (data) => { - return await new Promise((resolve, reject) => { - this._engine.loadCubeTexture(texture._hardwareTexture!.underlyingResource, data, !noMipmap, true, texture._useSRGBBuffer, resolve, reject); - }); - }) - // eslint-disable-next-line github/no-then - .then( - () => { - texture.isReady = true; - if (onLoad) { - onLoad(); - } - }, - (error) => { - if (onError) { - onError(`Failed to load cubemap: ${error.message}`, error); - } - } - ); - } - - this._internalTexturesCache.push(texture); - - return texture; - } - - /** @internal */ - public override _createHardwareTexture(): IHardwareTextureWrapper { - return new NativeHardwareTexture(this._createTexture() as NativeTexture, this._engine); - } - - /** @internal */ - public override _createHardwareRenderTargetWrapper(isMulti: boolean, isCube: boolean, size: TextureSize): RenderTargetWrapper { - const rtWrapper = new NativeRenderTargetWrapper(isMulti, isCube, size, this); - this._renderTargetWrapperCache.push(rtWrapper); - return rtWrapper; - } - - /** @internal */ - public override _createInternalTexture( - size: TextureSize, - options: boolean | InternalTextureCreationOptions, - _delayGPUTextureCreation = true, - source = InternalTextureSource.Unknown - ): InternalTexture { - let generateMipMaps = false; - let type = Constants.TEXTURETYPE_UNSIGNED_BYTE; - let samplingMode = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE; - let format = Constants.TEXTUREFORMAT_RGBA; - let useSRGBBuffer = false; - let samples = 1; - let label: string | undefined; - if (options !== undefined && typeof options === "object") { - generateMipMaps = !!options.generateMipMaps; - type = options.type === undefined ? Constants.TEXTURETYPE_UNSIGNED_BYTE : options.type; - samplingMode = options.samplingMode === undefined ? Constants.TEXTURE_TRILINEAR_SAMPLINGMODE : options.samplingMode; - format = options.format === undefined ? Constants.TEXTUREFORMAT_RGBA : options.format; - useSRGBBuffer = options.useSRGBBuffer === undefined ? false : options.useSRGBBuffer; - samples = options.samples ?? 1; - label = options.label; - } else { - generateMipMaps = !!options; - } - - useSRGBBuffer = this._getUseSRGBBuffer(useSRGBBuffer, !generateMipMaps); - - if (type === Constants.TEXTURETYPE_FLOAT && !this._caps.textureFloatLinearFiltering) { - // if floating point linear (gl.FLOAT) then force to NEAREST_SAMPLINGMODE - samplingMode = Constants.TEXTURE_NEAREST_SAMPLINGMODE; - } else if (type === Constants.TEXTURETYPE_HALF_FLOAT && !this._caps.textureHalfFloatLinearFiltering) { - // if floating point linear (HALF_FLOAT) then force to NEAREST_SAMPLINGMODE - samplingMode = Constants.TEXTURE_NEAREST_SAMPLINGMODE; - } - if (type === Constants.TEXTURETYPE_FLOAT && !this._caps.textureFloat) { - type = Constants.TEXTURETYPE_UNSIGNED_BYTE; - Logger.Warn("Float textures are not supported. Type forced to TEXTURETYPE_UNSIGNED_BYTE"); - } - - const texture = new InternalTexture(this, source); - const width = (<{ width: number; height: number; layers?: number }>size).width ?? size; - const height = (<{ width: number; height: number; layers?: number }>size).height ?? size; - - const layers = (<{ width: number; height: number; layers?: number }>size).layers || 0; - if (layers !== 0) { - throw new Error("Texture layers are not supported in Babylon Native"); - } - - const nativeTexture = texture._hardwareTexture!.underlyingResource; - const nativeTextureFormat = getNativeTextureFormat(format, type); - // REVIEW: We are always setting the renderTarget flag as we don't know whether the texture will be used as a render target. - this._engine.initializeTexture(nativeTexture, width, height, generateMipMaps, nativeTextureFormat, true, useSRGBBuffer, samples); - this._setTextureSampling(nativeTexture, getNativeSamplingMode(samplingMode)); - - texture._useSRGBBuffer = useSRGBBuffer; - texture.baseWidth = width; - texture.baseHeight = height; - texture.width = width; - texture.height = height; - texture.depth = layers; - texture.isReady = true; - texture.samples = samples; - texture.generateMipMaps = generateMipMaps; - texture.samplingMode = samplingMode; - texture.type = type; - texture.format = format; - texture.label = label; - - this._internalTexturesCache.push(texture); - - return texture; - } - - public override createRenderTargetTexture( - size: number | { width: number; height: number; depth: number }, - options: boolean | RenderTargetCreationOptions - ): RenderTargetWrapper { - const rtWrapper = this._createHardwareRenderTargetWrapper(false, false, size) as NativeRenderTargetWrapper; - - let generateDepthBuffer = true; - let generateStencilBuffer = false; - let noColorAttachment = false; - let colorAttachment: InternalTexture | undefined = undefined; - let samples = 1; - if (options !== undefined && typeof options === "object") { - generateDepthBuffer = options.generateDepthBuffer ?? true; - generateStencilBuffer = !!options.generateStencilBuffer; - noColorAttachment = !!options.noColorAttachment; - colorAttachment = options.colorAttachment; - samples = options.samples ?? 1; - } - - const texture = colorAttachment || (noColorAttachment ? null : this._createInternalTexture(size, options, true, InternalTextureSource.RenderTarget)); - const width = (<{ width: number; height: number; layers?: number }>size).width ?? size; - const height = (<{ width: number; height: number; layers?: number }>size).height ?? size; - - const framebuffer = this._engine.createFrameBuffer( - texture ? texture._hardwareTexture!.underlyingResource : null, - width, - height, - generateStencilBuffer, - generateDepthBuffer, - samples - ); - - rtWrapper._framebuffer = framebuffer; - rtWrapper._generateDepthBuffer = generateDepthBuffer; - rtWrapper._generateStencilBuffer = generateStencilBuffer; - rtWrapper._samples = samples; - - rtWrapper.setTextures(texture); - - return rtWrapper; - } - - public override updateRenderTargetTextureSampleCount(rtWrapper: RenderTargetWrapper, samples: number): number { - Logger.Warn("Updating render target sample count is not currently supported"); - return rtWrapper.samples; - } - - public override updateTextureSamplingMode(samplingMode: number, texture: InternalTexture): void { - if (texture._hardwareTexture) { - const filter = getNativeSamplingMode(samplingMode); - this._setTextureSampling(texture._hardwareTexture.underlyingResource, filter); - } - - texture.samplingMode = samplingMode; - } - - public override bindFramebuffer(texture: RenderTargetWrapper, faceIndex?: number, requiredWidth?: number, requiredHeight?: number, forceFullscreenViewport?: boolean): void { - const nativeRTWrapper = texture as NativeRenderTargetWrapper; - - if (this._currentRenderTarget) { - this.unBindFramebuffer(this._currentRenderTarget); - } - - this._currentRenderTarget = texture; - - if (faceIndex) { - throw new Error("Cuboid frame buffers are not yet supported in NativeEngine."); - } - - if (requiredWidth || requiredHeight) { - throw new Error("Required width/height for frame buffers not yet supported in NativeEngine."); - } - - if (nativeRTWrapper._framebufferDepthStencil) { - this._bindUnboundFramebuffer(nativeRTWrapper._framebufferDepthStencil); - } else { - this._bindUnboundFramebuffer(nativeRTWrapper._framebuffer); - } - } - - public override unBindFramebuffer(texture: RenderTargetWrapper, disableGenerateMipMaps = false, onBeforeUnbind?: () => void): void { - // NOTE: Disabling mipmap generation is not yet supported in NativeEngine. - - this._currentRenderTarget = null; - - if (onBeforeUnbind) { - onBeforeUnbind(); - } - - this._bindUnboundFramebuffer(null); - } - - public override createDynamicVertexBuffer(data: DataArray): DataBuffer { - return this.createVertexBuffer(data, true); - } - - public override updateDynamicIndexBuffer(indexBuffer: DataBuffer, indices: IndicesArray, offset: number = 0): void { - const buffer = indexBuffer as NativeDataBuffer; - const data = this._normalizeIndexData(indices); - buffer.is32Bits = data.BYTES_PER_ELEMENT === 4; - this._engine.updateDynamicIndexBuffer(buffer.nativeIndexBuffer!, data.buffer, data.byteOffset, data.byteLength, offset); - } - - public override updateDynamicVertexBuffer(vertexBuffer: DataBuffer, data: DataArray, byteOffset = 0, byteLength?: number): void { - const buffer = vertexBuffer as NativeDataBuffer; - const dataView = data instanceof Array ? new Float32Array(data) : data instanceof ArrayBuffer ? new Uint8Array(data) : data; - const byteView = new Uint8Array(dataView.buffer, dataView.byteOffset, byteLength ?? dataView.byteLength); - this._engine.updateDynamicVertexBuffer(buffer.nativeVertexBuffer!, byteView.buffer, byteView.byteOffset, byteView.byteLength, byteOffset); - } - - // TODO: Refactor to share more logic with base Engine implementation. - /** - * @internal - */ - public override _setTexture(channel: number, texture: Nullable, isPartOfTextureArray = false, depthStencilTexture = false): boolean { - const uniform = this._boundUniforms[channel] as unknown as NativeUniform; - if (!uniform) { - return false; - } - - // Not ready? - if (!texture) { - if (this._boundTexturesCache[channel] != null) { - this._activeChannel = channel; - this._boundTexturesCache[channel] = null; - this._unsetNativeTexture(uniform); - } - return false; - } - - // Video - if ((texture).video) { - this._activeChannel = channel; - (texture).update(); - } else if (texture.delayLoadState === Constants.DELAYLOADSTATE_NOTLOADED) { - // Delay loading - texture.delayLoad(); - return false; - } - - let internalTexture: InternalTexture; - if (depthStencilTexture) { - internalTexture = (texture).depthStencilTexture!; - } else if (texture.isReady()) { - internalTexture = texture.getInternalTexture(); - } else if (texture.isCube) { - internalTexture = this.emptyCubeTexture; - } else if (texture.is3D) { - internalTexture = this.emptyTexture3D; - } else if (texture.is2DArray) { - internalTexture = this.emptyTexture2DArray; - } else { - internalTexture = this.emptyTexture; - } - - this._activeChannel = channel; - - if (!internalTexture || !internalTexture._hardwareTexture) { - return false; - } - - this._setTextureWrapMode( - internalTexture._hardwareTexture.underlyingResource, - getNativeAddressMode(texture.wrapU), - getNativeAddressMode(texture.wrapV), - getNativeAddressMode(texture.wrapR) - ); - this._updateAnisotropicLevel(texture); - - this._setNativeTexture(uniform, internalTexture._hardwareTexture.underlyingResource); - - return true; - } - - // filter is a NativeFilter.XXXX value. - private _setTextureSampling(texture: NativeTexture, filter: number) { - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETTEXTURESAMPLING); - this._commandBufferEncoder.encodeCommandArgAsNativeData(texture); - this._commandBufferEncoder.encodeCommandArgAsUInt32(filter); - this._commandBufferEncoder.finishEncodingCommand(); - } - - // addressModes are NativeAddressMode.XXXX values. - private _setTextureWrapMode(texture: NativeTexture, addressModeU: number, addressModeV: number, addressModeW: number) { - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETTEXTUREWRAPMODE); - this._commandBufferEncoder.encodeCommandArgAsNativeData(texture); - this._commandBufferEncoder.encodeCommandArgAsUInt32(addressModeU); - this._commandBufferEncoder.encodeCommandArgAsUInt32(addressModeV); - this._commandBufferEncoder.encodeCommandArgAsUInt32(addressModeW); - this._commandBufferEncoder.finishEncodingCommand(); - } - - private _setNativeTexture(uniform: NativeUniform, texture: NativeTexture) { - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETTEXTURE); - this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform); - this._commandBufferEncoder.encodeCommandArgAsNativeData(texture); - this._commandBufferEncoder.finishEncodingCommand(); - } - - private _unsetNativeTexture(uniform: NativeUniform) { - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_UNSETTEXTURE); - this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform); - this._commandBufferEncoder.finishEncodingCommand(); - } - - // TODO: Share more of this logic with the base implementation. - // TODO: Rename to match naming in base implementation once refactoring allows different parameters. - private _updateAnisotropicLevel(texture: BaseTexture) { - const internalTexture = texture.getInternalTexture(); - const value = texture.anisotropicFilteringLevel; - - if (!internalTexture || !internalTexture._hardwareTexture) { - return; - } - - if (internalTexture._cachedAnisotropicFilteringLevel !== value) { - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETTEXTUREANISOTROPICLEVEL); - this._commandBufferEncoder.encodeCommandArgAsNativeData(internalTexture._hardwareTexture.underlyingResource); - this._commandBufferEncoder.encodeCommandArgAsUInt32(value); - this._commandBufferEncoder.finishEncodingCommand(); - internalTexture._cachedAnisotropicFilteringLevel = value; - } - } - - /** - * @internal - */ - public override _bindTexture(channel: number, texture: Nullable): void { - const uniform = this._boundUniforms[channel] as unknown as NativeUniform; - if (!uniform) { - return; - } - - if (texture && texture._hardwareTexture) { - const underlyingResource = texture._hardwareTexture.underlyingResource; - this._setNativeTexture(uniform, underlyingResource); - } else { - this._unsetNativeTexture(uniform); - } - } - - /** - * Unbind all textures - */ - public override unbindAllTextures(): void { - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_DISCARDALLTEXTURES); - this._commandBufferEncoder.finishEncodingCommand(); - } - - protected override _deleteBuffer(buffer: NativeDataBuffer): void { - if (buffer.nativeIndexBuffer) { - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_DELETEINDEXBUFFER); - this._commandBufferEncoder.encodeCommandArgAsNativeData(buffer.nativeIndexBuffer); - this._commandBufferEncoder.finishEncodingCommand(); - delete buffer.nativeIndexBuffer; - } - - if (buffer.nativeVertexBuffer) { - this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_DELETEVERTEXBUFFER); - this._commandBufferEncoder.encodeCommandArgAsNativeData(buffer.nativeVertexBuffer); - this._commandBufferEncoder.finishEncodingCommand(); - delete buffer.nativeVertexBuffer; - } - } - - /** - * Create a canvas - * @param width width - * @param height height - * @returns ICanvas interface - */ - public override createCanvas(width: number, height: number): ICanvas { - if (!_native.Canvas) { - throw new Error("Native Canvas plugin not available."); - } - const canvas = new _native.Canvas(); - canvas.width = width; - canvas.height = height; - return canvas; - } - - /** - * Create an image to use with canvas - * @returns IImage interface - */ - public override createCanvasImage(): IImage { - if (!_native.Image) { - throw new Error("Native Canvas plugin not available."); - } - const image = new _native.Image(); - return image; - } - - /** - * Create a 2D path to use with canvas - * @returns IPath2D interface - * @param d SVG path string - */ - public override createCanvasPath2D(d?: string): IPath2D { - if (!_native.Path2D) { - throw new Error("Native Canvas plugin not available."); - } - const path2d = new _native.Path2D(d); - return path2d; - } - - /** - * Update a portion of an internal texture - * @param texture defines the texture to update - * @param imageData defines the data to store into the texture - * @param xOffset defines the x coordinates of the update rectangle - * @param yOffset defines the y coordinates of the update rectangle - * @param width defines the width of the update rectangle - * @param height defines the height of the update rectangle - * @param faceIndex defines the face index if texture is a cube (0 by default) - * @param lod defines the lod level to update (0 by default) - * @param generateMipMaps defines whether to generate mipmaps or not - */ - public override updateTextureData( - texture: InternalTexture, - imageData: ArrayBufferView, - xOffset: number, - yOffset: number, - width: number, - height: number, - faceIndex: number = 0, - lod: number = 0, - generateMipMaps = false - ): void { - throw new Error("updateTextureData not implemented."); - } - - /** - * @internal - */ - public override _uploadCompressedDataToTextureDirectly( - texture: InternalTexture, - internalFormat: number, - width: number, - height: number, - data: ArrayBufferView, - faceIndex: number = 0, - lod: number = 0 - ) { - throw new Error("_uploadCompressedDataToTextureDirectly not implemented."); - } - - /** - * @internal - */ - public override _uploadDataToTextureDirectly(texture: InternalTexture, imageData: ArrayBufferView, faceIndex: number = 0, lod: number = 0): void { - throw new Error("_uploadDataToTextureDirectly not implemented."); - } - - /** - * @internal - */ - public override _uploadArrayBufferViewToTexture(texture: InternalTexture, imageData: ArrayBufferView, faceIndex: number = 0, lod: number = 0): void { - throw new Error("_uploadArrayBufferViewToTexture not implemented."); - } - - /** - * @internal - */ - public override _uploadImageToTexture(texture: InternalTexture, image: HTMLImageElement, faceIndex: number = 0, lod: number = 0) { - throw new Error("_uploadArrayBufferViewToTexture not implemented."); - } - - public override getFontOffset(font: string): { ascent: number; height: number; descent: number } { - // TODO - const result = { ascent: 0, height: 0, descent: 0 }; - return result; - } - - /** - * No equivalent for native. Do nothing. - */ - public override flushFramebuffer(): void {} - - // eslint-disable-next-line @typescript-eslint/promise-function-async - public override _readTexturePixels( - texture: InternalTexture, - width: number, - height: number, - faceIndex?: number, - level?: number, - buffer?: Nullable, - _flushRenderer?: boolean, - _noDataConversion?: boolean, - x?: number, - y?: number - ): Promise { - if (faceIndex !== undefined && faceIndex !== -1) { - throw new Error(`Reading cubemap faces is not supported, but faceIndex is ${faceIndex}.`); - } - - return ( - this._engine - .readTexture( - texture._hardwareTexture?.underlyingResource, - level ?? 0, - x ?? 0, - y ?? 0, - width, - height, - buffer?.buffer ?? null, - buffer?.byteOffset ?? 0, - buffer?.byteLength ?? 0 - ) - // eslint-disable-next-line github/no-then - .then((rawBuffer) => { - if (!buffer) { - buffer = new Uint8Array(rawBuffer); - } - - return buffer; - }) - ); - } - - override startTimeQuery(): Nullable<_TimeToken> { - if (!this._gpuFrameTimeToken) { - this._gpuFrameTimeToken = new _TimeToken(); - } - - // Always return the same time token. For native, we don't need a start marker, we just query for native frame stats. - return this._gpuFrameTimeToken; - } - - override endTimeQuery(token: _TimeToken): int { - this._engine.populateFrameStats(this._frameStats); - return this._frameStats.gpuTimeNs; - } -} +// Apply the ThinNativeEngine mixins to the NativeEngine. +applyMixins(NativeEngine, [ThinNativeEngine]); diff --git a/packages/dev/core/src/Engines/nullEngine.ts b/packages/dev/core/src/Engines/nullEngine.ts index 0ac2e26d013..ea951145a6f 100644 --- a/packages/dev/core/src/Engines/nullEngine.ts +++ b/packages/dev/core/src/Engines/nullEngine.ts @@ -17,6 +17,9 @@ import { RenderTargetWrapper } from "./renderTargetWrapper"; import type { IStencilState } from "../States/IStencilState"; import { IsWrapper } from "../Materials/drawWrapper.functions"; +import "./AbstractEngine/abstractEngine.loadFile"; +import "./AbstractEngine/abstractEngine.textureLoaders"; + // eslint-disable-next-line @typescript-eslint/naming-convention declare const global: any; diff --git a/packages/dev/core/src/Engines/thinNativeEngine.ts b/packages/dev/core/src/Engines/thinNativeEngine.ts new file mode 100644 index 00000000000..d368d8c3b4a --- /dev/null +++ b/packages/dev/core/src/Engines/thinNativeEngine.ts @@ -0,0 +1,2613 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import type { Nullable, IndicesArray, DataArray, FloatArray, DeepImmutable, int } from "../types"; + +import type { VertexBuffer } from "../Buffers/buffer"; +import { InternalTexture, InternalTextureSource } from "../Materials/Textures/internalTexture"; +import type { BaseTexture } from "../Materials/Textures/baseTexture"; +import type { VideoTexture } from "../Materials/Textures/videoTexture"; +import type { RenderTargetTexture } from "../Materials/Textures/renderTargetTexture"; +import type { Effect } from "../Materials/effect"; +import { DataBuffer } from "../Buffers/dataBuffer"; +import type { Observer } from "../Misc/observable"; +import { Observable } from "../Misc/observable"; +import type { RenderTargetCreationOptions, TextureSize, DepthTextureCreationOptions, InternalTextureCreationOptions } from "../Materials/Textures/textureCreationOptions"; +import type { IPipelineContext } from "./IPipelineContext"; +import type { IColor3Like, IColor4Like, IViewportLike } from "../Maths/math.like"; +import { Logger } from "../Misc/logger"; +import { Constants } from "./constants"; +import { AbstractEngine, type ISceneLike } from "./abstractEngine"; +import { ThinEngine } from "./thinEngine"; +import type { IWebRequest } from "../Misc/interfaces/iWebRequest"; +import { EngineStore } from "./engineStore"; +import { ShaderCodeInliner } from "./Processors/shaderCodeInliner"; +import { NativeShaderProcessor } from "./Native/nativeShaderProcessors"; +import type { IMaterialContext } from "./IMaterialContext"; +import type { IDrawContext } from "./IDrawContext"; +import type { ICanvas, IImage, IPath2D } from "./ICanvas"; +import type { IStencilState } from "../States/IStencilState"; +import type { RenderTargetWrapper } from "./renderTargetWrapper"; +import type { NativeData } from "./Native/nativeDataStream"; +import { NativeDataStream } from "./Native/nativeDataStream"; +import type { + INative, + INativeCamera, + INativeEngine, + NativeFramebuffer, + NativeFrameStats, + NativeProgram, + NativeTexture, + NativeUniform, + NativeVertexArrayObject, +} from "./Native/nativeInterfaces"; +import { NativePipelineContext } from "./Native/nativePipelineContext"; +import { NativeRenderTargetWrapper } from "./Native/nativeRenderTargetWrapper"; +import { NativeHardwareTexture } from "./Native/nativeHardwareTexture"; +import type { IHardwareTextureWrapper } from "../Materials/Textures/hardwareTextureWrapper"; +import { + getNativeAlphaMode, + getNativeAttribType, + getNativeSamplingMode, + getNativeTextureFormat, + getNativeStencilDepthFail, + getNativeStencilDepthPass, + getNativeStencilFunc, + getNativeStencilOpFail, + getNativeAddressMode, +} from "./Native/nativeHelpers"; +import { checkNonFloatVertexBuffers } from "../Buffers/buffer.nonFloatVertexBuffers"; +import type { _IShaderProcessingContext } from "./Processors/shaderProcessingOptions"; +import { NativeShaderProcessingContext } from "./Native/nativeShaderProcessingContext"; +import type { ShaderLanguage } from "../Materials/shaderLanguage"; +import type { WebGLHardwareTexture } from "./WebGL/webGLHardwareTexture"; + +import "../Buffers/buffer.align"; +import { _TimeToken } from "../Instrumentation/timeToken"; +import { PerfCounter } from "../Misc/perfCounter"; +import { DecodeBase64UrlToBinary } from "../Misc/fileTools"; + +// REVIEW: add a flag to effect to prevent multiple compilations of the same shader. +declare module "../Materials/effect" { + /** internal */ + export interface Effect { + /** internal */ + _checkedNonFloatVertexBuffers?: boolean; + } +} + +declare const _native: INative; + +const onNativeObjectInitialized = new Observable(); +if (typeof self !== "undefined" && !Object.prototype.hasOwnProperty.call(self, "_native")) { + let __native: INative; + Object.defineProperty(self, "_native", { + get: () => __native, + set: (value: INative) => { + __native = value; + if (__native) { + onNativeObjectInitialized.notifyObservers(__native); + } + }, + }); +} + +/** + * Returns _native only after it has been defined by BabylonNative. + * @internal + */ +export async function AcquireNativeObjectAsync(): Promise { + return await new Promise((resolve) => { + if (typeof _native === "undefined") { + onNativeObjectInitialized.addOnce((nativeObject) => resolve(nativeObject)); + } else { + resolve(_native); + } + }); +} + +/** + * Registers a constructor on the _native object. See NativeXRFrame for an example. + * @internal + */ +export async function RegisterNativeTypeAsync(typeName: string, constructor: Type) { + ((await AcquireNativeObjectAsync()) as any)[typeName] = constructor; +} + +/** + * Container for accessors for natively-stored mesh data buffers. + */ +class NativeDataBuffer extends DataBuffer { + /** + * Accessor value used to identify/retrieve a natively-stored index buffer. + */ + public nativeIndexBuffer?: NativeData; + + /** + * Accessor value used to identify/retrieve a natively-stored vertex buffer. + */ + public nativeVertexBuffer?: NativeData; +} + +/** + * Options to create the Native engine + */ +export interface ThinNativeEngineOptions { + /** + * defines whether to adapt to the device's viewport characteristics (default: false) + */ + adaptToDeviceRatio?: boolean; +} + +/** @internal */ +class CommandBufferEncoder { + private readonly _commandStream: NativeDataStream; + private readonly _pending = new Array(); + private _isCommandBufferScopeActive = false; + + public constructor(private readonly _engine: INativeEngine) { + this._commandStream = ThinNativeEngine._createNativeDataStream(); + this._engine.setCommandDataStream(this._commandStream); + } + + public beginCommandScope() { + if (this._isCommandBufferScopeActive) { + throw new Error("Command scope already active."); + } + + this._isCommandBufferScopeActive = true; + } + + public endCommandScope() { + if (!this._isCommandBufferScopeActive) { + throw new Error("Command scope is not active."); + } + + this._isCommandBufferScopeActive = false; + this._submit(); + } + + public startEncodingCommand(command: NativeData) { + this._commandStream.writeNativeData(command); + } + + public encodeCommandArgAsUInt32(commandArg: number) { + this._commandStream.writeUint32(commandArg); + } + + public encodeCommandArgAsUInt32s(commandArg: Uint32Array) { + this._commandStream.writeUint32Array(commandArg); + } + + public encodeCommandArgAsInt32(commandArg: number) { + this._commandStream.writeInt32(commandArg); + } + + public encodeCommandArgAsInt32s(commandArg: Int32Array) { + this._commandStream.writeInt32Array(commandArg); + } + + public encodeCommandArgAsFloat32(commandArg: number) { + this._commandStream.writeFloat32(commandArg); + } + + public encodeCommandArgAsFloat32s(commandArg: DeepImmutable) { + this._commandStream.writeFloat32Array(commandArg); + } + + public encodeCommandArgAsNativeData(commandArg: NativeData) { + this._commandStream.writeNativeData(commandArg); + this._pending.push(commandArg); + } + + public finishEncodingCommand() { + if (!this._isCommandBufferScopeActive) { + this._submit(); + } + } + + private _submit() { + this._engine.submitCommands(); + this._pending.length = 0; + } +} + +const remappedAttributesNames: string[] = []; + +/** @internal */ +export class ThinNativeEngine extends ThinEngine { + // This must match the protocol version in NativeEngine.cpp + private static readonly PROTOCOL_VERSION = 9; + + /** @internal */ + public static _createNativeDataStream(): NativeDataStream { + return new NativeDataStream(); + } + + /////////// No assignment allowed in constructor /////////// + /////////// They should all be in _initializeNativeEngine /////////// + /////////// To ensure a correct sharing with NativeEngine /////////// + protected _engine: INativeEngine; + private _camera: Nullable; + private _commandBufferEncoder: CommandBufferEncoder; + private _frameStats: NativeFrameStats; + private _boundBuffersVertexArray: any; + private _currentDepthTest: number; + private _stencilTest: boolean; + private _stencilMask: number; + private _stencilFunc: number; + private _stencilFuncRef: number; + private _stencilFuncMask: number; + private _stencilOpStencilFail: number; + private _stencilOpDepthFail: number; + private _stencilOpStencilDepthPass: number; + private _zOffset: number; + private _zOffsetUnits: number; + private _depthWrite: boolean; + // warning for non supported fill mode has already been displayed + private _fillModeWarningDisplayed: boolean; + + public constructor(options: ThinNativeEngineOptions = {}) { + super(null, false, undefined, options.adaptToDeviceRatio); + this._initializeNativeEngine(options.adaptToDeviceRatio ?? false); + } + ////////////////////////////////////////////////////////////////////// + + /** + * Keeps as a separate function to use in NativeEngine + * @internal + */ + protected _initializeNativeEngine(adaptToDeviceRatio: boolean): void { + this._engine = new _native.Engine({ + version: AbstractEngine.Version, + nonFloatVertexBuffers: true, + }); + this._camera = _native.Camera ? new _native.Camera() : null; + this._commandBufferEncoder = new CommandBufferEncoder(this._engine); + this._frameStats = { gpuTimeNs: Number.NaN }; + this._boundBuffersVertexArray = null; + this._currentDepthTest = _native.Engine.DEPTH_TEST_LEQUAL; + this._stencilTest; + this._stencilMask = 255; + this._stencilFunc = Constants.ALWAYS; + this._stencilFuncRef = 0; + this._stencilFuncMask = 255; + this._stencilOpStencilFail = Constants.KEEP; + this._stencilOpDepthFail = Constants.KEEP; + this._stencilOpStencilDepthPass = Constants.REPLACE; + this._zOffset = 0; + this._zOffsetUnits = 0; + this._depthWrite = true; + // warning for non supported fill mode has already been displayed + this._fillModeWarningDisplayed = false; + + this._drawCalls = new PerfCounter(); + + if (_native.Engine.PROTOCOL_VERSION !== ThinNativeEngine.PROTOCOL_VERSION) { + throw new Error(`Protocol version mismatch: ${_native.Engine.PROTOCOL_VERSION} (Native) !== ${ThinNativeEngine.PROTOCOL_VERSION} (JS)`); + } + + if (this._engine.setDeviceLostCallback) { + this._engine.setDeviceLostCallback(() => { + this.onContextLostObservable.notifyObservers(this); + this._contextWasLost = true; + this._restoreEngineAfterContextLost(); + }); + } + + this._webGLVersion = 2; + this.disableUniformBuffers = true; + this._shaderPlatformName = "NATIVE"; + + // TODO: Initialize this more correctly based on the hardware capabilities. + // Init caps + + this._caps = { + maxTexturesImageUnits: 16, + maxVertexTextureImageUnits: 16, + maxCombinedTexturesImageUnits: 32, + maxTextureSize: _native.Engine.CAPS_LIMITS_MAX_TEXTURE_SIZE, + maxCubemapTextureSize: 512, + maxRenderTextureSize: 512, + maxVertexAttribs: 16, + maxVaryingVectors: 16, + maxDrawBuffers: 8, + maxFragmentUniformVectors: 16, + maxVertexUniformVectors: 16, + shaderFloatPrecision: 23, // TODO: is this correct? + standardDerivatives: true, + astc: null, + pvrtc: null, + etc1: null, + etc2: null, + bptc: null, + maxAnisotropy: 16, // TODO: Retrieve this smartly. Currently set to D3D11 maximum allowable value. + uintIndices: true, + fragmentDepthSupported: false, + highPrecisionShaderSupported: true, + colorBufferFloat: false, + blendFloat: false, + supportFloatTexturesResolve: false, + rg11b10ufColorRenderable: false, + textureFloat: true, + textureFloatLinearFiltering: true, + textureFloatRender: true, + textureHalfFloat: true, + textureHalfFloatLinearFiltering: true, + textureHalfFloatRender: true, + textureLOD: true, + texelFetch: false, + drawBuffersExtension: false, + depthTextureExtension: false, + vertexArrayObject: true, + instancedArrays: true, + supportOcclusionQuery: false, + canUseTimestampForTimerQuery: false, + blendMinMax: false, + maxMSAASamples: 16, + canUseGLInstanceID: true, + canUseGLVertexID: true, + supportComputeShaders: false, + supportSRGBBuffers: true, + supportTransformFeedbacks: false, + textureMaxLevel: false, + texture2DArrayMaxLayerCount: _native.Engine.CAPS_LIMITS_MAX_TEXTURE_LAYERS, + disableMorphTargetTexture: false, + parallelShaderCompile: { COMPLETION_STATUS_KHR: 0 }, + textureNorm16: false, + blendParametersPerTarget: false, + dualSourceBlending: false, + }; + + this._features = { + forceBitmapOverHTMLImageElement: true, + supportRenderAndCopyToLodForFloatTextures: false, + supportDepthStencilTexture: false, + supportShadowSamplers: false, + uniformBufferHardCheckMatrix: false, + allowTexturePrefiltering: false, + trackUbosInFrame: false, + checkUbosContentBeforeUpload: false, + supportCSM: false, + basisNeedsPOT: false, + support3DTextures: false, + needTypeSuffixInShaderConstants: false, + supportMSAA: true, + supportSSAO2: false, + supportIBLShadows: false, + supportExtendedTextureFormats: false, + supportSwitchCaseInShader: false, + supportSyncTextureRead: false, + needsInvertingBitmap: true, + useUBOBindingCache: true, + needShaderCodeInlining: true, + needToAlwaysBindUniformBuffers: false, + supportRenderPasses: true, + supportSpriteInstancing: false, + forceVertexBufferStrideAndOffsetMultiple4Bytes: true, + _checkNonFloatVertexBuffersDontRecreatePipelineContext: false, + _collectUbosUpdatedInFrame: false, + }; + + Logger.Log("Babylon Native (v" + AbstractEngine.Version + ") launched"); + + // Wrappers + if (typeof URL === "undefined") { + (window.URL as any) = { + createObjectURL: function () {}, + revokeObjectURL: function () {}, + }; + } + + // TODO: Remove in next protocol version update + if (typeof Blob === "undefined") { + (window.Blob as any) = function (v: any) { + return v; + }; + } + + // polyfill for Chakra + if (!Array.prototype.flat) { + Object.defineProperty(Array.prototype, "flat", { + configurable: true, + value: function flat() { + const depth = isNaN(arguments[0]) ? 1 : Number(arguments[0]); + + return depth + ? Array.prototype.reduce.call( + this, + function (acc: any, cur: any) { + if (Array.isArray(cur)) { + // eslint-disable-next-line prefer-spread + acc.push.apply(acc, flat.call(cur, depth - 1)); + } else { + acc.push(cur); + } + return acc; + }, + [] + ) + : Array.prototype.slice.call(this); + }, + writable: true, + }); + } + + // Currently we do not fully configure the ThinEngine on construction of NativeEngine. + // Setup resolution scaling based on display settings. + const devicePixelRatio = window ? window.devicePixelRatio || 1.0 : 1.0; + this._hardwareScalingLevel = adaptToDeviceRatio ? 1.0 / devicePixelRatio : 1.0; + this._engine.setHardwareScalingLevel(this._hardwareScalingLevel); + this._lastDevicePixelRatio = devicePixelRatio; + this.resize(); + + const currentDepthFunction = this.getDepthFunction(); + if (currentDepthFunction) { + this.setDepthFunction(currentDepthFunction); + } + + // Shader processor + this._shaderProcessor = new NativeShaderProcessor(); + + this.onNewSceneAddedObservable.add((scene) => { + const originalRender = scene.render; + scene.render = (...args: Parameters) => { + this._commandBufferEncoder.beginCommandScope(); + originalRender.apply(scene, args); + this._commandBufferEncoder.endCommandScope(); + }; + }); + } + + public override setHardwareScalingLevel(level: number): void { + super.setHardwareScalingLevel(level); + this._engine.setHardwareScalingLevel(level); + } + + public override dispose(): void { + super.dispose(); + if (this._boundBuffersVertexArray) { + this._deleteVertexArray(this._boundBuffersVertexArray); + } + this._engine.dispose(); + } + + /** + * Enable scissor test on a specific rectangle (ie. render will only be executed on a specific portion of the screen) + * @param x defines the x-coordinate of the bottom left corner of the clear rectangle + * @param y defines the y-coordinate of the corner of the clear rectangle + * @param width defines the width of the clear rectangle + * @param height defines the height of the clear rectangle + */ + public enableScissor(x: number, y: number, width: number, height: number): void { + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETSCISSOR); + this._commandBufferEncoder.encodeCommandArgAsFloat32(x); + this._commandBufferEncoder.encodeCommandArgAsFloat32(y); + this._commandBufferEncoder.encodeCommandArgAsFloat32(width); + this._commandBufferEncoder.encodeCommandArgAsFloat32(height); + this._commandBufferEncoder.finishEncodingCommand(); + } + + /** + * Disable previously set scissor test rectangle + */ + public disableScissor() { + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETSCISSOR); + this._commandBufferEncoder.encodeCommandArgAsFloat32(0); + this._commandBufferEncoder.encodeCommandArgAsFloat32(0); + this._commandBufferEncoder.encodeCommandArgAsFloat32(0); + this._commandBufferEncoder.encodeCommandArgAsFloat32(0); + this._commandBufferEncoder.finishEncodingCommand(); + } + + /** + * Can be used to override the current requestAnimationFrame requester. + * @internal + */ + protected override _queueNewFrame(bindedRenderFunction: any, requester?: any): number { + // Use the provided requestAnimationFrame, unless the requester is the window. In that case, we will default to the Babylon Native version of requestAnimationFrame. + if (requester.requestAnimationFrame && requester !== window) { + requester.requestAnimationFrame(bindedRenderFunction); + } else { + this._engine.requestAnimationFrame(bindedRenderFunction); + } + return 0; + } + + protected override _restoreEngineAfterContextLost(): void { + this._clearEmptyResources(); + + const depthTest = this._depthCullingState.depthTest; // backup those values because the call to initEngine / wipeCaches will reset them + const depthFunc = this._depthCullingState.depthFunc; + const depthMask = this._depthCullingState.depthMask; + const stencilTest = this._stencilState.stencilTest; + + this._rebuildGraphicsResources(); + + this._depthCullingState.depthTest = depthTest; + this._depthCullingState.depthFunc = depthFunc; + this._depthCullingState.depthMask = depthMask; + this._stencilState.stencilTest = stencilTest; + + this._flagContextRestored(); + } + + /** + * Override default engine behavior. + * @param framebuffer + */ + public override _bindUnboundFramebuffer(framebuffer: Nullable) { + if (this._currentFramebuffer !== framebuffer) { + if (this._currentFramebuffer) { + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_UNBINDFRAMEBUFFER); + this._commandBufferEncoder.encodeCommandArgAsNativeData(this._currentFramebuffer as NativeFramebuffer); + this._commandBufferEncoder.finishEncodingCommand(); + } + + if (framebuffer) { + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_BINDFRAMEBUFFER); + this._commandBufferEncoder.encodeCommandArgAsNativeData(framebuffer as NativeFramebuffer); + this._commandBufferEncoder.finishEncodingCommand(); + } + + this._currentFramebuffer = framebuffer; + } + } + + /** + * Gets host document + * @returns the host document object + */ + public override getHostDocument(): Nullable { + return null; + } + + public override clear(color: Nullable, backBuffer: boolean, depth: boolean, stencil: boolean = false, stencilClearValue = 0): void { + if (this.useReverseDepthBuffer) { + throw new Error("reverse depth buffer is not currently implemented"); + } + + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_CLEAR); + this._commandBufferEncoder.encodeCommandArgAsUInt32(backBuffer && color ? 1 : 0); + this._commandBufferEncoder.encodeCommandArgAsFloat32(color ? color.r : 0); + this._commandBufferEncoder.encodeCommandArgAsFloat32(color ? color.g : 0); + this._commandBufferEncoder.encodeCommandArgAsFloat32(color ? color.b : 0); + this._commandBufferEncoder.encodeCommandArgAsFloat32(color ? color.a : 1); + this._commandBufferEncoder.encodeCommandArgAsUInt32(depth ? 1 : 0); + this._commandBufferEncoder.encodeCommandArgAsFloat32(1); + this._commandBufferEncoder.encodeCommandArgAsUInt32(stencil ? 1 : 0); + this._commandBufferEncoder.encodeCommandArgAsUInt32(stencilClearValue); + this._commandBufferEncoder.finishEncodingCommand(); + } + + public override createIndexBuffer(indices: IndicesArray, updateable?: boolean, _label?: string): NativeDataBuffer { + const data = this._normalizeIndexData(indices); + const buffer = new NativeDataBuffer(); + buffer.references = 1; + buffer.is32Bits = data.BYTES_PER_ELEMENT === 4; + if (data.byteLength) { + buffer.nativeIndexBuffer = this._engine.createIndexBuffer(data.buffer, data.byteOffset, data.byteLength, buffer.is32Bits, updateable ?? false); + } + return buffer; + } + + public override createVertexBuffer(vertices: DataArray, updateable?: boolean, _label?: string): NativeDataBuffer { + const data = ArrayBuffer.isView(vertices) ? vertices : new Float32Array(vertices); + const buffer = new NativeDataBuffer(); + buffer.references = 1; + if (data.byteLength) { + buffer.nativeVertexBuffer = this._engine.createVertexBuffer(data.buffer, data.byteOffset, data.byteLength, updateable ?? false); + } + return buffer; + } + + private _recordVertexArrayObject( + vertexArray: any, + vertexBuffers: { [key: string]: VertexBuffer }, + indexBuffer: Nullable, + effect: Effect, + overrideVertexBuffers?: { [kind: string]: Nullable } + ): void { + if (!effect._checkedNonFloatVertexBuffers) { + checkNonFloatVertexBuffers(vertexBuffers, effect); + effect._checkedNonFloatVertexBuffers = true; + } + + if (indexBuffer) { + this._engine.recordIndexBuffer(vertexArray, indexBuffer.nativeIndexBuffer!); + } + + const attributes = effect.getAttributesNames(); + for (let index = 0; index < attributes.length; index++) { + const location = effect.getAttributeLocation(index); + if (location >= 0) { + const kind = attributes[index]; + let vertexBuffer: Nullable = null; + + if (overrideVertexBuffers) { + vertexBuffer = overrideVertexBuffers[kind]; + } + if (!vertexBuffer) { + vertexBuffer = vertexBuffers[kind]; + } + + if (vertexBuffer) { + const buffer = vertexBuffer.effectiveBuffer as Nullable; + if (buffer && buffer.nativeVertexBuffer) { + this._engine.recordVertexBuffer( + vertexArray, + buffer.nativeVertexBuffer, + location, + vertexBuffer.effectiveByteOffset, + vertexBuffer.effectiveByteStride, + vertexBuffer.getSize(), + getNativeAttribType(vertexBuffer.type), + vertexBuffer.normalized, + vertexBuffer.getInstanceDivisor() + ); + } + } + } + } + } + + public override bindBuffers(vertexBuffers: { [key: string]: VertexBuffer }, indexBuffer: Nullable, effect: Effect): void { + if (this._boundBuffersVertexArray) { + this._deleteVertexArray(this._boundBuffersVertexArray); + } + this._boundBuffersVertexArray = this._engine.createVertexArray(); + this._recordVertexArrayObject(this._boundBuffersVertexArray, vertexBuffers, indexBuffer, effect); + this.bindVertexArrayObject(this._boundBuffersVertexArray); + } + + public override recordVertexArrayObject( + vertexBuffers: { [key: string]: VertexBuffer }, + indexBuffer: Nullable, + effect: Effect, + overrideVertexBuffers?: { [kind: string]: Nullable } + ): WebGLVertexArrayObject { + const vertexArray = this._engine.createVertexArray(); + this._recordVertexArrayObject(vertexArray, vertexBuffers, indexBuffer, effect, overrideVertexBuffers); + return vertexArray; + } + + private _deleteVertexArray(vertexArray: NativeVertexArrayObject) { + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_DELETEVERTEXARRAY); + this._commandBufferEncoder.encodeCommandArgAsNativeData(vertexArray); + this._commandBufferEncoder.finishEncodingCommand(); + } + + public override bindVertexArrayObject(vertexArray: WebGLVertexArrayObject): void { + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_BINDVERTEXARRAY); + this._commandBufferEncoder.encodeCommandArgAsNativeData(vertexArray as NativeVertexArrayObject); + this._commandBufferEncoder.finishEncodingCommand(); + } + + public override releaseVertexArrayObject(vertexArray: WebGLVertexArrayObject) { + this._deleteVertexArray(vertexArray as NativeVertexArrayObject); + } + + public override getAttributes(pipelineContext: IPipelineContext, attributesNames: string[]): number[] { + const nativePipelineContext = pipelineContext as NativePipelineContext; + const nativeShaderProcessingContext = nativePipelineContext.shaderProcessingContext!; + + remappedAttributesNames.length = 0; + for (let index = 0; index < attributesNames.length; index++) { + const origAttributeName = attributesNames[index]; + const attributeName = nativeShaderProcessingContext.remappedAttributeNames[origAttributeName] ?? origAttributeName; + remappedAttributesNames[index] = attributeName; + } + return this._engine.getAttributes(nativePipelineContext.program, remappedAttributesNames); + } + + /** + * Triangle Fan and Line Loop are not supported by modern rendering API + * @param fillMode defines the primitive to use + * @returns true if supported + */ + private _checkSupportedFillMode(fillMode: number): boolean { + if (fillMode == Constants.MATERIAL_LineLoopDrawMode || fillMode == Constants.MATERIAL_TriangleFanDrawMode) { + if (!this._fillModeWarningDisplayed) { + Logger.Warn("Line Loop and Triangle Fan are not supported fill modes with Babylon Native. Elements with these fill mode will not be visible."); + this._fillModeWarningDisplayed = true; + } + return false; + } + return true; + } + + /** + * Draw a list of indexed primitives + * @param fillMode defines the primitive to use + * @param indexStart defines the starting index + * @param indexCount defines the number of index to draw + * @param instancesCount defines the number of instances to draw (if instantiation is enabled) + */ + public override drawElementsType(fillMode: number, indexStart: number, indexCount: number, instancesCount?: number): void { + if (!this._checkSupportedFillMode(fillMode)) { + return; + } + // Apply states + this._drawCalls.addCount(1, false); + + if (instancesCount) { + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_DRAWINDEXEDINSTANCED); + this._commandBufferEncoder.encodeCommandArgAsUInt32(fillMode); + this._commandBufferEncoder.encodeCommandArgAsUInt32(indexStart); + this._commandBufferEncoder.encodeCommandArgAsUInt32(indexCount); + this._commandBufferEncoder.encodeCommandArgAsUInt32(instancesCount); + } else { + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_DRAWINDEXED); + this._commandBufferEncoder.encodeCommandArgAsUInt32(fillMode); + this._commandBufferEncoder.encodeCommandArgAsUInt32(indexStart); + this._commandBufferEncoder.encodeCommandArgAsUInt32(indexCount); + } + + this._commandBufferEncoder.finishEncodingCommand(); + } + + /** + * Draw a list of unindexed primitives + * @param fillMode defines the primitive to use + * @param verticesStart defines the index of first vertex to draw + * @param verticesCount defines the count of vertices to draw + * @param instancesCount defines the number of instances to draw (if instantiation is enabled) + */ + public override drawArraysType(fillMode: number, verticesStart: number, verticesCount: number, instancesCount?: number): void { + if (!this._checkSupportedFillMode(fillMode)) { + return; + } + // Apply states + this._drawCalls.addCount(1, false); + + if (instancesCount) { + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_DRAWINSTANCED); + this._commandBufferEncoder.encodeCommandArgAsUInt32(fillMode); + this._commandBufferEncoder.encodeCommandArgAsUInt32(verticesStart); + this._commandBufferEncoder.encodeCommandArgAsUInt32(verticesCount); + this._commandBufferEncoder.encodeCommandArgAsUInt32(instancesCount); + } else { + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_DRAW); + this._commandBufferEncoder.encodeCommandArgAsUInt32(fillMode); + this._commandBufferEncoder.encodeCommandArgAsUInt32(verticesStart); + this._commandBufferEncoder.encodeCommandArgAsUInt32(verticesCount); + } + + this._commandBufferEncoder.finishEncodingCommand(); + } + + public override createPipelineContext(shaderProcessingContext: Nullable<_IShaderProcessingContext>): IPipelineContext { + const isAsync = !!this._caps.parallelShaderCompile; + return new NativePipelineContext(this, isAsync, shaderProcessingContext as Nullable); + } + + public override createMaterialContext(): IMaterialContext | undefined { + return undefined; + } + + public override createDrawContext(): IDrawContext | undefined { + return undefined; + } + + /** + * Function is not technically Async + * @internal + */ + // eslint-disable-next-line no-restricted-syntax + public override _preparePipelineContextAsync( + pipelineContext: IPipelineContext, + vertexSourceCode: string, + fragmentSourceCode: string, + createAsRaw: boolean, + _rawVertexSourceCode: string, + _rawFragmentSourceCode: string, + _rebuildRebind: any, + defines: Nullable, + _transformFeedbackVaryings: Nullable, + _key: string, + onReady: () => void + ) { + if (createAsRaw) { + this.createRawShaderProgram(); + } else { + this.createShaderProgram(pipelineContext, vertexSourceCode, fragmentSourceCode, defines); + } + + onReady(); + } + + /** + * @internal + */ + public override _getShaderProcessingContext(_shaderLanguage: ShaderLanguage): Nullable<_IShaderProcessingContext> { + return new NativeShaderProcessingContext(); + } + + /** + * @internal + */ + public override _executeWhenRenderingStateIsCompiled(pipelineContext: IPipelineContext, action: () => void) { + const nativePipelineContext = pipelineContext as NativePipelineContext; + if (nativePipelineContext.isAsync) { + if (nativePipelineContext.onCompiled) { + const oldHandler = nativePipelineContext.onCompiled; + nativePipelineContext.onCompiled = () => { + oldHandler(); + action(); + }; + } else { + nativePipelineContext.onCompiled = action; + } + } else { + action(); + } + } + + public override createRawShaderProgram(): WebGLProgram { + throw new Error("Not Supported"); + } + + public override createShaderProgram(pipelineContext: IPipelineContext, vertexCode: string, fragmentCode: string, defines: Nullable): WebGLProgram { + const nativePipelineContext = pipelineContext as NativePipelineContext; + + this.onBeforeShaderCompilationObservable.notifyObservers(this); + + const vertexInliner = new ShaderCodeInliner(vertexCode); + vertexInliner.processCode(); + vertexCode = vertexInliner.code; + + const fragmentInliner = new ShaderCodeInliner(fragmentCode); + fragmentInliner.processCode(); + fragmentCode = fragmentInliner.code; + + vertexCode = ThinEngine._ConcatenateShader(vertexCode, defines); + fragmentCode = ThinEngine._ConcatenateShader(fragmentCode, defines); + + const onSuccess = () => { + nativePipelineContext.isCompiled = true; + nativePipelineContext.onCompiled?.(); + this.onAfterShaderCompilationObservable.notifyObservers(this); + }; + + if (pipelineContext.isAsync) { + nativePipelineContext.program = this._engine.createProgramAsync(vertexCode, fragmentCode, onSuccess, (error: Error) => { + nativePipelineContext.compilationError = error; + }); + } else { + try { + nativePipelineContext.program = this._engine.createProgram(vertexCode, fragmentCode); + onSuccess(); + } catch (e) { + const message = e?.message; + throw new Error("SHADER ERROR" + (typeof message === "string" ? "\n" + message : "")); + } + } + + return nativePipelineContext.program as WebGLProgram; + } + + /** + * Inline functions in shader code that are marked to be inlined + * @param code code to inline + * @returns inlined code + */ + public override inlineShaderCode(code: string): string { + const sci = new ShaderCodeInliner(code); + sci.debug = false; + sci.processCode(); + return sci.code; + } + + protected override _setProgram(program: WebGLProgram): void { + if (this._currentProgram !== program) { + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETPROGRAM); + this._commandBufferEncoder.encodeCommandArgAsNativeData(program as NativeProgram); + this._commandBufferEncoder.finishEncodingCommand(); + this._currentProgram = program; + } + } + + public override _deletePipelineContext(pipelineContext: IPipelineContext): void { + const nativePipelineContext = pipelineContext as NativePipelineContext; + if (nativePipelineContext && nativePipelineContext.program) { + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_DELETEPROGRAM); + this._commandBufferEncoder.encodeCommandArgAsNativeData(nativePipelineContext.program); + this._commandBufferEncoder.finishEncodingCommand(); + } + } + + public override getUniforms(pipelineContext: IPipelineContext, uniformsNames: string[]): WebGLUniformLocation[] { + const nativePipelineContext = pipelineContext as NativePipelineContext; + return this._engine.getUniforms(nativePipelineContext.program, uniformsNames); + } + + public override bindUniformBlock(pipelineContext: IPipelineContext, blockName: string, index: number): void { + // TODO + throw new Error("Not Implemented"); + } + + public override bindSamplers(effect: Effect): void { + const nativePipelineContext = effect.getPipelineContext() as NativePipelineContext; + this._setProgram(nativePipelineContext.program as WebGLProgram); + + // TODO: share this with engine? + const samplers = effect.getSamplers(); + for (let index = 0; index < samplers.length; index++) { + const uniform = effect.getUniform(samplers[index]); + + if (uniform) { + this._boundUniforms[index] = uniform; + } + } + this._currentEffect = null; + } + + public override getRenderWidth(useScreen = false): number { + if (!useScreen && this._currentRenderTarget) { + return this._currentRenderTarget.width; + } + + return this._engine.getRenderWidth(); + } + + public override getRenderHeight(useScreen = false): number { + if (!useScreen && this._currentRenderTarget) { + return this._currentRenderTarget.height; + } + + return this._engine.getRenderHeight(); + } + + public override setViewport(viewport: IViewportLike, requiredWidth?: number, requiredHeight?: number): void { + this._cachedViewport = viewport; + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETVIEWPORT); + this._commandBufferEncoder.encodeCommandArgAsFloat32(viewport.x); + this._commandBufferEncoder.encodeCommandArgAsFloat32(viewport.y); + this._commandBufferEncoder.encodeCommandArgAsFloat32(viewport.width); + this._commandBufferEncoder.encodeCommandArgAsFloat32(viewport.height); + this._commandBufferEncoder.finishEncodingCommand(); + } + + public override setStateCullFaceType(_cullBackFaces?: boolean, _force?: boolean): void { + throw new Error("setStateCullFaceType: Not Implemented"); + } + + public override setState( + culling: boolean, + zOffset: number = 0, + force?: boolean, + reverseSide = false, + cullBackFaces?: boolean, + stencil?: IStencilState, + zOffsetUnits: number = 0 + ): void { + this._zOffset = zOffset; + this._zOffsetUnits = zOffsetUnits; + if (this._zOffset !== 0) { + Logger.Warn("zOffset is not supported in Native engine."); + } + + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETSTATE); + this._commandBufferEncoder.encodeCommandArgAsUInt32(culling ? 1 : 0); + this._commandBufferEncoder.encodeCommandArgAsFloat32(zOffset); + this._commandBufferEncoder.encodeCommandArgAsFloat32(zOffsetUnits); + this._commandBufferEncoder.encodeCommandArgAsUInt32((this.cullBackFaces ?? cullBackFaces ?? true) ? 1 : 0); + this._commandBufferEncoder.encodeCommandArgAsUInt32(reverseSide ? 1 : 0); + this._commandBufferEncoder.finishEncodingCommand(); + } + + /** + * Gets the client rect of native canvas. Needed for InputManager. + * @returns a client rectangle + */ + public override getInputElementClientRect(): Nullable { + const rect = { + bottom: this.getRenderHeight(), + height: this.getRenderHeight(), + left: 0, + right: this.getRenderWidth(), + top: 0, + width: this.getRenderWidth(), + x: 0, + y: 0, + toJSON: () => {}, + }; + return rect; + } + + /** + * Set the z offset Factor to apply to current rendering + * @param value defines the offset to apply + */ + public override setZOffset(value: number): void { + if (value !== this._zOffset) { + this._zOffset = value; + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETZOFFSET); + this._commandBufferEncoder.encodeCommandArgAsFloat32(this.useReverseDepthBuffer ? -value : value); + this._commandBufferEncoder.finishEncodingCommand(); + } + } + + /** + * Gets the current value of the zOffset Factor + * @returns the current zOffset Factor state + */ + public override getZOffset(): number { + return this._zOffset; + } + + /** + * Set the z offset Units to apply to current rendering + * @param value defines the offset to apply + */ + public override setZOffsetUnits(value: number): void { + if (value !== this._zOffsetUnits) { + this._zOffsetUnits = value; + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETZOFFSETUNITS); + this._commandBufferEncoder.encodeCommandArgAsFloat32(this.useReverseDepthBuffer ? -value : value); + this._commandBufferEncoder.finishEncodingCommand(); + } + } + + /** + * Gets the current value of the zOffset Units + * @returns the current zOffset Units state + */ + public override getZOffsetUnits(): number { + return this._zOffsetUnits; + } + + /** + * Enable or disable depth buffering + * @param enable defines the state to set + */ + public override setDepthBuffer(enable: boolean): void { + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETDEPTHTEST); + this._commandBufferEncoder.encodeCommandArgAsUInt32(enable ? this._currentDepthTest : _native.Engine.DEPTH_TEST_ALWAYS); + this._commandBufferEncoder.finishEncodingCommand(); + } + + /** + * Gets a boolean indicating if depth writing is enabled + * @returns the current depth writing state + */ + public override getDepthWrite(): boolean { + return this._depthWrite; + } + + public override getDepthFunction(): Nullable { + switch (this._currentDepthTest) { + case _native.Engine.DEPTH_TEST_NEVER: + return Constants.NEVER; + case _native.Engine.DEPTH_TEST_ALWAYS: + return Constants.ALWAYS; + case _native.Engine.DEPTH_TEST_GREATER: + return Constants.GREATER; + case _native.Engine.DEPTH_TEST_GEQUAL: + return Constants.GEQUAL; + case _native.Engine.DEPTH_TEST_NOTEQUAL: + return Constants.NOTEQUAL; + case _native.Engine.DEPTH_TEST_EQUAL: + return Constants.EQUAL; + case _native.Engine.DEPTH_TEST_LESS: + return Constants.LESS; + case _native.Engine.DEPTH_TEST_LEQUAL: + return Constants.LEQUAL; + } + return null; + } + + public override setDepthFunction(depthFunc: number) { + let nativeDepthFunc = 0; + switch (depthFunc) { + case Constants.NEVER: + nativeDepthFunc = _native.Engine.DEPTH_TEST_NEVER; + break; + case Constants.ALWAYS: + nativeDepthFunc = _native.Engine.DEPTH_TEST_ALWAYS; + break; + case Constants.GREATER: + nativeDepthFunc = _native.Engine.DEPTH_TEST_GREATER; + break; + case Constants.GEQUAL: + nativeDepthFunc = _native.Engine.DEPTH_TEST_GEQUAL; + break; + case Constants.NOTEQUAL: + nativeDepthFunc = _native.Engine.DEPTH_TEST_NOTEQUAL; + break; + case Constants.EQUAL: + nativeDepthFunc = _native.Engine.DEPTH_TEST_EQUAL; + break; + case Constants.LESS: + nativeDepthFunc = _native.Engine.DEPTH_TEST_LESS; + break; + case Constants.LEQUAL: + nativeDepthFunc = _native.Engine.DEPTH_TEST_LEQUAL; + break; + } + + this._currentDepthTest = nativeDepthFunc; + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETDEPTHTEST); + this._commandBufferEncoder.encodeCommandArgAsUInt32(this._currentDepthTest); + this._commandBufferEncoder.finishEncodingCommand(); + } + + /** + * Enable or disable depth writing + * @param enable defines the state to set + */ + public override setDepthWrite(enable: boolean): void { + this._depthWrite = enable; + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETDEPTHWRITE); + this._commandBufferEncoder.encodeCommandArgAsUInt32(Number(enable)); + this._commandBufferEncoder.finishEncodingCommand(); + } + + /** + * Enable or disable color writing + * @param enable defines the state to set + */ + public override setColorWrite(enable: boolean): void { + this._colorWrite = enable; + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETCOLORWRITE); + this._commandBufferEncoder.encodeCommandArgAsUInt32(Number(enable)); + this._commandBufferEncoder.finishEncodingCommand(); + } + + /** + * Gets a boolean indicating if color writing is enabled + * @returns the current color writing state + */ + public override getColorWrite(): boolean { + return this._colorWrite; + } + + private applyStencil(): void { + this._setStencil( + this._stencilMask, + getNativeStencilOpFail(this._stencilOpStencilFail), + getNativeStencilDepthFail(this._stencilOpDepthFail), + getNativeStencilDepthPass(this._stencilOpStencilDepthPass), + getNativeStencilFunc(this._stencilFunc), + this._stencilFuncRef + ); + } + + private _setStencil(mask: number, stencilOpFail: number, depthOpFail: number, depthOpPass: number, func: number, ref: number) { + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETSTENCIL); + this._commandBufferEncoder.encodeCommandArgAsUInt32(mask); + this._commandBufferEncoder.encodeCommandArgAsUInt32(stencilOpFail); + this._commandBufferEncoder.encodeCommandArgAsUInt32(depthOpFail); + this._commandBufferEncoder.encodeCommandArgAsUInt32(depthOpPass); + this._commandBufferEncoder.encodeCommandArgAsUInt32(func); + this._commandBufferEncoder.encodeCommandArgAsUInt32(ref); + this._commandBufferEncoder.finishEncodingCommand(); + } + + /** + * Enable or disable the stencil buffer + * @param enable defines if the stencil buffer must be enabled or disabled + */ + public override setStencilBuffer(enable: boolean): void { + this._stencilTest = enable; + if (enable) { + this.applyStencil(); + } else { + this._setStencil( + 255, + _native.Engine.STENCIL_OP_FAIL_S_KEEP, + _native.Engine.STENCIL_OP_FAIL_Z_KEEP, + _native.Engine.STENCIL_OP_PASS_Z_KEEP, + _native.Engine.STENCIL_TEST_ALWAYS, + 0 + ); + } + } + + /** + * Gets a boolean indicating if stencil buffer is enabled + * @returns the current stencil buffer state + */ + public override getStencilBuffer(): boolean { + return this._stencilTest; + } + + /** + * Gets the current stencil operation when stencil passes + * @returns a number defining stencil operation to use when stencil passes + */ + public override getStencilOperationPass(): number { + return this._stencilOpStencilDepthPass; + } + + /** + * Sets the stencil operation to use when stencil passes + * @param operation defines the stencil operation to use when stencil passes + */ + public override setStencilOperationPass(operation: number): void { + this._stencilOpStencilDepthPass = operation; + this.applyStencil(); + } + + /** + * Sets the current stencil mask + * @param mask defines the new stencil mask to use + */ + public override setStencilMask(mask: number): void { + this._stencilMask = mask; + this.applyStencil(); + } + + /** + * Sets the current stencil function + * @param stencilFunc defines the new stencil function to use + */ + public override setStencilFunction(stencilFunc: number) { + this._stencilFunc = stencilFunc; + this.applyStencil(); + } + + /** + * Sets the current stencil reference + * @param reference defines the new stencil reference to use + */ + public override setStencilFunctionReference(reference: number) { + this._stencilFuncRef = reference; + this.applyStencil(); + } + + /** + * Sets the current stencil mask + * @param mask defines the new stencil mask to use + */ + public override setStencilFunctionMask(mask: number) { + this._stencilFuncMask = mask; + } + + /** + * Sets the stencil operation to use when stencil fails + * @param operation defines the stencil operation to use when stencil fails + */ + public override setStencilOperationFail(operation: number): void { + this._stencilOpStencilFail = operation; + this.applyStencil(); + } + + /** + * Sets the stencil operation to use when depth fails + * @param operation defines the stencil operation to use when depth fails + */ + public override setStencilOperationDepthFail(operation: number): void { + this._stencilOpDepthFail = operation; + this.applyStencil(); + } + + /** + * Gets the current stencil mask + * @returns a number defining the new stencil mask to use + */ + public override getStencilMask(): number { + return this._stencilMask; + } + + /** + * Gets the current stencil function + * @returns a number defining the stencil function to use + */ + public override getStencilFunction(): number { + return this._stencilFunc; + } + + /** + * Gets the current stencil reference value + * @returns a number defining the stencil reference value to use + */ + public override getStencilFunctionReference(): number { + return this._stencilFuncRef; + } + + /** + * Gets the current stencil mask + * @returns a number defining the stencil mask to use + */ + public override getStencilFunctionMask(): number { + return this._stencilFuncMask; + } + + /** + * Gets the current stencil operation when stencil fails + * @returns a number defining stencil operation to use when stencil fails + */ + public override getStencilOperationFail(): number { + return this._stencilOpStencilFail; + } + + /** + * Gets the current stencil operation when depth fails + * @returns a number defining stencil operation to use when depth fails + */ + public override getStencilOperationDepthFail(): number { + return this._stencilOpDepthFail; + } + + /** + * Sets alpha constants used by some alpha blending modes + * @param r defines the red component + * @param g defines the green component + * @param b defines the blue component + * @param a defines the alpha component + */ + public override setAlphaConstants(r: number, g: number, b: number, a: number) { + throw new Error("Setting alpha blend constant color not yet implemented."); + } + + /** + * Sets the current alpha mode + * @param mode defines the mode to use (one of the BABYLON.Constants.ALPHA_XXX) + * @param noDepthWriteChange defines if depth writing state should remains unchanged (false by default) + * @param targetIndex defines the index of the target to set the alpha mode for (default is 0) + * @see https://doc.babylonjs.com/features/featuresDeepDive/materials/advanced/transparent_rendering + */ + public override setAlphaMode(mode: number, noDepthWriteChange: boolean = false, targetIndex = 0): void { + if (this._alphaMode[targetIndex] === mode) { + return; + } + + const nativeMode = getNativeAlphaMode(mode); + + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETBLENDMODE); + this._commandBufferEncoder.encodeCommandArgAsUInt32(nativeMode); + this._commandBufferEncoder.finishEncodingCommand(); + + if (!noDepthWriteChange) { + this.setDepthWrite(mode === Constants.ALPHA_DISABLE); + } + + this._alphaMode[targetIndex] = mode; + } + + public override setInt(uniform: WebGLUniformLocation, int: number): boolean { + if (!uniform) { + return false; + } + + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETINT); + this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); + this._commandBufferEncoder.encodeCommandArgAsInt32(int); + this._commandBufferEncoder.finishEncodingCommand(); + return true; + } + + public override setIntArray(uniform: WebGLUniformLocation, array: Int32Array): boolean { + if (!uniform) { + return false; + } + + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETINTARRAY); + this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); + this._commandBufferEncoder.encodeCommandArgAsInt32s(array); + this._commandBufferEncoder.finishEncodingCommand(); + return true; + } + + public override setIntArray2(uniform: WebGLUniformLocation, array: Int32Array): boolean { + if (!uniform) { + return false; + } + + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETINTARRAY2); + this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); + this._commandBufferEncoder.encodeCommandArgAsInt32s(array); + this._commandBufferEncoder.finishEncodingCommand(); + return true; + } + + public override setIntArray3(uniform: WebGLUniformLocation, array: Int32Array): boolean { + if (!uniform) { + return false; + } + + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETINTARRAY3); + this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); + this._commandBufferEncoder.encodeCommandArgAsInt32s(array); + this._commandBufferEncoder.finishEncodingCommand(); + return true; + } + + public override setIntArray4(uniform: WebGLUniformLocation, array: Int32Array): boolean { + if (!uniform) { + return false; + } + + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETINTARRAY4); + this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); + this._commandBufferEncoder.encodeCommandArgAsInt32s(array); + this._commandBufferEncoder.finishEncodingCommand(); + return true; + } + + public setFloatArray(uniform: WebGLUniformLocation, array: Float32Array): boolean { + if (!uniform) { + return false; + } + + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETFLOATARRAY); + this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); + this._commandBufferEncoder.encodeCommandArgAsFloat32s(array); + this._commandBufferEncoder.finishEncodingCommand(); + return true; + } + + public setFloatArray2(uniform: WebGLUniformLocation, array: Float32Array): boolean { + if (!uniform) { + return false; + } + + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETFLOATARRAY2); + this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); + this._commandBufferEncoder.encodeCommandArgAsFloat32s(array); + this._commandBufferEncoder.finishEncodingCommand(); + return true; + } + + public setFloatArray3(uniform: WebGLUniformLocation, array: Float32Array): boolean { + if (!uniform) { + return false; + } + + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETFLOATARRAY3); + this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); + this._commandBufferEncoder.encodeCommandArgAsFloat32s(array); + this._commandBufferEncoder.finishEncodingCommand(); + return true; + } + + public setFloatArray4(uniform: WebGLUniformLocation, array: Float32Array): boolean { + if (!uniform) { + return false; + } + + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETFLOATARRAY4); + this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); + this._commandBufferEncoder.encodeCommandArgAsFloat32s(array); + this._commandBufferEncoder.finishEncodingCommand(); + return true; + } + + public override setArray(uniform: WebGLUniformLocation, array: number[]): boolean { + if (!uniform) { + return false; + } + + return this.setFloatArray(uniform, new Float32Array(array)); + } + + public override setArray2(uniform: WebGLUniformLocation, array: number[]): boolean { + if (!uniform) { + return false; + } + + return this.setFloatArray2(uniform, new Float32Array(array)); + } + + public override setArray3(uniform: WebGLUniformLocation, array: number[]): boolean { + if (!uniform) { + return false; + } + + return this.setFloatArray3(uniform, new Float32Array(array)); + } + + public override setArray4(uniform: WebGLUniformLocation, array: number[]): boolean { + if (!uniform) { + return false; + } + + return this.setFloatArray4(uniform, new Float32Array(array)); + } + + public override setMatrices(uniform: WebGLUniformLocation, matrices: DeepImmutable): boolean { + if (!uniform) { + return false; + } + + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETMATRICES); + this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); + this._commandBufferEncoder.encodeCommandArgAsFloat32s(matrices); + this._commandBufferEncoder.finishEncodingCommand(); + + return true; + } + + public override setMatrix3x3(uniform: WebGLUniformLocation, matrix: Float32Array): boolean { + if (!uniform) { + return false; + } + + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETMATRIX3X3); + this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); + this._commandBufferEncoder.encodeCommandArgAsFloat32s(matrix); + this._commandBufferEncoder.finishEncodingCommand(); + return true; + } + + public override setMatrix2x2(uniform: WebGLUniformLocation, matrix: Float32Array): boolean { + if (!uniform) { + return false; + } + + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETMATRIX2X2); + this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); + this._commandBufferEncoder.encodeCommandArgAsFloat32s(matrix); + this._commandBufferEncoder.finishEncodingCommand(); + return true; + } + + public override setFloat(uniform: WebGLUniformLocation, value: number): boolean { + if (!uniform) { + return false; + } + + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETFLOAT); + this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); + this._commandBufferEncoder.encodeCommandArgAsFloat32(value); + this._commandBufferEncoder.finishEncodingCommand(); + return true; + } + + public override setFloat2(uniform: WebGLUniformLocation, x: number, y: number): boolean { + if (!uniform) { + return false; + } + + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETFLOAT2); + this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); + this._commandBufferEncoder.encodeCommandArgAsFloat32(x); + this._commandBufferEncoder.encodeCommandArgAsFloat32(y); + this._commandBufferEncoder.finishEncodingCommand(); + return true; + } + + public override setFloat3(uniform: WebGLUniformLocation, x: number, y: number, z: number): boolean { + if (!uniform) { + return false; + } + + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETFLOAT3); + this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); + this._commandBufferEncoder.encodeCommandArgAsFloat32(x); + this._commandBufferEncoder.encodeCommandArgAsFloat32(y); + this._commandBufferEncoder.encodeCommandArgAsFloat32(z); + this._commandBufferEncoder.finishEncodingCommand(); + return true; + } + + public override setFloat4(uniform: WebGLUniformLocation, x: number, y: number, z: number, w: number): boolean { + if (!uniform) { + return false; + } + + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETFLOAT4); + this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform as any as NativeData); + this._commandBufferEncoder.encodeCommandArgAsFloat32(x); + this._commandBufferEncoder.encodeCommandArgAsFloat32(y); + this._commandBufferEncoder.encodeCommandArgAsFloat32(z); + this._commandBufferEncoder.encodeCommandArgAsFloat32(w); + this._commandBufferEncoder.finishEncodingCommand(); + return true; + } + + public setColor3(uniform: WebGLUniformLocation, color3: IColor3Like): boolean { + if (!uniform) { + return false; + } + + this.setFloat3(uniform, color3.r, color3.g, color3.b); + return true; + } + + public setColor4(uniform: WebGLUniformLocation, color3: IColor3Like, alpha: number): boolean { + if (!uniform) { + return false; + } + + this.setFloat4(uniform, color3.r, color3.g, color3.b, alpha); + return true; + } + + public override wipeCaches(bruteForce?: boolean): void { + if (this.preventCacheWipeBetweenFrames) { + return; + } + this.resetTextureCache(); + this._currentEffect = null; + + if (bruteForce) { + this._currentProgram = null; + + this._stencilStateComposer.reset(); + this._depthCullingState.reset(); + this._alphaState.reset(); + } + + this._cachedVertexBuffers = null; + this._cachedIndexBuffer = null; + this._cachedEffectForVertexBuffers = null; + } + + protected override _createTexture(): WebGLTexture { + return this._engine.createTexture(); + } + + protected override _deleteTexture(texture: Nullable): void { + if (texture) { + this._engine.deleteTexture(texture.underlyingResource as NativeTexture); + } + } + + /** + * Update the content of a dynamic texture + * @param texture defines the texture to update + * @param canvas defines the canvas containing the source + * @param invertY defines if data must be stored with Y axis inverted + * @param premulAlpha defines if alpha is stored as premultiplied + * @param format defines the format of the data + */ + public override updateDynamicTexture(texture: Nullable, canvas: any, invertY: boolean, premulAlpha: boolean = false, format?: number): void { + if (premulAlpha === void 0) { + premulAlpha = false; + } + + if (!!texture && !!texture._hardwareTexture) { + const destination = texture._hardwareTexture.underlyingResource; + const context = canvas.getContext(); + // flush need to happen before getCanvasTexture: flush will create the render target synchronously (if it's not been created before) + context.flush(); + const source = canvas.getCanvasTexture(); + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_COPYTEXTURE); + this._commandBufferEncoder.encodeCommandArgAsNativeData(source as NativeData); + this._commandBufferEncoder.encodeCommandArgAsNativeData(destination as NativeData); + this._commandBufferEncoder.finishEncodingCommand(); + texture.isReady = true; + } + } + + public override createDynamicTexture(width: number, height: number, generateMipMaps: boolean, samplingMode: number): InternalTexture { + // it's not possible to create 0x0 texture sized. Many bgfx methods assume texture size is at least 1x1(best case). + // Worst case is getting a crash/assert. + width = Math.max(width, 1); + height = Math.max(height, 1); + return this.createRawTexture(new Uint8Array(width * height * 4), width, height, Constants.TEXTUREFORMAT_RGBA, false, false, samplingMode); + } + + public override createVideoElement(constraints: MediaTrackConstraints): any { + // create native object depending on stream. Only NativeCamera is supported for now. + if (this._camera) { + return this._camera.createVideo(constraints); + } + return null; + } + + public override updateVideoTexture(texture: Nullable, video: HTMLVideoElement, invertY: boolean): void { + if (texture && texture._hardwareTexture && this._camera) { + const webGLTexture = texture._hardwareTexture.underlyingResource; + this._camera.updateVideoTexture(webGLTexture, video, invertY); + } + } + + public override createRawTexture( + data: Nullable, + width: number, + height: number, + format: number, + generateMipMaps: boolean, + invertY: boolean, + samplingMode: number, + compression: Nullable = null, + type: number = Constants.TEXTURETYPE_UNSIGNED_BYTE, + creationFlags: number = 0, + useSRGBBuffer: boolean = false + ): InternalTexture { + const texture = new InternalTexture(this, InternalTextureSource.Raw); + + texture.format = format; + texture.generateMipMaps = generateMipMaps; + texture.samplingMode = samplingMode; + texture.invertY = invertY; + texture.baseWidth = width; + texture.baseHeight = height; + texture.width = texture.baseWidth; + texture.height = texture.baseHeight; + texture._compression = compression; + texture.type = type; + texture._useSRGBBuffer = this._getUseSRGBBuffer(useSRGBBuffer, !generateMipMaps); + + this.updateRawTexture(texture, data, format, invertY, compression, type, texture._useSRGBBuffer); + + if (texture._hardwareTexture) { + const webGLTexture = texture._hardwareTexture.underlyingResource; + const filter = getNativeSamplingMode(samplingMode); + this._setTextureSampling(webGLTexture, filter); + } + + this._internalTexturesCache.push(texture); + return texture; + } + + public override createRawTexture2DArray( + data: Nullable, + width: number, + height: number, + depth: number, + format: number, + generateMipMaps: boolean, + invertY: boolean, + samplingMode: number, + compression: Nullable = null, + textureType = Constants.TEXTURETYPE_UNSIGNED_BYTE + ): InternalTexture { + const texture = new InternalTexture(this, InternalTextureSource.Raw2DArray); + + texture.baseWidth = width; + texture.baseHeight = height; + texture.baseDepth = depth; + texture.width = width; + texture.height = height; + texture.depth = depth; + texture.format = format; + texture.type = textureType; + texture.generateMipMaps = generateMipMaps; + texture.samplingMode = samplingMode; + texture.is2DArray = true; + + if (texture._hardwareTexture) { + const nativeTexture = texture._hardwareTexture.underlyingResource; + this._engine.loadRawTexture2DArray(nativeTexture, data, width, height, depth, getNativeTextureFormat(format, textureType), generateMipMaps, invertY); + + const filter = getNativeSamplingMode(samplingMode); + this._setTextureSampling(nativeTexture, filter); + } + + texture.isReady = true; + + this._internalTexturesCache.push(texture); + return texture; + } + + public override updateRawTexture( + texture: Nullable, + bufferView: Nullable, + format: number, + invertY: boolean, + compression: Nullable = null, + type: number = Constants.TEXTURETYPE_UNSIGNED_BYTE, + useSRGBBuffer: boolean = false + ): void { + if (!texture) { + return; + } + + if (bufferView && texture._hardwareTexture) { + const underlyingResource = texture._hardwareTexture.underlyingResource; + this._engine.loadRawTexture( + underlyingResource, + bufferView, + texture.width, + texture.height, + getNativeTextureFormat(format, type), + texture.generateMipMaps, + texture.invertY + ); + } + + texture.isReady = true; + } + + // TODO: Refactor to share more logic with babylon.engine.ts version. + /** + * Usually called from Texture.ts. + * Passed information to create a NativeTexture + * @param url defines a value which contains one of the following: + * * A conventional http URL, e.g. 'http://...' or 'file://...' + * * A base64 string of in-line texture data, e.g. 'data:image/jpg;base64,/...' + * * An indicator that data being passed using the buffer parameter, e.g. 'data:mytexture.jpg' + * @param noMipmap defines a boolean indicating that no mipmaps shall be generated. Ignored for compressed textures. They must be in the file + * @param invertY when true, image is flipped when loaded. You probably want true. Certain compressed textures may invert this if their default is inverted (eg. ktx) + * @param scene needed for loading to the correct scene + * @param samplingMode mode with should be used sample / access the texture (Default: Texture.TRILINEAR_SAMPLINGMODE) + * @param onLoad optional callback to be called upon successful completion + * @param onError optional callback to be called upon failure + * @param buffer a source of a file previously fetched as either a base64 string, an ArrayBuffer (compressed or image format), HTMLImageElement (image format), or a Blob + * @param fallback an internal argument in case the function must be called again, due to etc1 not having alpha capabilities + * @param format internal format. Default: RGB when extension is '.jpg' else RGBA. Ignored for compressed textures + * @param forcedExtension defines the extension to use to pick the right loader + * @param mimeType defines an optional mime type + * @param loaderOptions options to be passed to the loader + * @param creationFlags specific flags to use when creating the texture (Constants.TEXTURE_CREATIONFLAG_STORAGE for storage textures, for eg) + * @param useSRGBBuffer defines if the texture must be loaded in a sRGB GPU buffer (if supported by the GPU). + * @returns a InternalTexture for assignment back into BABYLON.Texture + */ + public override createTexture( + url: Nullable, + noMipmap: boolean, + invertY: boolean, + scene: Nullable, + samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE, + onLoad: Nullable<(texture: InternalTexture) => void> = null, + onError: Nullable<(message: string, exception: any) => void> = null, + buffer: Nullable = null, + fallback: Nullable = null, + format: Nullable = null, + forcedExtension: Nullable = null, + mimeType?: string, + loaderOptions?: any, + creationFlags?: number, + useSRGBBuffer = false + ): InternalTexture { + url = url || ""; + const fromData = url.substring(0, 5) === "data:"; + //const fromBlob = url.substring(0, 5) === "blob:"; + const isBase64 = fromData && url.indexOf(";base64,") !== -1; + + const texture = fallback ? fallback : new InternalTexture(this, InternalTextureSource.Url); + + const originalUrl = url; + if (this._transformTextureUrl && !isBase64 && !fallback && !buffer) { + url = this._transformTextureUrl(url); + } + + // establish the file extension, if possible + const lastDot = url.lastIndexOf("."); + const extension = forcedExtension ? forcedExtension : lastDot > -1 ? url.substring(lastDot).toLowerCase() : ""; + + // some formats are already supported by bimg, no need to try to load them with JS + // leaving TextureLoader extension check for future use + let loaderPromise = null; + if (extension.endsWith(".basis") || extension.endsWith(".ktx") || extension.endsWith(".ktx2") || mimeType === "image/ktx" || mimeType === "image/ktx2") { + loaderPromise = AbstractEngine.GetCompatibleTextureLoader(extension); + } + + if (scene) { + scene.addPendingData(texture); + } + texture.url = url; + texture.generateMipMaps = !noMipmap; + texture.samplingMode = samplingMode; + texture.invertY = invertY; + texture._useSRGBBuffer = this._getUseSRGBBuffer(useSRGBBuffer, noMipmap); + + if (!this.doNotHandleContextLost) { + // Keep a link to the buffer only if we plan to handle context lost + texture._buffer = buffer; + } + + let onLoadObserver: Nullable> = null; + if (onLoad && !fallback) { + onLoadObserver = texture.onLoadedObservable.add(onLoad); + } + + if (!fallback) { + this._internalTexturesCache.push(texture); + } + + const onInternalError = (message?: string, exception?: any) => { + if (scene) { + scene.removePendingData(texture); + } + + if (url === originalUrl) { + if (onLoadObserver) { + texture.onLoadedObservable.remove(onLoadObserver); + } + + if (EngineStore.UseFallbackTexture) { + this.createTexture(EngineStore.FallbackTexture, noMipmap, texture.invertY, scene, samplingMode, null, onError, buffer, texture); + } + + if (onError) { + onError((message || "Unknown error") + (EngineStore.UseFallbackTexture ? " - Fallback texture was used" : ""), exception); + } + } else { + // fall back to the original url if the transformed url fails to load + Logger.Warn(`Failed to load ${url}, falling back to ${originalUrl}`); + this.createTexture(originalUrl, noMipmap, texture.invertY, scene, samplingMode, onLoad, onError, buffer, texture, format, forcedExtension, mimeType, loaderOptions); + } + }; + + // processing for non-image formats + if (loaderPromise) { + throw new Error("Loading textures from IInternalTextureLoader not yet implemented."); + } else { + const onload = (data: ArrayBufferView) => { + if (!texture._hardwareTexture) { + if (scene) { + scene.removePendingData(texture); + } + + return; + } + + const underlyingResource = texture._hardwareTexture.underlyingResource; + + this._engine.loadTexture( + underlyingResource, + data, + !noMipmap, + invertY, + texture._useSRGBBuffer, + () => { + texture.baseWidth = this._engine.getTextureWidth(underlyingResource); + texture.baseHeight = this._engine.getTextureHeight(underlyingResource); + texture.width = texture.baseWidth; + texture.height = texture.baseHeight; + texture.isReady = true; + + const filter = getNativeSamplingMode(samplingMode); + this._setTextureSampling(underlyingResource, filter); + + if (scene) { + scene.removePendingData(texture); + } + + texture.onLoadedObservable.notifyObservers(texture); + texture.onLoadedObservable.clear(); + }, + () => { + throw new Error("Could not load a native texture."); + } + ); + }; + + if (fromData && buffer) { + if (buffer instanceof ArrayBuffer) { + onload(new Uint8Array(buffer)); + } else if (ArrayBuffer.isView(buffer)) { + onload(buffer); + } else if (typeof buffer === "string") { + onload(new Uint8Array(DecodeBase64UrlToBinary(buffer))); + } else { + throw new Error("Unsupported buffer type"); + } + } else { + if (isBase64) { + onload(new Uint8Array(DecodeBase64UrlToBinary(url))); + } else { + this._loadFile( + url, + (data) => onload(new Uint8Array(data as ArrayBuffer)), + undefined, + undefined, + true, + (request?: IWebRequest, exception?: any) => { + onInternalError("Unable to load " + (request ? request.responseURL : url, exception)); + } + ); + } + } + } + + return texture; + } + + /** + * Wraps an external native texture in a Babylon texture. + * @param texture defines the external texture + * @param hasMipMaps defines whether the external texture has mip maps + * @param samplingMode defines the sampling mode for the external texture (default: Constants.TEXTURE_TRILINEAR_SAMPLINGMODE) + * @returns the babylon internal texture + */ + public wrapNativeTexture(texture: NativeTexture, hasMipMaps: boolean = false, samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE): InternalTexture { + const hardwareTexture = new NativeHardwareTexture(texture, this._engine); + const internalTexture = new InternalTexture(this, InternalTextureSource.Unknown, true); + internalTexture._hardwareTexture = hardwareTexture; + internalTexture.baseWidth = this._engine.getTextureWidth(texture); + internalTexture.baseHeight = this._engine.getTextureHeight(texture); + internalTexture.width = internalTexture.baseWidth; + internalTexture.height = internalTexture.baseHeight; + internalTexture.isReady = true; + internalTexture.useMipMaps = hasMipMaps; + this.updateTextureSamplingMode(samplingMode, internalTexture); + return internalTexture; + } + + public override _createDepthStencilTexture(size: TextureSize, options: DepthTextureCreationOptions, rtWrapper: RenderTargetWrapper): InternalTexture { + // TODO: handle other options? + const generateStencil = options.generateStencil || false; + const samples = options.samples || 1; + + const nativeRTWrapper = rtWrapper as NativeRenderTargetWrapper; + const texture = new InternalTexture(this, InternalTextureSource.DepthStencil); + + const width = (<{ width: number; height: number; layers?: number }>size).width ?? size; + const height = (<{ width: number; height: number; layers?: number }>size).height ?? size; + + const framebuffer = this._engine.createFrameBuffer(texture._hardwareTexture!.underlyingResource, width, height, generateStencil, true, samples); + nativeRTWrapper._framebufferDepthStencil = framebuffer; + return texture; + } + + /** + * @internal + */ + public _releaseFramebufferObjects(framebuffer: Nullable): void { + if (framebuffer) { + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_DELETEFRAMEBUFFER); + this._commandBufferEncoder.encodeCommandArgAsNativeData(framebuffer); + this._commandBufferEncoder.finishEncodingCommand(); + } + } + + /** + * @internal Engine abstraction for loading and creating an image bitmap from a given source string. + * @param imageSource source to load the image from. + * @param _options An object that sets options for the image's extraction. + * @returns ImageBitmap + */ + public override async _createImageBitmapFromSource(imageSource: string, _options?: ImageBitmapOptions): Promise { + const promise = new Promise((resolve, reject) => { + const image = this.createCanvasImage(); + image.onload = () => { + try { + const imageBitmap = this._engine.createImageBitmap(image); + resolve(imageBitmap); + } catch (error) { + // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors + reject(`Error loading image ${image.src} with exception: ${error}`); + } + }; + image.onerror = (error) => { + // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors + reject(`Error loading image ${image.src} with exception: ${error}`); + }; + + image.src = imageSource; + }); + + return await promise; + } + + /** + * Engine abstraction for createImageBitmap + * @param image source for image + * @param options An object that sets options for the image's extraction. + * @returns ImageBitmap + */ + public override async createImageBitmap(image: ImageBitmapSource, options?: ImageBitmapOptions): Promise { + // Back-compat: Because of the previous Blob hack, this could be an array of BlobParts. + if (Array.isArray(image)) { + const arr = >image; + if (arr.length) { + return this._engine.createImageBitmap(arr[0]); + } + } + + if (image instanceof Blob) { + const data = await image.arrayBuffer(); + return this._engine.createImageBitmap(data); + } + + throw new Error("Unsupported data for createImageBitmap."); + } + + /** + * Resize an image and returns the image data as an uint8array + * @param image image to resize + * @param bufferWidth destination buffer width + * @param bufferHeight destination buffer height + * @returns an uint8array containing RGBA values of bufferWidth * bufferHeight size + */ + public override resizeImageBitmap(image: ImageBitmap, bufferWidth: number, bufferHeight: number): Uint8Array { + return this._engine.resizeImageBitmap(image, bufferWidth, bufferHeight); + } + + /** @internal */ + public override _createHardwareTexture(): IHardwareTextureWrapper { + return new NativeHardwareTexture(this._createTexture() as NativeTexture, this._engine); + } + + /** @internal */ + public override _createHardwareRenderTargetWrapper(isMulti: boolean, isCube: boolean, size: TextureSize): RenderTargetWrapper { + const rtWrapper = new NativeRenderTargetWrapper(isMulti, isCube, size, this); + this._renderTargetWrapperCache.push(rtWrapper); + return rtWrapper; + } + + /** @internal */ + public override _createInternalTexture( + size: TextureSize, + options: boolean | InternalTextureCreationOptions, + _delayGPUTextureCreation = true, + source = InternalTextureSource.Unknown + ): InternalTexture { + let generateMipMaps = false; + let type = Constants.TEXTURETYPE_UNSIGNED_BYTE; + let samplingMode = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE; + let format = Constants.TEXTUREFORMAT_RGBA; + let useSRGBBuffer = false; + let samples = 1; + let label: string | undefined; + if (options !== undefined && typeof options === "object") { + generateMipMaps = !!options.generateMipMaps; + type = options.type === undefined ? Constants.TEXTURETYPE_UNSIGNED_BYTE : options.type; + samplingMode = options.samplingMode === undefined ? Constants.TEXTURE_TRILINEAR_SAMPLINGMODE : options.samplingMode; + format = options.format === undefined ? Constants.TEXTUREFORMAT_RGBA : options.format; + useSRGBBuffer = options.useSRGBBuffer === undefined ? false : options.useSRGBBuffer; + samples = options.samples ?? 1; + label = options.label; + } else { + generateMipMaps = !!options; + } + + useSRGBBuffer = this._getUseSRGBBuffer(useSRGBBuffer, !generateMipMaps); + + if (type === Constants.TEXTURETYPE_FLOAT && !this._caps.textureFloatLinearFiltering) { + // if floating point linear (gl.FLOAT) then force to NEAREST_SAMPLINGMODE + samplingMode = Constants.TEXTURE_NEAREST_SAMPLINGMODE; + } else if (type === Constants.TEXTURETYPE_HALF_FLOAT && !this._caps.textureHalfFloatLinearFiltering) { + // if floating point linear (HALF_FLOAT) then force to NEAREST_SAMPLINGMODE + samplingMode = Constants.TEXTURE_NEAREST_SAMPLINGMODE; + } + if (type === Constants.TEXTURETYPE_FLOAT && !this._caps.textureFloat) { + type = Constants.TEXTURETYPE_UNSIGNED_BYTE; + Logger.Warn("Float textures are not supported. Type forced to TEXTURETYPE_UNSIGNED_BYTE"); + } + + const texture = new InternalTexture(this, source); + const width = (<{ width: number; height: number; layers?: number }>size).width ?? size; + const height = (<{ width: number; height: number; layers?: number }>size).height ?? size; + + const layers = (<{ width: number; height: number; layers?: number }>size).layers || 0; + if (layers !== 0) { + throw new Error("Texture layers are not supported in Babylon Native"); + } + + const nativeTexture = texture._hardwareTexture!.underlyingResource; + const nativeTextureFormat = getNativeTextureFormat(format, type); + // REVIEW: We are always setting the renderTarget flag as we don't know whether the texture will be used as a render target. + this._engine.initializeTexture(nativeTexture, width, height, generateMipMaps, nativeTextureFormat, true, useSRGBBuffer, samples); + this._setTextureSampling(nativeTexture, getNativeSamplingMode(samplingMode)); + + texture._useSRGBBuffer = useSRGBBuffer; + texture.baseWidth = width; + texture.baseHeight = height; + texture.width = width; + texture.height = height; + texture.depth = layers; + texture.isReady = true; + texture.samples = samples; + texture.generateMipMaps = generateMipMaps; + texture.samplingMode = samplingMode; + texture.type = type; + texture.format = format; + texture.label = label; + + this._internalTexturesCache.push(texture); + + return texture; + } + + public override createRenderTargetTexture( + size: number | { width: number; height: number; depth: number }, + options: boolean | RenderTargetCreationOptions + ): RenderTargetWrapper { + const rtWrapper = this._createHardwareRenderTargetWrapper(false, false, size) as NativeRenderTargetWrapper; + + let generateDepthBuffer = true; + let generateStencilBuffer = false; + let noColorAttachment = false; + let colorAttachment: InternalTexture | undefined = undefined; + let samples = 1; + if (options !== undefined && typeof options === "object") { + generateDepthBuffer = options.generateDepthBuffer ?? true; + generateStencilBuffer = !!options.generateStencilBuffer; + noColorAttachment = !!options.noColorAttachment; + colorAttachment = options.colorAttachment; + samples = options.samples ?? 1; + } + + const texture = colorAttachment || (noColorAttachment ? null : this._createInternalTexture(size, options, true, InternalTextureSource.RenderTarget)); + const width = (<{ width: number; height: number; layers?: number }>size).width ?? size; + const height = (<{ width: number; height: number; layers?: number }>size).height ?? size; + + const framebuffer = this._engine.createFrameBuffer( + texture ? texture._hardwareTexture!.underlyingResource : null, + width, + height, + generateStencilBuffer, + generateDepthBuffer, + samples + ); + + rtWrapper._framebuffer = framebuffer; + rtWrapper._generateDepthBuffer = generateDepthBuffer; + rtWrapper._generateStencilBuffer = generateStencilBuffer; + rtWrapper._samples = samples; + + rtWrapper.setTextures(texture); + + return rtWrapper; + } + + public override updateRenderTargetTextureSampleCount(rtWrapper: RenderTargetWrapper, samples: number): number { + Logger.Warn("Updating render target sample count is not currently supported"); + return rtWrapper.samples; + } + + public override updateTextureSamplingMode(samplingMode: number, texture: InternalTexture): void { + if (texture._hardwareTexture) { + const filter = getNativeSamplingMode(samplingMode); + this._setTextureSampling(texture._hardwareTexture.underlyingResource, filter); + } + + texture.samplingMode = samplingMode; + } + + public override bindFramebuffer(texture: RenderTargetWrapper, faceIndex?: number, requiredWidth?: number, requiredHeight?: number, forceFullscreenViewport?: boolean): void { + const nativeRTWrapper = texture as NativeRenderTargetWrapper; + + if (this._currentRenderTarget) { + this.unBindFramebuffer(this._currentRenderTarget); + } + + this._currentRenderTarget = texture; + + if (faceIndex) { + throw new Error("Cuboid frame buffers are not yet supported in NativeEngine."); + } + + if (requiredWidth || requiredHeight) { + throw new Error("Required width/height for frame buffers not yet supported in NativeEngine."); + } + + if (nativeRTWrapper._framebufferDepthStencil) { + this._bindUnboundFramebuffer(nativeRTWrapper._framebufferDepthStencil); + } else { + this._bindUnboundFramebuffer(nativeRTWrapper._framebuffer); + } + } + + public override unBindFramebuffer(texture: RenderTargetWrapper, disableGenerateMipMaps = false, onBeforeUnbind?: () => void): void { + // NOTE: Disabling mipmap generation is not yet supported in NativeEngine. + + this._currentRenderTarget = null; + + if (onBeforeUnbind) { + onBeforeUnbind(); + } + + this._bindUnboundFramebuffer(null); + } + + public override createDynamicVertexBuffer(data: DataArray): DataBuffer { + return this.createVertexBuffer(data, true); + } + + public override updateDynamicIndexBuffer(indexBuffer: DataBuffer, indices: IndicesArray, offset: number = 0): void { + const buffer = indexBuffer as NativeDataBuffer; + const data = this._normalizeIndexData(indices); + buffer.is32Bits = data.BYTES_PER_ELEMENT === 4; + this._engine.updateDynamicIndexBuffer(buffer.nativeIndexBuffer!, data.buffer, data.byteOffset, data.byteLength, offset); + } + + public override updateDynamicVertexBuffer(vertexBuffer: DataBuffer, data: DataArray, byteOffset = 0, byteLength?: number): void { + const buffer = vertexBuffer as NativeDataBuffer; + const dataView = data instanceof Array ? new Float32Array(data) : data instanceof ArrayBuffer ? new Uint8Array(data) : data; + const byteView = new Uint8Array(dataView.buffer, dataView.byteOffset, byteLength ?? dataView.byteLength); + this._engine.updateDynamicVertexBuffer(buffer.nativeVertexBuffer!, byteView.buffer, byteView.byteOffset, byteView.byteLength, byteOffset); + } + + // TODO: Refactor to share more logic with base Engine implementation. + /** + * @internal + */ + public override _setTexture(channel: number, texture: Nullable, isPartOfTextureArray = false, depthStencilTexture = false): boolean { + const uniform = this._boundUniforms[channel] as unknown as NativeUniform; + if (!uniform) { + return false; + } + + // Not ready? + if (!texture) { + if (this._boundTexturesCache[channel] != null) { + this._activeChannel = channel; + this._boundTexturesCache[channel] = null; + this._unsetNativeTexture(uniform); + } + return false; + } + + // Video + if ((texture).video) { + this._activeChannel = channel; + (texture).update(); + } else if (texture.delayLoadState === Constants.DELAYLOADSTATE_NOTLOADED) { + // Delay loading + texture.delayLoad(); + return false; + } + + let internalTexture: InternalTexture; + if (depthStencilTexture) { + internalTexture = (texture).depthStencilTexture!; + } else if (texture.isReady()) { + internalTexture = texture.getInternalTexture(); + } else if (texture.isCube) { + internalTexture = this.emptyCubeTexture; + } else if (texture.is3D) { + internalTexture = this.emptyTexture3D; + } else if (texture.is2DArray) { + internalTexture = this.emptyTexture2DArray; + } else { + internalTexture = this.emptyTexture; + } + + this._activeChannel = channel; + + if (!internalTexture || !internalTexture._hardwareTexture) { + return false; + } + + this._setTextureWrapMode( + internalTexture._hardwareTexture.underlyingResource, + getNativeAddressMode(texture.wrapU), + getNativeAddressMode(texture.wrapV), + getNativeAddressMode(texture.wrapR) + ); + this._updateAnisotropicLevel(texture); + + this._setNativeTexture(uniform, internalTexture._hardwareTexture.underlyingResource); + + return true; + } + + // filter is a NativeFilter.XXXX value. + private _setTextureSampling(texture: NativeTexture, filter: number) { + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETTEXTURESAMPLING); + this._commandBufferEncoder.encodeCommandArgAsNativeData(texture); + this._commandBufferEncoder.encodeCommandArgAsUInt32(filter); + this._commandBufferEncoder.finishEncodingCommand(); + } + + // addressModes are NativeAddressMode.XXXX values. + private _setTextureWrapMode(texture: NativeTexture, addressModeU: number, addressModeV: number, addressModeW: number) { + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETTEXTUREWRAPMODE); + this._commandBufferEncoder.encodeCommandArgAsNativeData(texture); + this._commandBufferEncoder.encodeCommandArgAsUInt32(addressModeU); + this._commandBufferEncoder.encodeCommandArgAsUInt32(addressModeV); + this._commandBufferEncoder.encodeCommandArgAsUInt32(addressModeW); + this._commandBufferEncoder.finishEncodingCommand(); + } + + private _setNativeTexture(uniform: NativeUniform, texture: NativeTexture) { + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETTEXTURE); + this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform); + this._commandBufferEncoder.encodeCommandArgAsNativeData(texture); + this._commandBufferEncoder.finishEncodingCommand(); + } + + private _unsetNativeTexture(uniform: NativeUniform) { + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_UNSETTEXTURE); + this._commandBufferEncoder.encodeCommandArgAsNativeData(uniform); + this._commandBufferEncoder.finishEncodingCommand(); + } + + // TODO: Share more of this logic with the base implementation. + // TODO: Rename to match naming in base implementation once refactoring allows different parameters. + private _updateAnisotropicLevel(texture: BaseTexture) { + const internalTexture = texture.getInternalTexture(); + const value = texture.anisotropicFilteringLevel; + + if (!internalTexture || !internalTexture._hardwareTexture) { + return; + } + + if (internalTexture._cachedAnisotropicFilteringLevel !== value) { + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_SETTEXTUREANISOTROPICLEVEL); + this._commandBufferEncoder.encodeCommandArgAsNativeData(internalTexture._hardwareTexture.underlyingResource); + this._commandBufferEncoder.encodeCommandArgAsUInt32(value); + this._commandBufferEncoder.finishEncodingCommand(); + internalTexture._cachedAnisotropicFilteringLevel = value; + } + } + + /** + * @internal + */ + public override _bindTexture(channel: number, texture: Nullable): void { + const uniform = this._boundUniforms[channel] as unknown as NativeUniform; + if (!uniform) { + return; + } + + if (texture && texture._hardwareTexture) { + const underlyingResource = texture._hardwareTexture.underlyingResource; + this._setNativeTexture(uniform, underlyingResource); + } else { + this._unsetNativeTexture(uniform); + } + } + + /** + * Unbind all textures + */ + public override unbindAllTextures(): void { + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_DISCARDALLTEXTURES); + this._commandBufferEncoder.finishEncodingCommand(); + } + + protected override _deleteBuffer(buffer: NativeDataBuffer): void { + if (buffer.nativeIndexBuffer) { + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_DELETEINDEXBUFFER); + this._commandBufferEncoder.encodeCommandArgAsNativeData(buffer.nativeIndexBuffer); + this._commandBufferEncoder.finishEncodingCommand(); + delete buffer.nativeIndexBuffer; + } + + if (buffer.nativeVertexBuffer) { + this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_DELETEVERTEXBUFFER); + this._commandBufferEncoder.encodeCommandArgAsNativeData(buffer.nativeVertexBuffer); + this._commandBufferEncoder.finishEncodingCommand(); + delete buffer.nativeVertexBuffer; + } + } + + /** + * Create a canvas + * @param width width + * @param height height + * @returns ICanvas interface + */ + public override createCanvas(width: number, height: number): ICanvas { + if (!_native.Canvas) { + throw new Error("Native Canvas plugin not available."); + } + const canvas = new _native.Canvas(); + canvas.width = width; + canvas.height = height; + return canvas; + } + + /** + * Create an image to use with canvas + * @returns IImage interface + */ + public override createCanvasImage(): IImage { + if (!_native.Image) { + throw new Error("Native Canvas plugin not available."); + } + const image = new _native.Image(); + return image; + } + + /** + * Create a 2D path to use with canvas + * @returns IPath2D interface + * @param d SVG path string + */ + public override createCanvasPath2D(d?: string): IPath2D { + if (!_native.Path2D) { + throw new Error("Native Canvas plugin not available."); + } + const path2d = new _native.Path2D(d); + return path2d; + } + + /** + * Update a portion of an internal texture + * @param texture defines the texture to update + * @param imageData defines the data to store into the texture + * @param xOffset defines the x coordinates of the update rectangle + * @param yOffset defines the y coordinates of the update rectangle + * @param width defines the width of the update rectangle + * @param height defines the height of the update rectangle + * @param faceIndex defines the face index if texture is a cube (0 by default) + * @param lod defines the lod level to update (0 by default) + * @param generateMipMaps defines whether to generate mipmaps or not + */ + public override updateTextureData( + texture: InternalTexture, + imageData: ArrayBufferView, + xOffset: number, + yOffset: number, + width: number, + height: number, + faceIndex: number = 0, + lod: number = 0, + generateMipMaps = false + ): void { + throw new Error("updateTextureData not implemented."); + } + + /** + * @internal + */ + public override _uploadCompressedDataToTextureDirectly( + texture: InternalTexture, + internalFormat: number, + width: number, + height: number, + data: ArrayBufferView, + faceIndex: number = 0, + lod: number = 0 + ) { + throw new Error("_uploadCompressedDataToTextureDirectly not implemented."); + } + + /** + * @internal + */ + public override _uploadDataToTextureDirectly(texture: InternalTexture, imageData: ArrayBufferView, faceIndex: number = 0, lod: number = 0): void { + throw new Error("_uploadDataToTextureDirectly not implemented."); + } + + /** + * @internal + */ + public override _uploadArrayBufferViewToTexture(texture: InternalTexture, imageData: ArrayBufferView, faceIndex: number = 0, lod: number = 0): void { + throw new Error("_uploadArrayBufferViewToTexture not implemented."); + } + + public override getFontOffset(font: string): { ascent: number; height: number; descent: number } { + // TODO + const result = { ascent: 0, height: 0, descent: 0 }; + return result; + } + + /** + * No equivalent for native. Do nothing. + */ + public override flushFramebuffer(): void {} + + // eslint-disable-next-line @typescript-eslint/promise-function-async + public override _readTexturePixels( + texture: InternalTexture, + width: number, + height: number, + faceIndex?: number, + level?: number, + buffer?: Nullable>, + _flushRenderer?: boolean, + _noDataConversion?: boolean, + x?: number, + y?: number + ): Promise { + if (faceIndex !== undefined && faceIndex !== -1) { + throw new Error(`Reading cubemap faces is not supported, but faceIndex is ${faceIndex}.`); + } + + return ( + this._engine + .readTexture( + texture._hardwareTexture?.underlyingResource, + level ?? 0, + x ?? 0, + y ?? 0, + width, + height, + buffer?.buffer ?? null, + buffer?.byteOffset ?? 0, + buffer?.byteLength ?? 0 + ) + // eslint-disable-next-line github/no-then + .then((rawBuffer) => { + if (!buffer) { + buffer = new Uint8Array(rawBuffer); + } + + return buffer; + }) + ); + } + + override startTimeQuery(): Nullable<_TimeToken> { + if (!this._gpuFrameTimeToken) { + this._gpuFrameTimeToken = new _TimeToken(); + } + + // Always return the same time token. For native, we don't need a start marker, we just query for native frame stats. + return this._gpuFrameTimeToken; + } + + override endTimeQuery(token: _TimeToken): int { + this._engine.populateFrameStats(this._frameStats); + return this._frameStats.gpuTimeNs; + } +} diff --git a/packages/dev/core/src/Engines/webgpuEngine.ts b/packages/dev/core/src/Engines/webgpuEngine.ts index 75d24b28444..75c02c4715e 100644 --- a/packages/dev/core/src/Engines/webgpuEngine.ts +++ b/packages/dev/core/src/Engines/webgpuEngine.ts @@ -84,6 +84,8 @@ import "./AbstractEngine/abstractEngine.dom"; import "./AbstractEngine/abstractEngine.states"; import "./AbstractEngine/abstractEngine.stencil"; import "./AbstractEngine/abstractEngine.renderPass"; +import "./AbstractEngine/abstractEngine.loadFile"; +import "./AbstractEngine/abstractEngine.textureLoaders"; import "../Audio/audioEngine"; import { resetCachedPipeline } from "../Materials/effect.functions"; diff --git a/packages/dev/core/src/Misc/tools.ts b/packages/dev/core/src/Misc/tools.ts index e53428ff187..f009a1a66dd 100644 --- a/packages/dev/core/src/Misc/tools.ts +++ b/packages/dev/core/src/Misc/tools.ts @@ -575,16 +575,29 @@ export class Tools { return await Tools.LoadScriptAsync(scriptUrl); } - /** - * This function is used internally by babylon components to load a script (identified by an url). When the url returns, the - * content of this file is added into a new script element, attached to the DOM (body element) - * @param scriptUrl defines the url of the script to load - * @param onSuccess defines the callback called when the script is loaded - * @param onError defines the callback to call if an error occurs - * @param scriptId defines the id of the script element - * @param useModule defines if we should use the module strategy to load the script - */ - public static LoadScript(scriptUrl: string, onSuccess?: () => void, onError?: (message?: string, exception?: any) => void, scriptId?: string, useModule = false) { + private static _LoadScriptNative(scriptUrl: string, onSuccess?: () => void, onError?: (message?: string, exception?: any) => void) { + if (_native) { + Tools.LoadFile( + scriptUrl, + (data) => { + Function(data as string).apply(null); + if (onSuccess) { + onSuccess(); + } + }, + undefined, + undefined, + false, + (_request, exception) => { + if (onError) { + onError("LoadScript Error", exception); + } + } + ); + } + } + + private static _LoadScriptWeb(scriptUrl: string, onSuccess?: () => void, onError?: (message?: string, exception?: any) => void, scriptId?: string, useModule = false) { if (typeof importScripts === "function") { try { importScripts(scriptUrl); @@ -627,6 +640,18 @@ export class Tools { head.appendChild(script); } + /** + * This function is used internally by babylon components to load a script (identified by an url). When the url returns, the + * content of this file is added into a new script element, attached to the DOM (body element) + * @param scriptUrl defines the url of the script to load + * @param onSuccess defines the callback called when the script is loaded + * @param onError defines the callback to call if an error occurs + * @param scriptId defines the id of the script element + * @param useModule defines if we should use the module strategy to load the script + */ + public static LoadScript: (scriptUrl: string, onSuccess?: () => void, onError?: (message?: string, exception?: any) => void, scriptId?: string, useModule?: boolean) => void = + typeof _native === "undefined" ? this._LoadScriptWeb : this._LoadScriptNative; + /** * Load an asynchronous script (identified by an url). When the url returns, the * content of this file is added into a new script element, attached to the DOM (body element) diff --git a/packages/dev/core/src/XR/native/nativeXRFrame.ts b/packages/dev/core/src/XR/native/nativeXRFrame.ts index 3b48a7d7547..aaf9f34ff44 100644 --- a/packages/dev/core/src/XR/native/nativeXRFrame.ts +++ b/packages/dev/core/src/XR/native/nativeXRFrame.ts @@ -1,4 +1,4 @@ -import { RegisterNativeTypeAsync } from "../../Engines/nativeEngine"; +import { RegisterNativeTypeAsync } from "../../Engines/thinNativeEngine"; /** @internal */ interface INativeXRFrame extends XRFrame {