UNPKG

@babylonjs/core

Version:

Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.

1,066 lines (1,064 loc) 151 kB
/* eslint-disable babylonjs/available */ import { Logger } from "../Misc/logger.js"; import { ThinWebGPUEngine } from "./thinWebGPUEngine.js"; import { Color4 } from "../Maths/math.js"; import { InternalTexture } from "../Materials/Textures/internalTexture.js"; import { Effect } from "../Materials/effect.js"; // eslint-disable-next-line @typescript-eslint/naming-convention import * as WebGPUConstants from "./WebGPU/webgpuConstants.js"; import { VertexBuffer } from "../Buffers/buffer.js"; import { WebGPUPipelineContext } from "./WebGPU/webgpuPipelineContext.js"; import { WebGPUShaderProcessorGLSL } from "./WebGPU/webgpuShaderProcessorsGLSL.js"; import { WebGPUShaderProcessorWGSL } from "./WebGPU/webgpuShaderProcessorsWGSL.js"; import { WebGPUShaderProcessingContext } from "./WebGPU/webgpuShaderProcessingContext.js"; import { Tools } from "../Misc/tools.js"; import { WebGPUTextureHelper } from "./WebGPU/webgpuTextureHelper.js"; import { WebGPUTextureManager } from "./WebGPU/webgpuTextureManager.js"; import { AbstractEngine } from "./abstractEngine.js"; import { WebGPUBufferManager } from "./WebGPU/webgpuBufferManager.js"; import { WebGPUHardwareTexture } from "./WebGPU/webgpuHardwareTexture.js"; import { UniformBuffer } from "../Materials/uniformBuffer.js"; import { WebGPUCacheSampler } from "./WebGPU/webgpuCacheSampler.js"; import { WebGPUCacheRenderPipelineTree } from "./WebGPU/webgpuCacheRenderPipelineTree.js"; import { WebGPUStencilStateComposer } from "./WebGPU/webgpuStencilStateComposer.js"; import { WebGPUDepthCullingState } from "./WebGPU/webgpuDepthCullingState.js"; import { WebGPUMaterialContext } from "./WebGPU/webgpuMaterialContext.js"; import { WebGPUDrawContext } from "./WebGPU/webgpuDrawContext.js"; import { WebGPUCacheBindGroups } from "./WebGPU/webgpuCacheBindGroups.js"; import { WebGPUClearQuad } from "./WebGPU/webgpuClearQuad.js"; import { WebGPURenderItemBlendColor, WebGPURenderItemScissor, WebGPURenderItemStencilRef, WebGPURenderItemViewport, WebGPUBundleList } from "./WebGPU/webgpuBundleList.js"; import { WebGPUTimestampQuery } from "./WebGPU/webgpuTimestampQuery.js"; import { WebGPUOcclusionQuery } from "./WebGPU/webgpuOcclusionQuery.js"; import { ShaderCodeInliner } from "./Processors/shaderCodeInliner.js"; import { WebGPUTintWASM } from "./WebGPU/webgpuTintWASM.js"; import { WebGPUShaderProcessor } from "./WebGPU/webgpuShaderProcessor.js"; import { WebGPUSnapshotRendering } from "./WebGPU/webgpuSnapshotRendering.js"; import "../Buffers/buffer.align.js"; import { SphericalPolynomial } from "../Maths/sphericalPolynomial.js"; import { PerformanceMonitor } from "../Misc/performanceMonitor.js"; import { CreateImageBitmapFromSource, ExitFullscreen, ExitPointerlock, GetFontOffset, RequestFullscreen, RequestPointerlock, ResizeImageBitmap, _CommonDispose, _CommonInit, } from "./engine.common.js"; import { IsWrapper } from "../Materials/drawWrapper.functions.js"; import { PerfCounter } from "../Misc/perfCounter.js"; import "./AbstractEngine/abstractEngine.loadingScreen.js"; import "./AbstractEngine/abstractEngine.dom.js"; import "./AbstractEngine/abstractEngine.states.js"; import "./AbstractEngine/abstractEngine.renderPass.js"; import "../Audio/audioEngine.js"; import { resetCachedPipeline } from "../Materials/effect.functions.js"; import { WebGPUExternalTexture } from "./WebGPU/webgpuExternalTexture.js"; import "./WebGPU/Extensions/engine.alpha.js"; import "./WebGPU/Extensions/engine.rawTexture.js"; import "./WebGPU/Extensions/engine.readTexture.js"; import "./WebGPU/Extensions/engine.cubeTexture.js"; import "./WebGPU/Extensions/engine.renderTarget.js"; import "./WebGPU/Extensions/engine.renderTargetTexture.js"; import "./WebGPU/Extensions/engine.renderTargetCube.js"; import "./WebGPU/Extensions/engine.query.js"; const viewDescriptorSwapChainAntialiasing = { label: `TextureView_SwapChain_ResolveTarget`, dimension: "2d" /* WebGPUConstants.TextureDimension.E2d */, format: undefined, // will be updated with the right value mipLevelCount: 1, arrayLayerCount: 1, }; const viewDescriptorSwapChain = { label: `TextureView_SwapChain`, dimension: "2d" /* WebGPUConstants.TextureDimension.E2d */, format: undefined, // will be updated with the right value mipLevelCount: 1, arrayLayerCount: 1, }; const tempColor4 = new Color4(); /** * The web GPU engine class provides support for WebGPU version of babylon.js. * @since 5.0.0 */ export class WebGPUEngine extends ThinWebGPUEngine { /** * Gets or sets the snapshot rendering mode */ get snapshotRenderingMode() { return this._snapshotRendering.mode; } set snapshotRenderingMode(mode) { this._snapshotRendering.mode = mode; } /** * Creates a new snapshot at the next frame using the current snapshotRenderingMode */ snapshotRenderingReset() { this._snapshotRendering.reset(); } /** * Enables or disables the snapshot rendering mode * Note that the WebGL engine does not support snapshot rendering so setting the value won't have any effect for this engine */ get snapshotRendering() { return this._snapshotRendering.enabled; } set snapshotRendering(activate) { this._snapshotRendering.enabled = activate; } /** * Sets this to true to disable the cache for the samplers. You should do it only for testing purpose! */ get disableCacheSamplers() { return this._cacheSampler ? this._cacheSampler.disabled : false; } set disableCacheSamplers(disable) { if (this._cacheSampler) { this._cacheSampler.disabled = disable; } } /** * Sets this to true to disable the cache for the render pipelines. You should do it only for testing purpose! */ get disableCacheRenderPipelines() { return this._cacheRenderPipeline ? this._cacheRenderPipeline.disabled : false; } set disableCacheRenderPipelines(disable) { if (this._cacheRenderPipeline) { this._cacheRenderPipeline.disabled = disable; } } /** * Sets this to true to disable the cache for the bind groups. You should do it only for testing purpose! */ get disableCacheBindGroups() { return this._cacheBindGroups ? this._cacheBindGroups.disabled : false; } set disableCacheBindGroups(disable) { if (this._cacheBindGroups) { this._cacheBindGroups.disabled = disable; } } /** * Gets a boolean indicating if all created effects are ready * @returns true if all effects are ready */ areAllEffectsReady() { return true; } /** * Get Font size information * @param font font name * @returns an object containing ascent, height and descent */ getFontOffset(font) { return GetFontOffset(font); } /** * Gets a Promise<boolean> indicating if the engine can be instantiated (ie. if a WebGPU context can be found) */ static get IsSupportedAsync() { return !navigator.gpu ? Promise.resolve(false) : navigator.gpu .requestAdapter() .then((adapter) => !!adapter, () => false) .catch(() => false); } /** * Not supported by WebGPU, you should call IsSupportedAsync instead! */ static get IsSupported() { Logger.Warn("You must call IsSupportedAsync for WebGPU!"); return false; } /** * Gets a boolean indicating that the engine supports uniform buffers */ get supportsUniformBuffers() { return true; } /** Gets the supported extensions by the WebGPU adapter */ get supportedExtensions() { return this._adapterSupportedExtensions; } /** Gets the currently enabled extensions on the WebGPU device */ get enabledExtensions() { return this._deviceEnabledExtensions; } /** Gets the supported limits by the WebGPU adapter */ get supportedLimits() { return this._adapterSupportedLimits; } /** Gets the current limits of the WebGPU device */ get currentLimits() { return this._deviceLimits; } /** * Returns a string describing the current engine */ get description() { const description = this.name + this.version; return description; } /** * Returns the version of the engine */ get version() { return 1; } /** * Gets an object containing information about the current engine context * @returns an object containing the vendor, the renderer and the version of the current engine context */ getInfo() { return { vendor: this._adapterInfo.vendor || "unknown vendor", renderer: this._adapterInfo.architecture || "unknown renderer", version: this._adapterInfo.description || "unknown version", }; } /** * (WebGPU only) True (default) to be in compatibility mode, meaning rendering all existing scenes without artifacts (same rendering than WebGL). * Setting the property to false will improve performances but may not work in some scenes if some precautions are not taken. * See https://doc.babylonjs.com/setup/support/webGPU/webGPUOptimization/webGPUNonCompatibilityMode for more details */ get compatibilityMode() { return this._compatibilityMode; } set compatibilityMode(mode) { this._compatibilityMode = mode; } /** @internal */ get currentSampleCount() { return this._currentRenderTarget ? this._currentRenderTarget.samples : this._mainPassSampleCount; } /** * Create a new instance of the gpu engine asynchronously * @param canvas Defines the canvas to use to display the result * @param options Defines the options passed to the engine to create the GPU context dependencies * @returns a promise that resolves with the created engine */ static CreateAsync(canvas, options = {}) { const engine = new WebGPUEngine(canvas, options); return new Promise((resolve) => { engine.initAsync(options.glslangOptions, options.twgslOptions).then(() => resolve(engine)); }); } /** * Create a new instance of the gpu engine. * @param canvas Defines the canvas to use to display the result * @param options Defines the options passed to the engine to create the GPU context dependencies */ constructor(canvas, options = {}) { super(options.antialias ?? true, options); /** A unique id to identify this instance */ this.uniqueId = -1; // Page Life cycle and constants this._uploadEncoderDescriptor = { label: "upload" }; this._renderEncoderDescriptor = { label: "render" }; /** @internal */ this._clearDepthValue = 1; /** @internal */ this._clearReverseDepthValue = 0; /** @internal */ this._clearStencilValue = 0; this._defaultSampleCount = 4; // Only supported value for now. this._glslang = null; this._tintWASM = null; this._glslangAndTintAreFullyLoaded = false; this._adapterInfo = { vendor: "", architecture: "", device: "", description: "", }; /** @internal */ this._compiledComputeEffects = {}; /** @internal */ this._counters = { numEnableEffects: 0, numEnableDrawWrapper: 0, numBundleCreationNonCompatMode: 0, numBundleReuseNonCompatMode: 0, }; /** * Counters from last frame */ this.countersLastFrame = { numEnableEffects: 0, numEnableDrawWrapper: 0, numBundleCreationNonCompatMode: 0, numBundleReuseNonCompatMode: 0, }; /** * Max number of uncaptured error messages to log */ this.numMaxUncapturedErrors = 20; /** * Gets the list of created scenes */ this.scenes = []; /** @internal */ this._virtualScenes = new Array(); this._commandBuffers = [null, null]; // Frame Buffer Life Cycle (recreated for each render target pass) this._mainRenderPassWrapper = { renderPassDescriptor: null, colorAttachmentViewDescriptor: null, depthAttachmentViewDescriptor: null, colorAttachmentGPUTextures: [], depthTextureFormat: undefined, }; this._rttRenderPassWrapper = { renderPassDescriptor: null, colorAttachmentViewDescriptor: null, depthAttachmentViewDescriptor: null, colorAttachmentGPUTextures: [], depthTextureFormat: undefined, }; /** @internal */ this._pendingDebugCommands = []; this._currentOverrideVertexBuffers = null; this._currentIndexBuffer = null; this._colorWriteLocal = true; this._forceEnableEffect = false; /** * Indicates if the z range in NDC space is 0..1 (value: true) or -1..1 (value: false) */ this.isNDCHalfZRange = true; /** * Indicates that the origin of the texture/framebuffer space is the bottom left corner. If false, the origin is top left */ this.hasOriginBottomLeft = false; //------------------------------------------------------------------------------ // Initialization //------------------------------------------------------------------------------ this._workingGlslangAndTintPromise = null; //------------------------------------------------------------------------------ // Dynamic WebGPU States //------------------------------------------------------------------------------ // index 0 is for main render pass, 1 for RTT render pass this._viewportsCurrent = { x: 0, y: 0, w: 0, h: 0 }; this._scissorsCurrent = { x: 0, y: 0, w: 0, h: 0 }; this._scissorCached = { x: 0, y: 0, z: 0, w: 0 }; this._stencilRefsCurrent = -1; this._blendColorsCurrent = [null, null, null, null]; this._performanceMonitor = new PerformanceMonitor(); this._name = "WebGPU"; this._drawCalls = new PerfCounter(); options.deviceDescriptor = options.deviceDescriptor || {}; options.enableGPUDebugMarkers = options.enableGPUDebugMarkers ?? false; Logger.Log(`Babylon.js v${AbstractEngine.Version} - ${this.description} engine`); if (!navigator.gpu) { Logger.Error("WebGPU is not supported by your browser."); return; } options.swapChainFormat = options.swapChainFormat || navigator.gpu.getPreferredCanvasFormat(); this._isWebGPU = true; this._shaderPlatformName = "WEBGPU"; this._renderingCanvas = canvas; this._options = options; this._mainPassSampleCount = options.antialias ? this._defaultSampleCount : 1; if (navigator && navigator.userAgent) { this._setupMobileChecks(); } this._sharedInit(this._renderingCanvas); this._shaderProcessor = new WebGPUShaderProcessorGLSL(); this._shaderProcessorWGSL = new WebGPUShaderProcessorWGSL(); } /** * Load the glslang and tintWASM libraries and prepare them for use. * @returns a promise that resolves when the engine is ready to use the glslang and tintWASM */ prepareGlslangAndTintAsync() { if (!this._workingGlslangAndTintPromise) { this._workingGlslangAndTintPromise = new Promise((resolve) => { this._initGlslang(this._glslangOptions ?? this._options?.glslangOptions).then((glslang) => { this._glslang = glslang; this._tintWASM = new WebGPUTintWASM(); this._tintWASM.initTwgsl(this._twgslOptions ?? this._options?.twgslOptions).then(() => { this._glslangAndTintAreFullyLoaded = true; resolve(); }); }); }); } return this._workingGlslangAndTintPromise; } /** * Initializes the WebGPU context and dependencies. * @param glslangOptions Defines the GLSLang compiler options if necessary * @param twgslOptions Defines the Twgsl compiler options if necessary * @returns a promise notifying the readiness of the engine. */ initAsync(glslangOptions, twgslOptions) { this.uniqueId = WebGPUEngine._InstanceId++; this._glslangOptions = glslangOptions; this._twgslOptions = twgslOptions; return navigator .gpu.requestAdapter(this._options) .then((adapter) => { if (!adapter) { // eslint-disable-next-line no-throw-literal throw "Could not retrieve a WebGPU adapter (adapter is null)."; } else { this._adapter = adapter; this._adapterSupportedExtensions = []; this._adapter.features?.forEach((feature) => { this._adapterSupportedExtensions.push(feature); }); this._adapterSupportedLimits = this._adapter.limits; this._adapterInfo = this._adapter.info; const deviceDescriptor = this._options.deviceDescriptor ?? {}; const requiredFeatures = deviceDescriptor?.requiredFeatures ?? (this._options.enableAllFeatures ? this._adapterSupportedExtensions : undefined); if (requiredFeatures) { const requestedExtensions = requiredFeatures; const validExtensions = []; for (const extension of requestedExtensions) { if (this._adapterSupportedExtensions.indexOf(extension) !== -1) { validExtensions.push(extension); } } deviceDescriptor.requiredFeatures = validExtensions; } if (this._options.setMaximumLimits && !deviceDescriptor.requiredLimits) { deviceDescriptor.requiredLimits = {}; for (const name in this._adapterSupportedLimits) { if (name === "minSubgroupSize" || name === "maxSubgroupSize") { // Chrome exposes these limits in "webgpu developer" mode, but these can't be set on the device. continue; } deviceDescriptor.requiredLimits[name] = this._adapterSupportedLimits[name]; } } deviceDescriptor.label = `BabylonWebGPUDevice${this.uniqueId}`; return this._adapter.requestDevice(deviceDescriptor); } }) .then((device) => { this._device = device; this._deviceEnabledExtensions = []; this._device.features?.forEach((feature) => { this._deviceEnabledExtensions.push(feature); }); this._deviceLimits = device.limits; let numUncapturedErrors = -1; this._device.addEventListener("uncapturederror", (event) => { if (++numUncapturedErrors < this.numMaxUncapturedErrors) { Logger.Warn(`WebGPU uncaptured error (${numUncapturedErrors + 1}): ${event.error} - ${event.error.message}`); } else if (numUncapturedErrors++ === this.numMaxUncapturedErrors) { Logger.Warn(`WebGPU uncaptured error: too many warnings (${this.numMaxUncapturedErrors}), no more warnings will be reported to the console for this engine.`); } }); if (!this._doNotHandleContextLost) { this._device.lost?.then((info) => { if (this._isDisposed) { return; } this._contextWasLost = true; Logger.Warn("WebGPU context lost. " + info); this.onContextLostObservable.notifyObservers(this); this._restoreEngineAfterContextLost(async () => { const snapshotRenderingMode = this.snapshotRenderingMode; const snapshotRendering = this.snapshotRendering; const disableCacheSamplers = this.disableCacheSamplers; const disableCacheRenderPipelines = this.disableCacheRenderPipelines; const disableCacheBindGroups = this.disableCacheBindGroups; const enableGPUTimingMeasurements = this.enableGPUTimingMeasurements; await this.initAsync(this._glslangOptions ?? this._options?.glslangOptions, this._twgslOptions ?? this._options?.twgslOptions); this.snapshotRenderingMode = snapshotRenderingMode; this.snapshotRendering = snapshotRendering; this.disableCacheSamplers = disableCacheSamplers; this.disableCacheRenderPipelines = disableCacheRenderPipelines; this.disableCacheBindGroups = disableCacheBindGroups; this.enableGPUTimingMeasurements = enableGPUTimingMeasurements; this._currentRenderPass = null; }); }); } }) .then(() => { this._initializeLimits(); this._bufferManager = new WebGPUBufferManager(this, this._device); this._textureHelper = new WebGPUTextureManager(this, this._device, this._bufferManager, this._deviceEnabledExtensions); this._cacheSampler = new WebGPUCacheSampler(this._device); this._cacheBindGroups = new WebGPUCacheBindGroups(this._device, this._cacheSampler, this); this._timestampQuery = new WebGPUTimestampQuery(this, this._device, this._bufferManager); this._occlusionQuery = this._device.createQuerySet ? new WebGPUOcclusionQuery(this, this._device, this._bufferManager) : undefined; this._bundleList = new WebGPUBundleList(this._device); this._snapshotRendering = new WebGPUSnapshotRendering(this, this._snapshotRenderingMode, this._bundleList); this._ubInvertY = this._bufferManager.createBuffer(new Float32Array([-1, 0]), WebGPUConstants.BufferUsage.Uniform | WebGPUConstants.BufferUsage.CopyDst, "UBInvertY"); this._ubDontInvertY = this._bufferManager.createBuffer(new Float32Array([1, 0]), WebGPUConstants.BufferUsage.Uniform | WebGPUConstants.BufferUsage.CopyDst, "UBDontInvertY"); if (this.dbgVerboseLogsForFirstFrames) { if (this._count === undefined) { this._count = 0; Logger.Log(["%c frame #" + this._count + " - begin", "background: #ffff00"]); } } this._uploadEncoder = this._device.createCommandEncoder(this._uploadEncoderDescriptor); this._renderEncoder = this._device.createCommandEncoder(this._renderEncoderDescriptor); this._emptyVertexBuffer = new VertexBuffer(this, [0], "", { stride: 1, offset: 0, size: 1, label: "EmptyVertexBuffer", }); this._cacheRenderPipeline = new WebGPUCacheRenderPipelineTree(this._device, this._emptyVertexBuffer); this._depthCullingState = new WebGPUDepthCullingState(this._cacheRenderPipeline); this._stencilStateComposer = new WebGPUStencilStateComposer(this._cacheRenderPipeline); this._stencilStateComposer.stencilGlobal = this._stencilState; this._depthCullingState.depthTest = true; this._depthCullingState.depthFunc = 515; this._depthCullingState.depthMask = true; this._textureHelper.setCommandEncoder(this._uploadEncoder); this._clearQuad = new WebGPUClearQuad(this._device, this, this._emptyVertexBuffer); this._defaultDrawContext = this.createDrawContext(); this._currentDrawContext = this._defaultDrawContext; this._defaultMaterialContext = this.createMaterialContext(); this._currentMaterialContext = this._defaultMaterialContext; this._initializeContextAndSwapChain(); this._initializeMainAttachments(); this.resize(); }) .catch((e) => { Logger.Error("A fatal error occurred during WebGPU creation/initialization."); throw e; }); } _initGlslang(glslangOptions) { glslangOptions = glslangOptions || {}; glslangOptions = { ...WebGPUEngine._GlslangDefaultOptions, ...glslangOptions, }; if (glslangOptions.glslang) { return Promise.resolve(glslangOptions.glslang); } if (self.glslang) { return self.glslang(glslangOptions.wasmPath); } if (glslangOptions.jsPath && glslangOptions.wasmPath) { return Tools.LoadBabylonScriptAsync(glslangOptions.jsPath).then(() => { return self.glslang(Tools.GetBabylonScriptURL(glslangOptions.wasmPath)); }); } return Promise.reject("gslang is not available."); } _initializeLimits() { // Init caps // TODO WEBGPU Real Capability check once limits will be working. this._caps = { maxTexturesImageUnits: this._deviceLimits.maxSampledTexturesPerShaderStage, maxVertexTextureImageUnits: this._deviceLimits.maxSampledTexturesPerShaderStage, maxCombinedTexturesImageUnits: this._deviceLimits.maxSampledTexturesPerShaderStage * 2, maxTextureSize: this._deviceLimits.maxTextureDimension2D, maxCubemapTextureSize: this._deviceLimits.maxTextureDimension2D, maxRenderTextureSize: this._deviceLimits.maxTextureDimension2D, maxVertexAttribs: this._deviceLimits.maxVertexAttributes, maxDrawBuffers: 8, maxVaryingVectors: this._deviceLimits.maxInterStageShaderVariables, maxFragmentUniformVectors: Math.floor(this._deviceLimits.maxUniformBufferBindingSize / 4), maxVertexUniformVectors: Math.floor(this._deviceLimits.maxUniformBufferBindingSize / 4), standardDerivatives: true, astc: (this._deviceEnabledExtensions.indexOf("texture-compression-astc" /* WebGPUConstants.FeatureName.TextureCompressionASTC */) >= 0 ? true : undefined), s3tc: (this._deviceEnabledExtensions.indexOf("texture-compression-bc" /* WebGPUConstants.FeatureName.TextureCompressionBC */) >= 0 ? true : undefined), pvrtc: null, etc1: null, etc2: (this._deviceEnabledExtensions.indexOf("texture-compression-etc2" /* WebGPUConstants.FeatureName.TextureCompressionETC2 */) >= 0 ? true : undefined), bptc: this._deviceEnabledExtensions.indexOf("texture-compression-bc" /* WebGPUConstants.FeatureName.TextureCompressionBC */) >= 0 ? true : undefined, maxAnisotropy: 16, // Most implementations support maxAnisotropy values in range between 1 and 16, inclusive. The used value of maxAnisotropy will be clamped to the maximum value that the platform supports. uintIndices: true, fragmentDepthSupported: true, highPrecisionShaderSupported: true, colorBufferFloat: true, supportFloatTexturesResolve: false, // See https://github.com/gpuweb/gpuweb/issues/3844 rg11b10ufColorRenderable: this._deviceEnabledExtensions.indexOf("rg11b10ufloat-renderable" /* WebGPUConstants.FeatureName.RG11B10UFloatRenderable */) >= 0, textureFloat: true, textureFloatLinearFiltering: this._deviceEnabledExtensions.indexOf("float32-filterable" /* WebGPUConstants.FeatureName.Float32Filterable */) >= 0, textureFloatRender: true, textureHalfFloat: true, textureHalfFloatLinearFiltering: true, textureHalfFloatRender: true, textureLOD: true, texelFetch: true, drawBuffersExtension: true, depthTextureExtension: true, vertexArrayObject: false, instancedArrays: true, timerQuery: typeof BigUint64Array !== "undefined" && this._deviceEnabledExtensions.indexOf("timestamp-query" /* WebGPUConstants.FeatureName.TimestampQuery */) !== -1 ? true : undefined, supportOcclusionQuery: typeof BigUint64Array !== "undefined", canUseTimestampForTimerQuery: true, multiview: false, oculusMultiview: false, parallelShaderCompile: undefined, blendMinMax: true, maxMSAASamples: 4, // the spec only supports values of 1 and 4 canUseGLInstanceID: true, canUseGLVertexID: true, supportComputeShaders: true, supportSRGBBuffers: true, supportTransformFeedbacks: false, textureMaxLevel: true, texture2DArrayMaxLayerCount: this._deviceLimits.maxTextureArrayLayers, disableMorphTargetTexture: false, textureNorm16: false, // in the works: https://github.com/gpuweb/gpuweb/issues/3001 }; this._features = { forceBitmapOverHTMLImageElement: true, supportRenderAndCopyToLodForFloatTextures: true, supportDepthStencilTexture: true, supportShadowSamplers: true, uniformBufferHardCheckMatrix: false, allowTexturePrefiltering: true, trackUbosInFrame: true, checkUbosContentBeforeUpload: true, supportCSM: true, basisNeedsPOT: false, support3DTextures: true, needTypeSuffixInShaderConstants: true, supportMSAA: true, supportSSAO2: true, supportIBLShadows: true, supportExtendedTextureFormats: true, supportSwitchCaseInShader: true, supportSyncTextureRead: false, needsInvertingBitmap: false, useUBOBindingCache: false, needShaderCodeInlining: true, needToAlwaysBindUniformBuffers: true, supportRenderPasses: true, supportSpriteInstancing: true, forceVertexBufferStrideAndOffsetMultiple4Bytes: true, _checkNonFloatVertexBuffersDontRecreatePipelineContext: true, _collectUbosUpdatedInFrame: false, }; } _initializeContextAndSwapChain() { if (!this._renderingCanvas) { // eslint-disable-next-line no-throw-literal throw "The rendering canvas has not been set!"; } this._context = this._renderingCanvas.getContext("webgpu"); this._configureContext(); this._colorFormat = this._options.swapChainFormat; this._mainRenderPassWrapper.colorAttachmentGPUTextures = [new WebGPUHardwareTexture(this)]; this._mainRenderPassWrapper.colorAttachmentGPUTextures[0].format = this._colorFormat; this._setColorFormat(this._mainRenderPassWrapper); } // Set default values as WebGL with depth and stencil attachment for the broadest Compat. _initializeMainAttachments() { if (!this._bufferManager) { return; } this.flushFramebuffer(); this._mainTextureExtends = { width: this.getRenderWidth(true), height: this.getRenderHeight(true), depthOrArrayLayers: 1, }; const bufferDataUpdate = new Float32Array([this.getRenderHeight(true)]); this._bufferManager.setSubData(this._ubInvertY, 4, bufferDataUpdate); this._bufferManager.setSubData(this._ubDontInvertY, 4, bufferDataUpdate); let mainColorAttachments; if (this._options.antialias) { const mainTextureDescriptor = { label: `Texture_MainColor_${this._mainTextureExtends.width}x${this._mainTextureExtends.height}_antialiasing`, size: this._mainTextureExtends, mipLevelCount: 1, sampleCount: this._mainPassSampleCount, dimension: "2d" /* WebGPUConstants.TextureDimension.E2d */, format: this._options.swapChainFormat, usage: 16 /* WebGPUConstants.TextureUsage.RenderAttachment */, }; if (this._mainTexture) { this._textureHelper.releaseTexture(this._mainTexture); } this._mainTexture = this._device.createTexture(mainTextureDescriptor); mainColorAttachments = [ { view: this._mainTexture.createView({ label: "TextureView_MainColor_antialiasing", dimension: "2d" /* WebGPUConstants.TextureDimension.E2d */, format: this._options.swapChainFormat, mipLevelCount: 1, arrayLayerCount: 1, }), clearValue: new Color4(0, 0, 0, 1), loadOp: "clear" /* WebGPUConstants.LoadOp.Clear */, storeOp: "store" /* WebGPUConstants.StoreOp.Store */, // don't use StoreOp.Discard, else using several cameras with different viewports or using scissors will fail because we call beginRenderPass / endPass several times for the same color attachment! }, ]; } else { mainColorAttachments = [ { view: undefined, clearValue: new Color4(0, 0, 0, 1), loadOp: "clear" /* WebGPUConstants.LoadOp.Clear */, storeOp: "store" /* WebGPUConstants.StoreOp.Store */, }, ]; } this._mainRenderPassWrapper.depthTextureFormat = this.isStencilEnable ? "depth24plus-stencil8" /* WebGPUConstants.TextureFormat.Depth24PlusStencil8 */ : "depth32float" /* WebGPUConstants.TextureFormat.Depth32Float */; this._setDepthTextureFormat(this._mainRenderPassWrapper); this._setColorFormat(this._mainRenderPassWrapper); const depthTextureDescriptor = { label: `Texture_MainDepthStencil_${this._mainTextureExtends.width}x${this._mainTextureExtends.height}`, size: this._mainTextureExtends, mipLevelCount: 1, sampleCount: this._mainPassSampleCount, dimension: "2d" /* WebGPUConstants.TextureDimension.E2d */, format: this._mainRenderPassWrapper.depthTextureFormat, usage: 16 /* WebGPUConstants.TextureUsage.RenderAttachment */, }; if (this._depthTexture) { this._textureHelper.releaseTexture(this._depthTexture); } this._depthTexture = this._device.createTexture(depthTextureDescriptor); const mainDepthAttachment = { view: this._depthTexture.createView({ label: `TextureView_MainDepthStencil_${this._mainTextureExtends.width}x${this._mainTextureExtends.height}`, dimension: "2d" /* WebGPUConstants.TextureDimension.E2d */, format: this._depthTexture.format, mipLevelCount: 1, arrayLayerCount: 1, }), depthClearValue: this._clearDepthValue, depthLoadOp: "clear" /* WebGPUConstants.LoadOp.Clear */, depthStoreOp: "store" /* WebGPUConstants.StoreOp.Store */, stencilClearValue: this._clearStencilValue, stencilLoadOp: !this.isStencilEnable ? undefined : "clear" /* WebGPUConstants.LoadOp.Clear */, stencilStoreOp: !this.isStencilEnable ? undefined : "store" /* WebGPUConstants.StoreOp.Store */, }; this._mainRenderPassWrapper.renderPassDescriptor = { label: "MainRenderPass", colorAttachments: mainColorAttachments, depthStencilAttachment: mainDepthAttachment, }; } /** * Shared initialization across engines types. * @param canvas The canvas associated with this instance of the engine. */ _sharedInit(canvas) { super._sharedInit(canvas); _CommonInit(this, canvas, this._creationOptions); } _configureContext() { this._context.configure({ device: this._device, format: this._options.swapChainFormat, usage: 16 /* WebGPUConstants.TextureUsage.RenderAttachment */ | 1 /* WebGPUConstants.TextureUsage.CopySrc */, alphaMode: this.premultipliedAlpha ? "premultiplied" /* WebGPUConstants.CanvasAlphaMode.Premultiplied */ : "opaque" /* WebGPUConstants.CanvasAlphaMode.Opaque */, }); } /** * 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 */ resizeImageBitmap(image, bufferWidth, bufferHeight) { return ResizeImageBitmap(this, image, bufferWidth, bufferHeight); } /** * 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 */ _createImageBitmapFromSource(imageSource, options) { return CreateImageBitmapFromSource(this, imageSource, options); } /** * Toggle full screen mode * @param requestPointerLock defines if a pointer lock should be requested from the user */ switchFullscreen(requestPointerLock) { if (this.isFullscreen) { this.exitFullscreen(); } else { this.enterFullscreen(requestPointerLock); } } /** * Enters full screen mode * @param requestPointerLock defines if a pointer lock should be requested from the user */ enterFullscreen(requestPointerLock) { if (!this.isFullscreen) { this._pointerLockRequested = requestPointerLock; if (this._renderingCanvas) { RequestFullscreen(this._renderingCanvas); } } } /** * Exits full screen mode */ exitFullscreen() { if (this.isFullscreen) { ExitFullscreen(); } } /** * Enters Pointerlock mode */ enterPointerlock() { if (this._renderingCanvas) { RequestPointerlock(this._renderingCanvas); } } /** * Exits Pointerlock mode */ exitPointerlock() { ExitPointerlock(); } _rebuildBuffers() { super._rebuildBuffers(); for (const storageBuffer of this._storageBuffers) { // The buffer can already be rebuilt by the call to _rebuildGeometries(), which recreates the storage buffers for the ComputeShaderParticleSystem if (storageBuffer.getBuffer().engineId !== this.uniqueId) { storageBuffer._rebuild(); } } } _restoreEngineAfterContextLost(initEngine) { WebGPUCacheRenderPipelineTree.ResetCache(); WebGPUCacheBindGroups.ResetCache(); // Clear the draw wrappers and material contexts const cleanScenes = (scenes) => { for (const scene of scenes) { for (const mesh of scene.meshes) { const subMeshes = mesh.subMeshes; if (!subMeshes) { continue; } for (const subMesh of subMeshes) { subMesh._drawWrappers = []; } } for (const material of scene.materials) { material._materialContext?.reset(); } } }; cleanScenes(this.scenes); cleanScenes(this._virtualScenes); // The leftOver uniform buffers are removed from the list because they will be recreated when we rebuild the effects const uboList = []; for (const uniformBuffer of this._uniformBuffers) { if (uniformBuffer.name.indexOf("leftOver") < 0) { uboList.push(uniformBuffer); } } this._uniformBuffers = uboList; super._restoreEngineAfterContextLost(initEngine); } /** * Force a specific size of the canvas * @param width defines the new canvas' width * @param height defines the new canvas' height * @param forceSetSize true to force setting the sizes of the underlying canvas * @returns true if the size was changed */ setSize(width, height, forceSetSize = false) { if (!super.setSize(width, height, forceSetSize)) { return false; } if (this.dbgVerboseLogsForFirstFrames) { if (this._count === undefined) { this._count = 0; } if (!this._count || this._count < this.dbgVerboseLogsNumFrames) { Logger.Log(["frame #" + this._count + " - setSize -", width, height]); } } this._initializeMainAttachments(); if (this.snapshotRendering) { // reset snapshot rendering so that the next frame will record a new list of bundles this.snapshotRenderingReset(); } return true; } /** * @internal */ _getShaderProcessor(shaderLanguage) { if (shaderLanguage === 1 /* ShaderLanguage.WGSL */) { return this._shaderProcessorWGSL; } return this._shaderProcessor; } /** * @internal */ _getShaderProcessingContext(shaderLanguage, pureMode) { return new WebGPUShaderProcessingContext(shaderLanguage, pureMode); } _getCurrentRenderPass() { if (this._currentRenderTarget && !this._currentRenderPass) { // delayed creation of the render target pass, but we now need to create it as we are requested the render pass this._startRenderTargetRenderPass(this._currentRenderTarget, false, null, false, false); } else if (!this._currentRenderPass) { this._startMainRenderPass(false); } return this._currentRenderPass; } /** @internal */ _getCurrentRenderPassWrapper() { return this._currentRenderTarget ? this._rttRenderPassWrapper : this._mainRenderPassWrapper; } //------------------------------------------------------------------------------ // Static Pipeline WebGPU States //------------------------------------------------------------------------------ /** @internal */ applyStates() { this._stencilStateComposer.apply(); this._cacheRenderPipeline.setAlphaBlendEnabled(this._alphaState.alphaBlend); } /** * Force the entire cache to be cleared * You should not have to use this function unless your engine needs to share the WebGPU context with another engine * @param bruteForce defines a boolean to force clearing ALL caches (including stencil, detoh and alpha states) */ wipeCaches(bruteForce) { if (this.preventCacheWipeBetweenFrames && !bruteForce) { return; } //this._currentEffect = null; // can't reset _currentEffect, else some crashes can occur (for eg in ProceduralTexture which calls bindFrameBuffer (which calls wipeCaches) after having called enableEffect and before drawing into the texture) // _forceEnableEffect = true assumes the role of _currentEffect = null this._forceEnableEffect = true; this._currentIndexBuffer = null; this._currentOverrideVertexBuffers = null; this._cacheRenderPipeline.setBuffers(null, null, null); if (bruteForce) { this._stencilStateComposer.reset(); this._depthCullingState.reset(); this._depthCullingState.depthFunc = 515; this._alphaState.reset(); this._alphaMode = 1; this._alphaEquation = 0; this._cacheRenderPipeline.setAlphaBlendFactors(this._alphaState._blendFunctionParameters, this._alphaState._blendEquationParameters); this._cacheRenderPipeline.setAlphaBlendEnabled(false); this.setColorWrite(true); } this._cachedVertexBuffers = null; this._cachedIndexBuffer = null; this._cachedEffectForVertexBuffers = null; } /** * Enable or disable color writing * @param enable defines the state to set */ setColorWrite(enable) { this._colorWriteLocal = enable; this._cacheRenderPipeline.setWriteMask(enable ? 0xf : 0); } /** * Gets a boolean indicating if color writing is enabled * @returns the current color writing state */ getColorWrite() { return this._colorWriteLocal; } _mustUpdateViewport() { const x = this._viewportCached.x, y = this._viewportCached.y, w = this._viewportCached.z, h = this._viewportCached.w; const update = this._viewportsCurrent.x !== x || this._viewportsCurrent.y !== y || this._viewportsCurrent.w !== w || this._viewportsCurrent.h !== h; if (update) { this._viewportsCurrent.x = this._viewportCached.x; this._viewportsCurrent.y = this._viewportCached.y; this._viewportsCurrent.w = this._viewportCached.z; this._viewportsCurrent.h = this._viewportCached.w; } return update; } _applyViewport(bundleList) { const x = Math.floor(this._viewportCached.x); const w = Math.floor(this._viewportCached.z); const h = Math.floor(this._viewportCached.w); let y = Math.floor(this._viewportCached.y); if (!this._currentRenderTarget) { y = this.getRenderHeight(true) - y - h; } if (bundleList) { bundleList.addItem(new WebGPURenderItemViewport(x, y, w, h)); } else { this._getCurrentRenderPass().setViewport(x, y, w, h, 0, 1); } if (this.dbgVerboseLogsForFirstFrames) { if (this._count === undefined) { this._count = 0; } if (!this._count || this._count < this.dbgVerboseLogsNumFrames) { Logger.Log([ "frame #" + this._count + " - viewport applied - (", this._viewportCached.x, this._viewportCached.y, this._viewportCached.z, this._viewportCached.w, ") current pass is main pass=" + this._currentPassIsMainPass(), ]); } } } /** * @internal */ _viewport(x, y, width, height) { this._viewportCached.x = x; this._viewportCached.y = y; this._viewportCached.z = width; this._viewportCached.w = height; } _mustUpdateScissor() { const x = this._scissorCached.x, y = this._scissorCached.y, w = this._scissorCached.z, h = this._scissorCached.w; const update = this._scissorsCurrent.x !== x || this._scissorsCurrent.y !== y || this._scissorsCurrent.w !== w || this._scissorsCurrent.h !== h; if (update) { this._scissorsCurrent.x = this._scissorCached.x; this._scissorsCurrent.y = this._scissorCached.y; this._scissorsCurrent.w = this._scissorCached.z; this._scissorsCurrent.h = this._scissorCached.w; } return update; } _applyScissor(bundleList) { const y = this._currentRenderTarget ? this._scissorCached.y : this.getRenderHeight() - this._scissorCached.w - this._scissorCached.y; if (bundleList) { bundleList.addItem(new WebGPURenderItemScissor(this._scissorCached.x, y, this._scissorCached.z, this._scissorCached.w)); } else { this._getCurrentRenderPass().setScissorRect(this._scissorCached.x, y, this._scissorCached.z, this._scissorCached.w); } if (this.dbgVerboseLogsForFirstFrames) { if (this._count === undefined) { this._count = 0; } if (!this._count || this._count < this.dbgVerboseLogsNumFrames) { Logger.Log([ "frame #" + this._count + " - scissor applied - (", this._scissorCached.x, this._scissorCached.y, this._scissorCached.z, this._scissorCached.w, ") current pass is main pass=" + this._currentPassIsMainPass(), ]); } } } _scissorIsActive() { return this._scissorCached.x !== 0 || this._scissorCached.y !== 0 || this._scissorCached.z !== 0 || this._scissorCached.w !== 0; } enableScissor(x, y, width, height) { this._scissorCached.x = x; this._scissorCached.y = y; this._scissorCached.z = width; this._scissorCached.w = height; } disableScissor() { this._scissorCached.x = this._scissorCached.y = this._scissorCached.z = this._scissorCached.w = 0; this._scissorsCurrent.x = this._scissorsCurrent.y = this._scissorsCurrent.w = this._scissorsCurrent.h = 0; } _mustUpdateStencilRef() { const update = this._stencilStateComposer.funcRef !== this._stencilRefsCurrent;