@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
JavaScript
/* 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;