@babylonjs/viewer
Version:
The Babylon Viewer aims to simplify a specific but common Babylon.js use case: loading, viewing, and interacting with a 3D model.
11,325 lines • 563 kB
JavaScript
import { aO as ILog2, C as Constants, cJ as PerfCounter, A as AbstractEngine, y as Logger, cK as UniformBuffer, cL as Initialize, cM as Process, cN as Finalize, cO as DataBuffer, aS as FromHalfFloat, cC as allocateAndCopyTypedBuffer, a7 as VertexBuffer, cP as VertexBufferDeduceStride, cQ as StencilStateComposer, cR as DepthCullingState, w as InternalTexture, aY as Tools, cS as GetFontOffset, cj as RegisterAbstractEngineDom, ck as RegisterAbstractEngineRenderPass, cT as PerformanceMonitor, cU as AlphaState, b1 as Color4, cV as _CommonInit, cW as ResizeImageBitmap, cX as CreateImageBitmapFromSource, cY as RequestFullscreen, cZ as ExitFullscreen, c_ as RequestPointerlock, c$ as ExitPointerlock, d0 as Effect, d1 as IsWrapper, d2 as resetCachedPipeline, d3 as _CommonDispose } from './index-HyNDfLMI.esm.js';
import { a as SphericalPolynomial } from './sphericalPolynomial.pure-Tla2dEm0.esm.js';
/** @internal */
class WebGPUTextureHelper {
static ComputeNumMipmapLevels(width, height) {
return ILog2(Math.max(width, height)) + 1;
}
static GetTextureTypeFromFormat(format) {
switch (format) {
// One Component = 8 bits unsigned
case "r8unorm" /* WebGPUConstants.TextureFormat.R8Unorm */:
case "r8uint" /* WebGPUConstants.TextureFormat.R8Uint */:
case "rg8unorm" /* WebGPUConstants.TextureFormat.RG8Unorm */:
case "rg8uint" /* WebGPUConstants.TextureFormat.RG8Uint */:
case "rgba8unorm" /* WebGPUConstants.TextureFormat.RGBA8Unorm */:
case "rgba8unorm-srgb" /* WebGPUConstants.TextureFormat.RGBA8UnormSRGB */:
case "rgba8uint" /* WebGPUConstants.TextureFormat.RGBA8Uint */:
case "bgra8unorm" /* WebGPUConstants.TextureFormat.BGRA8Unorm */:
case "bgra8unorm-srgb" /* WebGPUConstants.TextureFormat.BGRA8UnormSRGB */:
case "rgb10a2uint" /* WebGPUConstants.TextureFormat.RGB10A2UINT */: // composite format - let's say it's byte...
case "rgb10a2unorm" /* WebGPUConstants.TextureFormat.RGB10A2Unorm */: // composite format - let's say it's byte...
case "rgb9e5ufloat" /* WebGPUConstants.TextureFormat.RGB9E5UFloat */: // composite format - let's say it's byte...
case "rg11b10ufloat" /* WebGPUConstants.TextureFormat.RG11B10UFloat */: // composite format - let's say it's byte...
case "bc7-rgba-unorm" /* WebGPUConstants.TextureFormat.BC7RGBAUnorm */:
case "bc7-rgba-unorm-srgb" /* WebGPUConstants.TextureFormat.BC7RGBAUnormSRGB */:
case "bc6h-rgb-ufloat" /* WebGPUConstants.TextureFormat.BC6HRGBUFloat */:
case "bc5-rg-unorm" /* WebGPUConstants.TextureFormat.BC5RGUnorm */:
case "bc3-rgba-unorm" /* WebGPUConstants.TextureFormat.BC3RGBAUnorm */:
case "bc3-rgba-unorm-srgb" /* WebGPUConstants.TextureFormat.BC3RGBAUnormSRGB */:
case "bc2-rgba-unorm" /* WebGPUConstants.TextureFormat.BC2RGBAUnorm */:
case "bc2-rgba-unorm-srgb" /* WebGPUConstants.TextureFormat.BC2RGBAUnormSRGB */:
case "bc4-r-unorm" /* WebGPUConstants.TextureFormat.BC4RUnorm */:
case "bc1-rgba-unorm" /* WebGPUConstants.TextureFormat.BC1RGBAUnorm */:
case "bc1-rgba-unorm-srgb" /* WebGPUConstants.TextureFormat.BC1RGBAUnormSRGB */:
case "etc2-rgb8unorm" /* WebGPUConstants.TextureFormat.ETC2RGB8Unorm */:
case "etc2-rgb8unorm-srgb" /* WebGPUConstants.TextureFormat.ETC2RGB8UnormSRGB */:
case "etc2-rgb8a1unorm" /* WebGPUConstants.TextureFormat.ETC2RGB8A1Unorm */:
case "etc2-rgb8a1unorm-srgb" /* WebGPUConstants.TextureFormat.ETC2RGB8A1UnormSRGB */:
case "etc2-rgba8unorm" /* WebGPUConstants.TextureFormat.ETC2RGBA8Unorm */:
case "etc2-rgba8unorm-srgb" /* WebGPUConstants.TextureFormat.ETC2RGBA8UnormSRGB */:
case "eac-r11unorm" /* WebGPUConstants.TextureFormat.EACR11Unorm */:
case "eac-rg11unorm" /* WebGPUConstants.TextureFormat.EACRG11Unorm */:
case "astc-4x4-unorm" /* WebGPUConstants.TextureFormat.ASTC4x4Unorm */:
case "astc-4x4-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC4x4UnormSRGB */:
case "astc-5x4-unorm" /* WebGPUConstants.TextureFormat.ASTC5x4Unorm */:
case "astc-5x4-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC5x4UnormSRGB */:
case "astc-5x5-unorm" /* WebGPUConstants.TextureFormat.ASTC5x5Unorm */:
case "astc-5x5-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC5x5UnormSRGB */:
case "astc-6x5-unorm" /* WebGPUConstants.TextureFormat.ASTC6x5Unorm */:
case "astc-6x5-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC6x5UnormSRGB */:
case "astc-6x6-unorm" /* WebGPUConstants.TextureFormat.ASTC6x6Unorm */:
case "astc-6x6-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC6x6UnormSRGB */:
case "astc-8x5-unorm" /* WebGPUConstants.TextureFormat.ASTC8x5Unorm */:
case "astc-8x5-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC8x5UnormSRGB */:
case "astc-8x6-unorm" /* WebGPUConstants.TextureFormat.ASTC8x6Unorm */:
case "astc-8x6-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC8x6UnormSRGB */:
case "astc-8x8-unorm" /* WebGPUConstants.TextureFormat.ASTC8x8Unorm */:
case "astc-8x8-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC8x8UnormSRGB */:
case "astc-10x5-unorm" /* WebGPUConstants.TextureFormat.ASTC10x5Unorm */:
case "astc-10x5-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC10x5UnormSRGB */:
case "astc-10x6-unorm" /* WebGPUConstants.TextureFormat.ASTC10x6Unorm */:
case "astc-10x6-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC10x6UnormSRGB */:
case "astc-10x8-unorm" /* WebGPUConstants.TextureFormat.ASTC10x8Unorm */:
case "astc-10x8-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC10x8UnormSRGB */:
case "astc-10x10-unorm" /* WebGPUConstants.TextureFormat.ASTC10x10Unorm */:
case "astc-10x10-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC10x10UnormSRGB */:
case "astc-12x10-unorm" /* WebGPUConstants.TextureFormat.ASTC12x10Unorm */:
case "astc-12x10-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC12x10UnormSRGB */:
case "astc-12x12-unorm" /* WebGPUConstants.TextureFormat.ASTC12x12Unorm */:
case "astc-12x12-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC12x12UnormSRGB */:
case "stencil8" /* WebGPUConstants.TextureFormat.Stencil8 */:
return Constants.TEXTURETYPE_UNSIGNED_BYTE;
// One Component = 8 bits signed
case "r8snorm" /* WebGPUConstants.TextureFormat.R8Snorm */:
case "r8sint" /* WebGPUConstants.TextureFormat.R8Sint */:
case "rg8snorm" /* WebGPUConstants.TextureFormat.RG8Snorm */:
case "rg8sint" /* WebGPUConstants.TextureFormat.RG8Sint */:
case "rgba8snorm" /* WebGPUConstants.TextureFormat.RGBA8Snorm */:
case "rgba8sint" /* WebGPUConstants.TextureFormat.RGBA8Sint */:
case "bc6h-rgb-float" /* WebGPUConstants.TextureFormat.BC6HRGBFloat */:
case "bc5-rg-snorm" /* WebGPUConstants.TextureFormat.BC5RGSnorm */:
case "bc4-r-snorm" /* WebGPUConstants.TextureFormat.BC4RSnorm */:
case "eac-r11snorm" /* WebGPUConstants.TextureFormat.EACR11Snorm */:
case "eac-rg11snorm" /* WebGPUConstants.TextureFormat.EACRG11Snorm */:
return Constants.TEXTURETYPE_BYTE;
// One component = 16 bits unsigned
case "r16uint" /* WebGPUConstants.TextureFormat.R16Uint */:
case "r16unorm" /* WebGPUConstants.TextureFormat.R16Unorm */:
case "rg16unorm" /* WebGPUConstants.TextureFormat.RG16Unorm */:
case "rgba16unorm" /* WebGPUConstants.TextureFormat.RGBA16Unorm */:
case "rg16uint" /* WebGPUConstants.TextureFormat.RG16Uint */:
case "rgba16uint" /* WebGPUConstants.TextureFormat.RGBA16Uint */:
case "depth16unorm" /* WebGPUConstants.TextureFormat.Depth16Unorm */:
return Constants.TEXTURETYPE_UNSIGNED_SHORT;
// One component = 16 bits signed
case "r16sint" /* WebGPUConstants.TextureFormat.R16Sint */:
case "r16snorm" /* WebGPUConstants.TextureFormat.R16Snorm */:
case "rg16snorm" /* WebGPUConstants.TextureFormat.RG16Snorm */:
case "rgba16snorm" /* WebGPUConstants.TextureFormat.RGBA16Snorm */:
case "rg16sint" /* WebGPUConstants.TextureFormat.RG16Sint */:
case "rgba16sint" /* WebGPUConstants.TextureFormat.RGBA16Sint */:
return Constants.TEXTURETYPE_SHORT;
case "r16float" /* WebGPUConstants.TextureFormat.R16Float */:
case "rg16float" /* WebGPUConstants.TextureFormat.RG16Float */:
case "rgba16float" /* WebGPUConstants.TextureFormat.RGBA16Float */:
return Constants.TEXTURETYPE_HALF_FLOAT;
// One component = 32 bits unsigned
case "r32uint" /* WebGPUConstants.TextureFormat.R32Uint */:
case "rg32uint" /* WebGPUConstants.TextureFormat.RG32Uint */:
case "rgba32uint" /* WebGPUConstants.TextureFormat.RGBA32Uint */:
return Constants.TEXTURETYPE_UNSIGNED_INTEGER;
// One component = 32 bits signed
case "r32sint" /* WebGPUConstants.TextureFormat.R32Sint */:
case "rg32sint" /* WebGPUConstants.TextureFormat.RG32Sint */:
case "rgba32sint" /* WebGPUConstants.TextureFormat.RGBA32Sint */:
return Constants.TEXTURETYPE_UNSIGNED_INTEGER;
case "r32float" /* WebGPUConstants.TextureFormat.R32Float */:
case "rg32float" /* WebGPUConstants.TextureFormat.RG32Float */:
case "rgba32float" /* WebGPUConstants.TextureFormat.RGBA32Float */:
case "depth32float" /* WebGPUConstants.TextureFormat.Depth32Float */:
case "depth32float-stencil8" /* WebGPUConstants.TextureFormat.Depth32FloatStencil8 */:
case "depth24plus" /* WebGPUConstants.TextureFormat.Depth24Plus */:
case "depth24plus-stencil8" /* WebGPUConstants.TextureFormat.Depth24PlusStencil8 */:
return Constants.TEXTURETYPE_FLOAT;
}
return Constants.TEXTURETYPE_UNSIGNED_BYTE;
}
static GetBlockInformationFromFormat(format) {
switch (format) {
// 8 bits formats
case "r8unorm" /* WebGPUConstants.TextureFormat.R8Unorm */:
case "r8snorm" /* WebGPUConstants.TextureFormat.R8Snorm */:
case "r8uint" /* WebGPUConstants.TextureFormat.R8Uint */:
case "r8sint" /* WebGPUConstants.TextureFormat.R8Sint */:
return { width: 1, height: 1, length: 1 };
// 16 bits formats
case "r16uint" /* WebGPUConstants.TextureFormat.R16Uint */:
case "r16sint" /* WebGPUConstants.TextureFormat.R16Sint */:
case "r16unorm" /* WebGPUConstants.TextureFormat.R16Unorm */:
case "r16snorm" /* WebGPUConstants.TextureFormat.R16Snorm */:
case "r16float" /* WebGPUConstants.TextureFormat.R16Float */:
case "rg8unorm" /* WebGPUConstants.TextureFormat.RG8Unorm */:
case "rg8snorm" /* WebGPUConstants.TextureFormat.RG8Snorm */:
case "rg8uint" /* WebGPUConstants.TextureFormat.RG8Uint */:
case "rg8sint" /* WebGPUConstants.TextureFormat.RG8Sint */:
return { width: 1, height: 1, length: 2 };
// 32 bits formats
case "r32uint" /* WebGPUConstants.TextureFormat.R32Uint */:
case "r32sint" /* WebGPUConstants.TextureFormat.R32Sint */:
case "r32float" /* WebGPUConstants.TextureFormat.R32Float */:
case "rg16uint" /* WebGPUConstants.TextureFormat.RG16Uint */:
case "rg16sint" /* WebGPUConstants.TextureFormat.RG16Sint */:
case "rg16float" /* WebGPUConstants.TextureFormat.RG16Float */:
case "rg16unorm" /* WebGPUConstants.TextureFormat.RG16Unorm */:
case "rg16snorm" /* WebGPUConstants.TextureFormat.RG16Snorm */:
case "rgba8unorm" /* WebGPUConstants.TextureFormat.RGBA8Unorm */:
case "rgba8unorm-srgb" /* WebGPUConstants.TextureFormat.RGBA8UnormSRGB */:
case "rgba8snorm" /* WebGPUConstants.TextureFormat.RGBA8Snorm */:
case "rgba8uint" /* WebGPUConstants.TextureFormat.RGBA8Uint */:
case "rgba8sint" /* WebGPUConstants.TextureFormat.RGBA8Sint */:
case "bgra8unorm" /* WebGPUConstants.TextureFormat.BGRA8Unorm */:
case "bgra8unorm-srgb" /* WebGPUConstants.TextureFormat.BGRA8UnormSRGB */:
case "rgb9e5ufloat" /* WebGPUConstants.TextureFormat.RGB9E5UFloat */:
case "rgb10a2uint" /* WebGPUConstants.TextureFormat.RGB10A2UINT */:
case "rgb10a2unorm" /* WebGPUConstants.TextureFormat.RGB10A2Unorm */:
case "rg11b10ufloat" /* WebGPUConstants.TextureFormat.RG11B10UFloat */:
return { width: 1, height: 1, length: 4 };
// 64 bits formats
case "rg32uint" /* WebGPUConstants.TextureFormat.RG32Uint */:
case "rg32sint" /* WebGPUConstants.TextureFormat.RG32Sint */:
case "rg32float" /* WebGPUConstants.TextureFormat.RG32Float */:
case "rgba16uint" /* WebGPUConstants.TextureFormat.RGBA16Uint */:
case "rgba16sint" /* WebGPUConstants.TextureFormat.RGBA16Sint */:
case "rgba16float" /* WebGPUConstants.TextureFormat.RGBA16Float */:
case "rgba16unorm" /* WebGPUConstants.TextureFormat.RGBA16Unorm */:
case "rgba16snorm" /* WebGPUConstants.TextureFormat.RGBA16Snorm */:
return { width: 1, height: 1, length: 8 };
// 128 bits formats
case "rgba32uint" /* WebGPUConstants.TextureFormat.RGBA32Uint */:
case "rgba32sint" /* WebGPUConstants.TextureFormat.RGBA32Sint */:
case "rgba32float" /* WebGPUConstants.TextureFormat.RGBA32Float */:
return { width: 1, height: 1, length: 16 };
// Depth and stencil formats
case "stencil8" /* WebGPUConstants.TextureFormat.Stencil8 */:
// eslint-disable-next-line no-throw-literal
throw "No fixed size for Stencil8 format!";
case "depth16unorm" /* WebGPUConstants.TextureFormat.Depth16Unorm */:
return { width: 1, height: 1, length: 2 };
case "depth24plus" /* WebGPUConstants.TextureFormat.Depth24Plus */:
// eslint-disable-next-line no-throw-literal
throw "No fixed size for Depth24Plus format!";
case "depth24plus-stencil8" /* WebGPUConstants.TextureFormat.Depth24PlusStencil8 */:
// eslint-disable-next-line no-throw-literal
throw "No fixed size for Depth24PlusStencil8 format!";
case "depth32float" /* WebGPUConstants.TextureFormat.Depth32Float */:
return { width: 1, height: 1, length: 4 };
case "depth32float-stencil8" /* WebGPUConstants.TextureFormat.Depth32FloatStencil8 */:
return { width: 1, height: 1, length: 5 };
// BC compressed formats usable if "texture-compression-bc" is both
// supported by the device/user agent and enabled in requestDevice.
case "bc7-rgba-unorm" /* WebGPUConstants.TextureFormat.BC7RGBAUnorm */:
case "bc7-rgba-unorm-srgb" /* WebGPUConstants.TextureFormat.BC7RGBAUnormSRGB */:
case "bc6h-rgb-ufloat" /* WebGPUConstants.TextureFormat.BC6HRGBUFloat */:
case "bc6h-rgb-float" /* WebGPUConstants.TextureFormat.BC6HRGBFloat */:
case "bc5-rg-unorm" /* WebGPUConstants.TextureFormat.BC5RGUnorm */:
case "bc5-rg-snorm" /* WebGPUConstants.TextureFormat.BC5RGSnorm */:
case "bc3-rgba-unorm" /* WebGPUConstants.TextureFormat.BC3RGBAUnorm */:
case "bc3-rgba-unorm-srgb" /* WebGPUConstants.TextureFormat.BC3RGBAUnormSRGB */:
case "bc2-rgba-unorm" /* WebGPUConstants.TextureFormat.BC2RGBAUnorm */:
case "bc2-rgba-unorm-srgb" /* WebGPUConstants.TextureFormat.BC2RGBAUnormSRGB */:
return { width: 4, height: 4, length: 16 };
case "bc4-r-unorm" /* WebGPUConstants.TextureFormat.BC4RUnorm */:
case "bc4-r-snorm" /* WebGPUConstants.TextureFormat.BC4RSnorm */:
case "bc1-rgba-unorm" /* WebGPUConstants.TextureFormat.BC1RGBAUnorm */:
case "bc1-rgba-unorm-srgb" /* WebGPUConstants.TextureFormat.BC1RGBAUnormSRGB */:
return { width: 4, height: 4, length: 8 };
// ETC2 compressed formats usable if "texture-compression-etc2" is both
// supported by the device/user agent and enabled in requestDevice.
case "etc2-rgb8unorm" /* WebGPUConstants.TextureFormat.ETC2RGB8Unorm */:
case "etc2-rgb8unorm-srgb" /* WebGPUConstants.TextureFormat.ETC2RGB8UnormSRGB */:
case "etc2-rgb8a1unorm" /* WebGPUConstants.TextureFormat.ETC2RGB8A1Unorm */:
case "etc2-rgb8a1unorm-srgb" /* WebGPUConstants.TextureFormat.ETC2RGB8A1UnormSRGB */:
case "eac-r11unorm" /* WebGPUConstants.TextureFormat.EACR11Unorm */:
case "eac-r11snorm" /* WebGPUConstants.TextureFormat.EACR11Snorm */:
return { width: 4, height: 4, length: 8 };
case "etc2-rgba8unorm" /* WebGPUConstants.TextureFormat.ETC2RGBA8Unorm */:
case "etc2-rgba8unorm-srgb" /* WebGPUConstants.TextureFormat.ETC2RGBA8UnormSRGB */:
case "eac-rg11unorm" /* WebGPUConstants.TextureFormat.EACRG11Unorm */:
case "eac-rg11snorm" /* WebGPUConstants.TextureFormat.EACRG11Snorm */:
return { width: 4, height: 4, length: 16 };
// ASTC compressed formats usable if "texture-compression-astc" is both
// supported by the device/user agent and enabled in requestDevice.
case "astc-4x4-unorm" /* WebGPUConstants.TextureFormat.ASTC4x4Unorm */:
case "astc-4x4-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC4x4UnormSRGB */:
return { width: 4, height: 4, length: 16 };
case "astc-5x4-unorm" /* WebGPUConstants.TextureFormat.ASTC5x4Unorm */:
case "astc-5x4-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC5x4UnormSRGB */:
return { width: 5, height: 4, length: 16 };
case "astc-5x5-unorm" /* WebGPUConstants.TextureFormat.ASTC5x5Unorm */:
case "astc-5x5-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC5x5UnormSRGB */:
return { width: 5, height: 5, length: 16 };
case "astc-6x5-unorm" /* WebGPUConstants.TextureFormat.ASTC6x5Unorm */:
case "astc-6x5-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC6x5UnormSRGB */:
return { width: 6, height: 5, length: 16 };
case "astc-6x6-unorm" /* WebGPUConstants.TextureFormat.ASTC6x6Unorm */:
case "astc-6x6-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC6x6UnormSRGB */:
return { width: 6, height: 6, length: 16 };
case "astc-8x5-unorm" /* WebGPUConstants.TextureFormat.ASTC8x5Unorm */:
case "astc-8x5-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC8x5UnormSRGB */:
return { width: 8, height: 5, length: 16 };
case "astc-8x6-unorm" /* WebGPUConstants.TextureFormat.ASTC8x6Unorm */:
case "astc-8x6-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC8x6UnormSRGB */:
return { width: 8, height: 6, length: 16 };
case "astc-8x8-unorm" /* WebGPUConstants.TextureFormat.ASTC8x8Unorm */:
case "astc-8x8-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC8x8UnormSRGB */:
return { width: 8, height: 8, length: 16 };
case "astc-10x5-unorm" /* WebGPUConstants.TextureFormat.ASTC10x5Unorm */:
case "astc-10x5-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC10x5UnormSRGB */:
return { width: 10, height: 5, length: 16 };
case "astc-10x6-unorm" /* WebGPUConstants.TextureFormat.ASTC10x6Unorm */:
case "astc-10x6-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC10x6UnormSRGB */:
return { width: 10, height: 6, length: 16 };
case "astc-10x8-unorm" /* WebGPUConstants.TextureFormat.ASTC10x8Unorm */:
case "astc-10x8-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC10x8UnormSRGB */:
return { width: 10, height: 8, length: 16 };
case "astc-10x10-unorm" /* WebGPUConstants.TextureFormat.ASTC10x10Unorm */:
case "astc-10x10-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC10x10UnormSRGB */:
return { width: 10, height: 10, length: 16 };
case "astc-12x10-unorm" /* WebGPUConstants.TextureFormat.ASTC12x10Unorm */:
case "astc-12x10-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC12x10UnormSRGB */:
return { width: 12, height: 10, length: 16 };
case "astc-12x12-unorm" /* WebGPUConstants.TextureFormat.ASTC12x12Unorm */:
case "astc-12x12-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC12x12UnormSRGB */:
return { width: 12, height: 12, length: 16 };
}
return { width: 1, height: 1, length: 4 };
}
static IsHardwareTexture(texture) {
return !!texture.release;
}
static IsInternalTexture(texture) {
return !!texture.dispose;
}
static IsImageBitmap(imageBitmap) {
return imageBitmap.close !== undefined;
}
static IsImageBitmapArray(imageBitmap) {
return Array.isArray(imageBitmap) && imageBitmap[0].close !== undefined;
}
static IsCompressedFormat(format) {
switch (format) {
case "bc7-rgba-unorm-srgb" /* WebGPUConstants.TextureFormat.BC7RGBAUnormSRGB */:
case "bc7-rgba-unorm" /* WebGPUConstants.TextureFormat.BC7RGBAUnorm */:
case "bc6h-rgb-float" /* WebGPUConstants.TextureFormat.BC6HRGBFloat */:
case "bc6h-rgb-ufloat" /* WebGPUConstants.TextureFormat.BC6HRGBUFloat */:
case "bc5-rg-snorm" /* WebGPUConstants.TextureFormat.BC5RGSnorm */:
case "bc5-rg-unorm" /* WebGPUConstants.TextureFormat.BC5RGUnorm */:
case "bc4-r-snorm" /* WebGPUConstants.TextureFormat.BC4RSnorm */:
case "bc4-r-unorm" /* WebGPUConstants.TextureFormat.BC4RUnorm */:
case "bc3-rgba-unorm-srgb" /* WebGPUConstants.TextureFormat.BC3RGBAUnormSRGB */:
case "bc3-rgba-unorm" /* WebGPUConstants.TextureFormat.BC3RGBAUnorm */:
case "bc2-rgba-unorm-srgb" /* WebGPUConstants.TextureFormat.BC2RGBAUnormSRGB */:
case "bc2-rgba-unorm" /* WebGPUConstants.TextureFormat.BC2RGBAUnorm */:
case "bc1-rgba-unorm-srgb" /* WebGPUConstants.TextureFormat.BC1RGBAUnormSRGB */:
case "bc1-rgba-unorm" /* WebGPUConstants.TextureFormat.BC1RGBAUnorm */:
case "etc2-rgb8unorm" /* WebGPUConstants.TextureFormat.ETC2RGB8Unorm */:
case "etc2-rgb8unorm-srgb" /* WebGPUConstants.TextureFormat.ETC2RGB8UnormSRGB */:
case "etc2-rgb8a1unorm" /* WebGPUConstants.TextureFormat.ETC2RGB8A1Unorm */:
case "etc2-rgb8a1unorm-srgb" /* WebGPUConstants.TextureFormat.ETC2RGB8A1UnormSRGB */:
case "etc2-rgba8unorm" /* WebGPUConstants.TextureFormat.ETC2RGBA8Unorm */:
case "etc2-rgba8unorm-srgb" /* WebGPUConstants.TextureFormat.ETC2RGBA8UnormSRGB */:
case "eac-r11unorm" /* WebGPUConstants.TextureFormat.EACR11Unorm */:
case "eac-r11snorm" /* WebGPUConstants.TextureFormat.EACR11Snorm */:
case "eac-rg11unorm" /* WebGPUConstants.TextureFormat.EACRG11Unorm */:
case "eac-rg11snorm" /* WebGPUConstants.TextureFormat.EACRG11Snorm */:
case "astc-4x4-unorm" /* WebGPUConstants.TextureFormat.ASTC4x4Unorm */:
case "astc-4x4-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC4x4UnormSRGB */:
case "astc-5x4-unorm" /* WebGPUConstants.TextureFormat.ASTC5x4Unorm */:
case "astc-5x4-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC5x4UnormSRGB */:
case "astc-5x5-unorm" /* WebGPUConstants.TextureFormat.ASTC5x5Unorm */:
case "astc-5x5-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC5x5UnormSRGB */:
case "astc-6x5-unorm" /* WebGPUConstants.TextureFormat.ASTC6x5Unorm */:
case "astc-6x5-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC6x5UnormSRGB */:
case "astc-6x6-unorm" /* WebGPUConstants.TextureFormat.ASTC6x6Unorm */:
case "astc-6x6-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC6x6UnormSRGB */:
case "astc-8x5-unorm" /* WebGPUConstants.TextureFormat.ASTC8x5Unorm */:
case "astc-8x5-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC8x5UnormSRGB */:
case "astc-8x6-unorm" /* WebGPUConstants.TextureFormat.ASTC8x6Unorm */:
case "astc-8x6-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC8x6UnormSRGB */:
case "astc-8x8-unorm" /* WebGPUConstants.TextureFormat.ASTC8x8Unorm */:
case "astc-8x8-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC8x8UnormSRGB */:
case "astc-10x5-unorm" /* WebGPUConstants.TextureFormat.ASTC10x5Unorm */:
case "astc-10x5-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC10x5UnormSRGB */:
case "astc-10x6-unorm" /* WebGPUConstants.TextureFormat.ASTC10x6Unorm */:
case "astc-10x6-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC10x6UnormSRGB */:
case "astc-10x8-unorm" /* WebGPUConstants.TextureFormat.ASTC10x8Unorm */:
case "astc-10x8-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC10x8UnormSRGB */:
case "astc-10x10-unorm" /* WebGPUConstants.TextureFormat.ASTC10x10Unorm */:
case "astc-10x10-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC10x10UnormSRGB */:
case "astc-12x10-unorm" /* WebGPUConstants.TextureFormat.ASTC12x10Unorm */:
case "astc-12x10-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC12x10UnormSRGB */:
case "astc-12x12-unorm" /* WebGPUConstants.TextureFormat.ASTC12x12Unorm */:
case "astc-12x12-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC12x12UnormSRGB */:
return true;
}
return false;
}
static GetWebGPUTextureFormat(type, format, useSRGBBuffer = false) {
switch (format) {
case Constants.TEXTUREFORMAT_DEPTH16:
return "depth16unorm" /* WebGPUConstants.TextureFormat.Depth16Unorm */;
case Constants.TEXTUREFORMAT_DEPTH24:
return "depth24plus" /* WebGPUConstants.TextureFormat.Depth24Plus */;
case Constants.TEXTUREFORMAT_DEPTH24_STENCIL8:
return "depth24plus-stencil8" /* WebGPUConstants.TextureFormat.Depth24PlusStencil8 */;
case Constants.TEXTUREFORMAT_DEPTH32_FLOAT:
return "depth32float" /* WebGPUConstants.TextureFormat.Depth32Float */;
case Constants.TEXTUREFORMAT_DEPTH32FLOAT_STENCIL8:
return "depth32float-stencil8" /* WebGPUConstants.TextureFormat.Depth32FloatStencil8 */;
case Constants.TEXTUREFORMAT_STENCIL8:
return "stencil8" /* WebGPUConstants.TextureFormat.Stencil8 */;
case Constants.TEXTUREFORMAT_COMPRESSED_RGBA_BPTC_UNORM:
return useSRGBBuffer ? "bc7-rgba-unorm-srgb" /* WebGPUConstants.TextureFormat.BC7RGBAUnormSRGB */ : "bc7-rgba-unorm" /* WebGPUConstants.TextureFormat.BC7RGBAUnorm */;
case Constants.TEXTUREFORMAT_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:
return "bc6h-rgb-ufloat" /* WebGPUConstants.TextureFormat.BC6HRGBUFloat */;
case Constants.TEXTUREFORMAT_COMPRESSED_RGB_BPTC_SIGNED_FLOAT:
return "bc6h-rgb-float" /* WebGPUConstants.TextureFormat.BC6HRGBFloat */;
case Constants.TEXTUREFORMAT_COMPRESSED_RGBA_S3TC_DXT5:
return useSRGBBuffer ? "bc3-rgba-unorm-srgb" /* WebGPUConstants.TextureFormat.BC3RGBAUnormSRGB */ : "bc3-rgba-unorm" /* WebGPUConstants.TextureFormat.BC3RGBAUnorm */;
case Constants.TEXTUREFORMAT_COMPRESSED_RGBA_S3TC_DXT3:
return useSRGBBuffer ? "bc2-rgba-unorm-srgb" /* WebGPUConstants.TextureFormat.BC2RGBAUnormSRGB */ : "bc2-rgba-unorm" /* WebGPUConstants.TextureFormat.BC2RGBAUnorm */;
case Constants.TEXTUREFORMAT_COMPRESSED_RGBA_S3TC_DXT1:
case Constants.TEXTUREFORMAT_COMPRESSED_RGB_S3TC_DXT1:
return useSRGBBuffer ? "bc1-rgba-unorm-srgb" /* WebGPUConstants.TextureFormat.BC1RGBAUnormSRGB */ : "bc1-rgba-unorm" /* WebGPUConstants.TextureFormat.BC1RGBAUnorm */;
case Constants.TEXTUREFORMAT_COMPRESSED_RGBA_ASTC_4x4:
return useSRGBBuffer ? "astc-4x4-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC4x4UnormSRGB */ : "astc-4x4-unorm" /* WebGPUConstants.TextureFormat.ASTC4x4Unorm */;
case Constants.TEXTUREFORMAT_COMPRESSED_RGBA_ASTC_5x4:
return useSRGBBuffer ? "astc-5x4-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC5x4UnormSRGB */ : "astc-5x4-unorm" /* WebGPUConstants.TextureFormat.ASTC5x4Unorm */;
case Constants.TEXTUREFORMAT_COMPRESSED_RGBA_ASTC_5x5:
return useSRGBBuffer ? "astc-5x5-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC5x5UnormSRGB */ : "astc-5x5-unorm" /* WebGPUConstants.TextureFormat.ASTC5x5Unorm */;
case Constants.TEXTUREFORMAT_COMPRESSED_RGBA_ASTC_6x5:
return useSRGBBuffer ? "astc-6x5-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC6x5UnormSRGB */ : "astc-6x5-unorm" /* WebGPUConstants.TextureFormat.ASTC6x5Unorm */;
case Constants.TEXTUREFORMAT_COMPRESSED_RGBA_ASTC_6x6:
return useSRGBBuffer ? "astc-6x6-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC6x6UnormSRGB */ : "astc-6x6-unorm" /* WebGPUConstants.TextureFormat.ASTC6x6Unorm */;
case Constants.TEXTUREFORMAT_COMPRESSED_RGBA_ASTC_8x5:
return useSRGBBuffer ? "astc-8x5-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC8x5UnormSRGB */ : "astc-8x5-unorm" /* WebGPUConstants.TextureFormat.ASTC8x5Unorm */;
case Constants.TEXTUREFORMAT_COMPRESSED_RGBA_ASTC_8x6:
return useSRGBBuffer ? "astc-8x6-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC8x6UnormSRGB */ : "astc-8x6-unorm" /* WebGPUConstants.TextureFormat.ASTC8x6Unorm */;
case Constants.TEXTUREFORMAT_COMPRESSED_RGBA_ASTC_8x8:
return useSRGBBuffer ? "astc-8x8-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC8x8UnormSRGB */ : "astc-8x8-unorm" /* WebGPUConstants.TextureFormat.ASTC8x8Unorm */;
case Constants.TEXTUREFORMAT_COMPRESSED_RGBA_ASTC_10x5:
return useSRGBBuffer ? "astc-10x5-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC10x5UnormSRGB */ : "astc-10x5-unorm" /* WebGPUConstants.TextureFormat.ASTC10x5Unorm */;
case Constants.TEXTUREFORMAT_COMPRESSED_RGBA_ASTC_10x6:
return useSRGBBuffer ? "astc-10x6-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC10x6UnormSRGB */ : "astc-10x6-unorm" /* WebGPUConstants.TextureFormat.ASTC10x6Unorm */;
case Constants.TEXTUREFORMAT_COMPRESSED_RGBA_ASTC_10x8:
return useSRGBBuffer ? "astc-10x8-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC10x8UnormSRGB */ : "astc-10x8-unorm" /* WebGPUConstants.TextureFormat.ASTC10x8Unorm */;
case Constants.TEXTUREFORMAT_COMPRESSED_RGBA_ASTC_10x10:
return useSRGBBuffer ? "astc-10x10-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC10x10UnormSRGB */ : "astc-10x10-unorm" /* WebGPUConstants.TextureFormat.ASTC10x10Unorm */;
case Constants.TEXTUREFORMAT_COMPRESSED_RGBA_ASTC_12x10:
return useSRGBBuffer ? "astc-12x10-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC12x10UnormSRGB */ : "astc-12x10-unorm" /* WebGPUConstants.TextureFormat.ASTC12x10Unorm */;
case Constants.TEXTUREFORMAT_COMPRESSED_RGBA_ASTC_12x12:
return useSRGBBuffer ? "astc-12x12-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC12x12UnormSRGB */ : "astc-12x12-unorm" /* WebGPUConstants.TextureFormat.ASTC12x12Unorm */;
case Constants.TEXTUREFORMAT_COMPRESSED_RGB_ETC1_WEBGL:
case Constants.TEXTUREFORMAT_COMPRESSED_RGB8_ETC2:
return useSRGBBuffer ? "etc2-rgb8unorm-srgb" /* WebGPUConstants.TextureFormat.ETC2RGB8UnormSRGB */ : "etc2-rgb8unorm" /* WebGPUConstants.TextureFormat.ETC2RGB8Unorm */;
case Constants.TEXTUREFORMAT_COMPRESSED_RGBA8_ETC2_EAC:
return useSRGBBuffer ? "etc2-rgba8unorm-srgb" /* WebGPUConstants.TextureFormat.ETC2RGBA8UnormSRGB */ : "etc2-rgba8unorm" /* WebGPUConstants.TextureFormat.ETC2RGBA8Unorm */;
}
switch (type) {
case Constants.TEXTURETYPE_BYTE:
switch (format) {
case Constants.TEXTUREFORMAT_RED:
return "r8snorm" /* WebGPUConstants.TextureFormat.R8Snorm */;
case Constants.TEXTUREFORMAT_RG:
return "rg8snorm" /* WebGPUConstants.TextureFormat.RG8Snorm */;
case Constants.TEXTUREFORMAT_RGB:
// eslint-disable-next-line no-throw-literal
throw "RGB format not supported in WebGPU";
case Constants.TEXTUREFORMAT_RED_INTEGER:
return "r8sint" /* WebGPUConstants.TextureFormat.R8Sint */;
case Constants.TEXTUREFORMAT_RG_INTEGER:
return "rg8sint" /* WebGPUConstants.TextureFormat.RG8Sint */;
case Constants.TEXTUREFORMAT_RGB_INTEGER:
// eslint-disable-next-line no-throw-literal
throw "RGB_INTEGER format not supported in WebGPU";
case Constants.TEXTUREFORMAT_RGBA_INTEGER:
return "rgba8sint" /* WebGPUConstants.TextureFormat.RGBA8Sint */;
default:
return "rgba8snorm" /* WebGPUConstants.TextureFormat.RGBA8Snorm */;
}
case Constants.TEXTURETYPE_UNSIGNED_BYTE:
switch (format) {
case Constants.TEXTUREFORMAT_RED:
return "r8unorm" /* WebGPUConstants.TextureFormat.R8Unorm */;
case Constants.TEXTUREFORMAT_RG:
return "rg8unorm" /* WebGPUConstants.TextureFormat.RG8Unorm */;
case Constants.TEXTUREFORMAT_RGB:
// eslint-disable-next-line no-throw-literal
throw "TEXTUREFORMAT_RGB format not supported in WebGPU";
case Constants.TEXTUREFORMAT_RGBA:
return useSRGBBuffer ? "rgba8unorm-srgb" /* WebGPUConstants.TextureFormat.RGBA8UnormSRGB */ : "rgba8unorm" /* WebGPUConstants.TextureFormat.RGBA8Unorm */;
case Constants.TEXTUREFORMAT_BGRA:
return useSRGBBuffer ? "bgra8unorm-srgb" /* WebGPUConstants.TextureFormat.BGRA8UnormSRGB */ : "bgra8unorm" /* WebGPUConstants.TextureFormat.BGRA8Unorm */;
case Constants.TEXTUREFORMAT_RED_INTEGER:
return "r8uint" /* WebGPUConstants.TextureFormat.R8Uint */;
case Constants.TEXTUREFORMAT_RG_INTEGER:
return "rg8uint" /* WebGPUConstants.TextureFormat.RG8Uint */;
case Constants.TEXTUREFORMAT_RGB_INTEGER:
// eslint-disable-next-line no-throw-literal
throw "RGB_INTEGER format not supported in WebGPU";
case Constants.TEXTUREFORMAT_RGBA_INTEGER:
return "rgba8uint" /* WebGPUConstants.TextureFormat.RGBA8Uint */;
case Constants.TEXTUREFORMAT_ALPHA:
// eslint-disable-next-line no-throw-literal
throw "TEXTUREFORMAT_ALPHA format not supported in WebGPU";
case Constants.TEXTUREFORMAT_LUMINANCE:
// eslint-disable-next-line no-throw-literal
throw "TEXTUREFORMAT_LUMINANCE format not supported in WebGPU";
case Constants.TEXTUREFORMAT_LUMINANCE_ALPHA:
// eslint-disable-next-line no-throw-literal
throw "TEXTUREFORMAT_LUMINANCE_ALPHA format not supported in WebGPU";
default:
return "rgba8unorm" /* WebGPUConstants.TextureFormat.RGBA8Unorm */;
}
case Constants.TEXTURETYPE_SHORT:
switch (format) {
case Constants.TEXTUREFORMAT_RED_INTEGER:
return "r16sint" /* WebGPUConstants.TextureFormat.R16Sint */;
case Constants.TEXTUREFORMAT_RG_INTEGER:
return "rg16sint" /* WebGPUConstants.TextureFormat.RG16Sint */;
case Constants.TEXTUREFORMAT_RGB_INTEGER:
// eslint-disable-next-line no-throw-literal
throw "TEXTUREFORMAT_RGB_INTEGER format not supported in WebGPU";
case Constants.TEXTUREFORMAT_RGBA_INTEGER:
return "rgba16sint" /* WebGPUConstants.TextureFormat.RGBA16Sint */;
default:
return "rgba16sint" /* WebGPUConstants.TextureFormat.RGBA16Sint */;
}
case Constants.TEXTURETYPE_UNSIGNED_SHORT:
switch (format) {
case Constants.TEXTUREFORMAT_RED_INTEGER:
return "r16uint" /* WebGPUConstants.TextureFormat.R16Uint */;
case Constants.TEXTUREFORMAT_RG_INTEGER:
return "rg16uint" /* WebGPUConstants.TextureFormat.RG16Uint */;
case Constants.TEXTUREFORMAT_RGB_INTEGER:
// eslint-disable-next-line no-throw-literal
throw "TEXTUREFORMAT_RGB_INTEGER format not supported in WebGPU";
case Constants.TEXTUREFORMAT_RGBA_INTEGER:
return "rgba16uint" /* WebGPUConstants.TextureFormat.RGBA16Uint */;
default:
return "rgba16uint" /* WebGPUConstants.TextureFormat.RGBA16Uint */;
}
case Constants.TEXTURETYPE_INT:
switch (format) {
case Constants.TEXTUREFORMAT_RED_INTEGER:
return "r32sint" /* WebGPUConstants.TextureFormat.R32Sint */;
case Constants.TEXTUREFORMAT_RG_INTEGER:
return "rg32sint" /* WebGPUConstants.TextureFormat.RG32Sint */;
case Constants.TEXTUREFORMAT_RGB_INTEGER:
// eslint-disable-next-line no-throw-literal
throw "TEXTUREFORMAT_RGB_INTEGER format not supported in WebGPU";
case Constants.TEXTUREFORMAT_RGBA_INTEGER:
return "rgba32sint" /* WebGPUConstants.TextureFormat.RGBA32Sint */;
default:
return "rgba32sint" /* WebGPUConstants.TextureFormat.RGBA32Sint */;
}
case Constants.TEXTURETYPE_UNSIGNED_INTEGER: // Refers to UNSIGNED_INT
switch (format) {
case Constants.TEXTUREFORMAT_RED_INTEGER:
return "r32uint" /* WebGPUConstants.TextureFormat.R32Uint */;
case Constants.TEXTUREFORMAT_RG_INTEGER:
return "rg32uint" /* WebGPUConstants.TextureFormat.RG32Uint */;
case Constants.TEXTUREFORMAT_RGB_INTEGER:
// eslint-disable-next-line no-throw-literal
throw "TEXTUREFORMAT_RGB_INTEGER format not supported in WebGPU";
case Constants.TEXTUREFORMAT_RGBA_INTEGER:
return "rgba32uint" /* WebGPUConstants.TextureFormat.RGBA32Uint */;
default:
return "rgba32uint" /* WebGPUConstants.TextureFormat.RGBA32Uint */;
}
case Constants.TEXTURETYPE_FLOAT:
switch (format) {
case Constants.TEXTUREFORMAT_RED:
return "r32float" /* WebGPUConstants.TextureFormat.R32Float */; // By default. Other possibility is R16Float.
case Constants.TEXTUREFORMAT_RG:
return "rg32float" /* WebGPUConstants.TextureFormat.RG32Float */; // By default. Other possibility is RG16Float.
case Constants.TEXTUREFORMAT_RGB:
// eslint-disable-next-line no-throw-literal
throw "TEXTUREFORMAT_RGB format not supported in WebGPU";
case Constants.TEXTUREFORMAT_RGBA:
return "rgba32float" /* WebGPUConstants.TextureFormat.RGBA32Float */; // By default. Other possibility is RGBA16Float.
default:
return "rgba32float" /* WebGPUConstants.TextureFormat.RGBA32Float */;
}
case Constants.TEXTURETYPE_HALF_FLOAT:
switch (format) {
case Constants.TEXTUREFORMAT_RED:
return "r16float" /* WebGPUConstants.TextureFormat.R16Float */;
case Constants.TEXTUREFORMAT_RG:
return "rg16float" /* WebGPUConstants.TextureFormat.RG16Float */;
case Constants.TEXTUREFORMAT_RGB:
// eslint-disable-next-line no-throw-literal
throw "TEXTUREFORMAT_RGB format not supported in WebGPU";
case Constants.TEXTUREFORMAT_RGBA:
return "rgba16float" /* WebGPUConstants.TextureFormat.RGBA16Float */;
default:
return "rgba16float" /* WebGPUConstants.TextureFormat.RGBA16Float */;
}
case Constants.TEXTURETYPE_UNSIGNED_SHORT_5_6_5:
// eslint-disable-next-line no-throw-literal
throw "TEXTURETYPE_UNSIGNED_SHORT_5_6_5 format not supported in WebGPU";
case Constants.TEXTURETYPE_UNSIGNED_INT_10F_11F_11F_REV:
switch (format) {
case Constants.TEXTUREFORMAT_RGBA:
return "rg11b10ufloat" /* WebGPUConstants.TextureFormat.RG11B10UFloat */;
case Constants.TEXTUREFORMAT_RGBA_INTEGER:
// eslint-disable-next-line no-throw-literal
throw "TEXTUREFORMAT_RGBA_INTEGER format not supported in WebGPU when type is TEXTURETYPE_UNSIGNED_INT_10F_11F_11F_REV";
default:
return "rg11b10ufloat" /* WebGPUConstants.TextureFormat.RG11B10UFloat */;
}
case Constants.TEXTURETYPE_UNSIGNED_INT_5_9_9_9_REV:
switch (format) {
case Constants.TEXTUREFORMAT_RGBA:
return "rgb9e5ufloat" /* WebGPUConstants.TextureFormat.RGB9E5UFloat */;
case Constants.TEXTUREFORMAT_RGBA_INTEGER:
// eslint-disable-next-line no-throw-literal
throw "TEXTUREFORMAT_RGBA_INTEGER format not supported in WebGPU when type is TEXTURETYPE_UNSIGNED_INT_5_9_9_9_REV";
default:
return "rgb9e5ufloat" /* WebGPUConstants.TextureFormat.RGB9E5UFloat */;
}
case Constants.TEXTURETYPE_UNSIGNED_SHORT_4_4_4_4:
// eslint-disable-next-line no-throw-literal
throw "TEXTURETYPE_UNSIGNED_SHORT_4_4_4_4 format not supported in WebGPU";
case Constants.TEXTURETYPE_UNSIGNED_SHORT_5_5_5_1:
// eslint-disable-next-line no-throw-literal
throw "TEXTURETYPE_UNSIGNED_SHORT_5_5_5_1 format not supported in WebGPU";
case Constants.TEXTURETYPE_UNSIGNED_INT_2_10_10_10_REV:
switch (format) {
case Constants.TEXTUREFORMAT_RGBA:
return "rgb10a2unorm" /* WebGPUConstants.TextureFormat.RGB10A2Unorm */;
case Constants.TEXTUREFORMAT_RGBA_INTEGER:
return "rgb10a2uint" /* WebGPUConstants.TextureFormat.RGB10A2UINT */;
default:
return "rgb10a2unorm" /* WebGPUConstants.TextureFormat.RGB10A2Unorm */;
}
}
return useSRGBBuffer ? "rgba8unorm-srgb" /* WebGPUConstants.TextureFormat.RGBA8UnormSRGB */ : "rgba8unorm" /* WebGPUConstants.TextureFormat.RGBA8Unorm */;
}
static GetNumChannelsFromWebGPUTextureFormat(format) {
switch (format) {
case "r8unorm" /* WebGPUConstants.TextureFormat.R8Unorm */:
case "r8snorm" /* WebGPUConstants.TextureFormat.R8Snorm */:
case "r8uint" /* WebGPUConstants.TextureFormat.R8Uint */:
case "r8sint" /* WebGPUConstants.TextureFormat.R8Sint */:
case "bc4-r-unorm" /* WebGPUConstants.TextureFormat.BC4RUnorm */:
case "bc4-r-snorm" /* WebGPUConstants.TextureFormat.BC4RSnorm */:
case "r16uint" /* WebGPUConstants.TextureFormat.R16Uint */:
case "r16sint" /* WebGPUConstants.TextureFormat.R16Sint */:
case "depth16unorm" /* WebGPUConstants.TextureFormat.Depth16Unorm */:
case "r16float" /* WebGPUConstants.TextureFormat.R16Float */:
case "r16unorm" /* WebGPUConstants.TextureFormat.R16Unorm */:
case "r16snorm" /* WebGPUConstants.TextureFormat.R16Snorm */:
case "r32uint" /* WebGPUConstants.TextureFormat.R32Uint */:
case "r32sint" /* WebGPUConstants.TextureFormat.R32Sint */:
case "r32float" /* WebGPUConstants.TextureFormat.R32Float */:
case "depth32float" /* WebGPUConstants.TextureFormat.Depth32Float */:
case "stencil8" /* WebGPUConstants.TextureFormat.Stencil8 */:
case "depth24plus" /* WebGPUConstants.TextureFormat.Depth24Plus */:
case "eac-r11unorm" /* WebGPUConstants.TextureFormat.EACR11Unorm */:
case "eac-r11snorm" /* WebGPUConstants.TextureFormat.EACR11Snorm */:
return 1;
case "rg8unorm" /* WebGPUConstants.TextureFormat.RG8Unorm */:
case "rg8snorm" /* WebGPUConstants.TextureFormat.RG8Snorm */:
case "rg8uint" /* WebGPUConstants.TextureFormat.RG8Uint */:
case "rg8sint" /* WebGPUConstants.TextureFormat.RG8Sint */:
case "depth32float-stencil8" /* WebGPUConstants.TextureFormat.Depth32FloatStencil8 */:
case "bc5-rg-unorm" /* WebGPUConstants.TextureFormat.BC5RGUnorm */:
case "bc5-rg-snorm" /* WebGPUConstants.TextureFormat.BC5RGSnorm */:
case "rg16uint" /* WebGPUConstants.TextureFormat.RG16Uint */:
case "rg16sint" /* WebGPUConstants.TextureFormat.RG16Sint */:
case "rg16float" /* WebGPUConstants.TextureFormat.RG16Float */:
case "rg16unorm" /* WebGPUConstants.TextureFormat.RG16Unorm */:
case "rg16snorm" /* WebGPUConstants.TextureFormat.RG16Snorm */:
case "rg32uint" /* WebGPUConstants.TextureFormat.RG32Uint */:
case "rg32sint" /* WebGPUConstants.TextureFormat.RG32Sint */:
case "rg32float" /* WebGPUConstants.TextureFormat.RG32Float */:
case "depth24plus-stencil8" /* WebGPUConstants.TextureFormat.Depth24PlusStencil8 */:
case "eac-rg11unorm" /* WebGPUConstants.TextureFormat.EACRG11Unorm */:
case "eac-rg11snorm" /* WebGPUConstants.TextureFormat.EACRG11Snorm */:
return 2;
case "rgb9e5ufloat" /* WebGPUConstants.TextureFormat.RGB9E5UFloat */:
case "rg11b10ufloat" /* WebGPUConstants.TextureFormat.RG11B10UFloat */:
case "bc6h-rgb-ufloat" /* WebGPUConstants.TextureFormat.BC6HRGBUFloat */:
case "bc6h-rgb-float" /* WebGPUConstants.TextureFormat.BC6HRGBFloat */:
case "etc2-rgb8unorm" /* WebGPUConstants.TextureFormat.ETC2RGB8Unorm */:
case "etc2-rgb8unorm-srgb" /* WebGPUConstants.TextureFormat.ETC2RGB8UnormSRGB */:
return 3;
case "rgba8unorm" /* WebGPUConstants.TextureFormat.RGBA8Unorm */:
case "rgba8unorm-srgb" /* WebGPUConstants.TextureFormat.RGBA8UnormSRGB */:
case "rgba8snorm" /* WebGPUConstants.TextureFormat.RGBA8Snorm */:
case "rgba8uint" /* WebGPUConstants.TextureFormat.RGBA8Uint */:
case "rgba8sint" /* WebGPUConstants.TextureFormat.RGBA8Sint */:
case "bgra8unorm" /* WebGPUConstants.TextureFormat.BGRA8Unorm */:
case "bgra8unorm-srgb" /* WebGPUConstants.TextureFormat.BGRA8UnormSRGB */:
case "rgba16unorm" /* WebGPUConstants.TextureFormat.RGBA16Unorm */:
case "rgba16snorm" /* WebGPUConstants.TextureFormat.RGBA16Snorm */:
case "rgb10a2uint" /* WebGPUConstants.TextureFormat.RGB10A2UINT */:
case "rgb10a2unorm" /* WebGPUConstants.TextureFormat.RGB10A2Unorm */:
case "bc7-rgba-unorm" /* WebGPUConstants.TextureFormat.BC7RGBAUnorm */:
case "bc7-rgba-unorm-srgb" /* WebGPUConstants.TextureFormat.BC7RGBAUnormSRGB */:
case "bc3-rgba-unorm" /* WebGPUConstants.TextureFormat.BC3RGBAUnorm */:
case "bc3-rgba-unorm-srgb" /* WebGPUConstants.TextureFormat.BC3RGBAUnormSRGB */:
case "bc2-rgba-unorm" /* WebGPUConstants.TextureFormat.BC2RGBAUnorm */:
case "bc2-rgba-unorm-srgb" /* WebGPUConstants.TextureFormat.BC2RGBAUnormSRGB */:
case "bc1-rgba-unorm" /* WebGPUConstants.TextureFormat.BC1RGBAUnorm */:
case "bc1-rgba-unorm-srgb" /* WebGPUConstants.TextureFormat.BC1RGBAUnormSRGB */:
case "rgba16uint" /* WebGPUConstants.TextureFormat.RGBA16Uint */:
case "rgba16sint" /* WebGPUConstants.TextureFormat.RGBA16Sint */:
case "rgba16float" /* WebGPUConstants.TextureFormat.RGBA16Float */:
case "rgba32uint" /* WebGPUConstants.TextureFormat.RGBA32Uint */:
case "rgba32sint" /* WebGPUConstants.TextureFormat.RGBA32Sint */:
case "rgba32float" /* WebGPUConstants.TextureFormat.RGBA32Float */:
case "etc2-rgb8a1unorm" /* WebGPUConstants.TextureFormat.ETC2RGB8A1Unorm */:
case "etc2-rgb8a1unorm-srgb" /* WebGPUConstants.TextureFormat.ETC2RGB8A1UnormSRGB */:
case "etc2-rgba8unorm" /* WebGPUConstants.TextureFormat.ETC2RGBA8Unorm */:
case "etc2-rgba8unorm-srgb" /* WebGPUConstants.TextureFormat.ETC2RGBA8UnormSRGB */:
case "astc-4x4-unorm" /* WebGPUConstants.TextureFormat.ASTC4x4Unorm */:
case "astc-4x4-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC4x4UnormSRGB */:
case "astc-5x4-unorm" /* WebGPUConstants.TextureFormat.ASTC5x4Unorm */:
case "astc-5x4-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC5x4UnormSRGB */:
case "astc-5x5-unorm" /* WebGPUConstants.TextureFormat.ASTC5x5Unorm */:
case "astc-5x5-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC5x5UnormSRGB */:
case "astc-6x5-unorm" /* WebGPUConstants.TextureFormat.ASTC6x5Unorm */:
case "astc-6x5-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC6x5UnormSRGB */:
case "astc-6x6-unorm" /* WebGPUConstants.TextureFormat.ASTC6x6Unorm */:
case "astc-6x6-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC6x6UnormSRGB */:
case "astc-8x5-unorm" /* WebGPUConstants.TextureFormat.ASTC8x5Unorm */:
case "astc-8x5-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC8x5UnormSRGB */:
case "astc-8x6-unorm" /* WebGPUConstants.TextureFormat.ASTC8x6Unorm */:
case "astc-8x6-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC8x6UnormSRGB */:
case "astc-8x8-unorm" /* WebGPUConstants.TextureFormat.ASTC8x8Unorm */:
case "astc-8x8-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC8x8UnormSRGB */:
case "astc-10x5-unorm" /* WebGPUConstants.TextureFormat.ASTC10x5Unorm */:
case "astc-10x5-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC10x5UnormSRGB */:
case "astc-10x6-unorm" /* WebGPUConstants.TextureFormat.ASTC10x6Unorm */:
case "astc-10x6-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC10x6UnormSRGB */:
case "astc-10x8-unorm" /* WebGPUConstants.TextureFormat.ASTC10x8Unorm */:
case "astc-10x8-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC10x8UnormSRGB */:
case "astc-10x10-unorm" /* WebGPUConstants.TextureFormat.ASTC10x10Unorm */:
case "astc-10x10-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC10x10UnormSRGB */:
case "astc-12x10-unorm" /* WebGPUConstants.TextureFormat.ASTC12x10Unorm */:
case "astc-12x10-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC12x10UnormSRGB */:
case "astc-12x12-unorm" /* WebGPUConstants.TextureFormat.ASTC12x12Unorm */:
case "astc-12x12-unorm-srgb" /* WebGPUConstants.TextureFormat.ASTC12x12UnormSRGB */:
return 4;
}
// eslint-disable-next-line no-throw-literal
throw `Unknown format ${format}!`;
}
static HasStencilAspect(format) {
switch (format) {
case "stencil8" /* WebGPUConstants.TextureFormat.Stencil8 */:
case "depth32float-stencil8" /* WebGPUConstants.TextureFormat.Depth32FloatStencil8 */:
case "depth24plus-stencil8" /* WebGPUConstants.TextureFormat.Depth24PlusStencil8 */:
return true;
}
return false;
}
static HasDepthAspect(format) {
switch (format) {
case "depth16unorm" /* WebGPUConstants.TextureFormat.Depth16Unorm */:
case "depth24plus" /* WebGPUConstants.TextureFormat.Depth24Plus */:
case "depth24plus-stencil8" /* WebGPUConstants.TextureFormat.Depth24PlusStencil8 */:
case "depth32float" /* WebGPUConstants.TextureFormat.Depth32Float */:
case "depth32float-stencil8" /* WebGPUConstants.TextureFormat.Depth32FloatStencil8 */:
return true;
}
return false;
}
static HasDepthAndStencilAspects(format) {
switch (format) {
case "depth32float-stencil8" /* WebGPUConstants.TextureFormat.Depth32FloatStencil8 */:
case "depth24plus-stencil8" /* WebGPUConstants.TextureFormat.Depth24PlusStencil8 */:
return true;
}
return false;
}
static GetDepthFormatOnly(format) {
switch (format) {
case "depth16unorm" /* WebGPUConstants.TextureFormat.Depth16Unorm */:
return "depth16unorm" /* WebGPUConstants.TextureFormat.Depth16Unorm */;
case "depth24plus" /* WebGPUConstants.TextureFormat.Depth24Plus */:
return "depth24plus" /* WebGPUConstants.TextureFormat.Depth24Plus */;
case "depth24plus-stencil8" /* WebGPUConstants.TextureFormat.Depth24PlusStencil8 */:
return "depth24plus" /* WebGPUConstants.TextureFormat.Depth24Plus */;
case "depth32float" /* WebGPUConstants.TextureFormat.Depth32Float */:
return "depth32float" /* WebGPUConstants.TextureFormat.Depth32Float */;
case "depth32float-stencil8" /* WebGPUConstants.TextureFormat.Depth32FloatStencil8 */:
return "depth32float" /* WebGPUConstants.TextureFormat.Depth32Float */;
}
return format;
}
static GetSample(sampleCount) {
// WebGPU only supports 1 or 4
return sampleCount > 1 ? 4 : 1;
}
}
/**
* Class used to define a WebGPU performance counter
*/
class WebGPUPerfCounter {
constructor() {
this._gpuTimeInFrameId = -1;
/**
* The GPU time in nanoseconds spent in the last frame
*/
this.counter = new PerfCounter();
}
/**
* @internal
*/
_addDuration(currentFrameId, duration) {
if (currentFrameId < this._gpuTimeInFrameId) {
return;
}
if (this._gpuTimeInFrameId !== currentFrameId) {
this.counter._fetchResult();
this.counter.fetchNewFrame();
this.counter.addCount(duration, false);
this._gpuTimeInFrameId = currentFrameId;
}
else {
this.counter.addCount(duration, false);
}
}
}
/**
* The base engine class for WebGPU
*/
class ThinWebGPUEngine extends AbstractEngine {
constructor() {
super(...arguments);
// TODO WEBGPU remove those variables when code stabilized
/** @internal */
this.dbgShowShaderCode = false;
/** @internal */
this.dbgSanityChecks = true;
/** @internal */
this.dbgLogIfNotDrawWrapper = true;
/** @internal */
this.dbgShowEmptyEnableEffectCalls = true;
/** @internal */
this._currentRenderPass = null;
this._snapshotRenderingMode = Constants.SNAPSHOTRENDERING_STANDARD;
/** @internal */
this._timestampIndex = 0;
/** @internal */
this._showGPUDebugMarkersLog = false;
/** @internal */
this._debugMarkersEncoderGroups = [];
/** @internal */
this._debugMarkersPassGroups = [];
/** @internal */
this._debugMarkersPendingEncoderPops = 0;
}
/**
* Enables or disables GPU timing measurements.
* Note that this is only supported if the "timestamp-query" extension is enabled in the options.
*/
get enableGPUTimingMeasurements() {
return this._timestampQuery.enable;
}
set enableGPUTimingMeasurements(enable) {
if (this._timestampQuery.enable === enable) {
return;
}
this.gpuTimeInFrameForMainPass = enable ? new WebGPUPerfCounter() : undefined;
this._timestampQuery.enable = enable;
}
_currentPassIsMainPass() {
return this._currentRenderTarget === null;
}
/** @internal */
_endCurrentRenderPass() {
if (!this._currentRenderPass) {
return 0;
}
this._debugPopBeforeEndOfEncoder();
const currentPassIndex = this._currentPassIsMainPass() ? 2 : 1;
if (!this._snapshotRendering.endRenderPass(this._currentRenderPass) && !this.compatibilityMode) {
this._bundleList.run(this._currentRenderPass);
this._bundleList.reset();
}
this._currentRenderPass.end();
this._timestampQuery.endPass(this._timestampIndex, (this._currentRenderTarget && this._currentRenderTarget.gpuTimeInFrame
? this._currentRenderTarget.gpuTimeInFrame
: this.gpuTimeInFrameForMainPass));
this._timestampIndex += 2;
this._debugPendingPop(this._currentRenderPass);
this._currentRenderPass = null;
return currentPassIndex;
}
/**
* @internal
*/
_generateMipmaps(texture, commandEncoder) {
commandEncoder = commandEncoder ?? this._renderEncoder;
const gpuHardwareTexture = texture._hardwareTexture;
if (!gpuHardwareTexture) {
return;
}
if (commandEncoder === this._renderEncoder) {
// We must close the current pass (if any) because we are going to use the render encoder to generate the mipmaps (so, we are going to create a new render pass)
this._endCurrentRenderPass();
}
const mipmapCount = WebGPUTextureHelper.ComputeNumMipmapLevels(texture.width, texture.height);
if (texture.isCube) {
this._textureHelper.generateCubeMipmaps(gpuHardwareTexture, mipmapCount, commandEncoder);
}
else if (texture._source === 3 /* InternalTextureSource.Raw */ || texture._source === 11 /* InternalTextureSource.Raw2DArray */) {
this._textureHelper.generateMipmaps(gpuHardwareTexture, texture.mipLevelCount, 0, commandEncoder);
}
else {
this._textureHelper.generateMipmaps(gpuHardwareTexture, mipmapCount, 0, commandEncoder);
}
}
_debugPopBeforeEndOfEncoder() {
if (!this._enableGPUDebugMarkers) {
return;
}
// When a render pass is active, pop its groups; otherwise pop encoder-level groups.
// Pass-level groups are never pushed on the encoder, so we never pop them from it.
const groups = this._currentRenderPass ? this._debugMarkersPassGroups : this._debugMarkersEncoderGroups;
const target = this._currentRenderPass ?? this._renderEncoder;
for (let i = groups.length - 1; i >= 0; --i) {
if (this._showGPUDebugMarkersLog) {
Logger.Log(`[${this.frameId}] [E${this._debugMarkersEncoderGroups.length}|P${this._debugMarkersPassGroups.length}] [automatic] Popping debug group '${groups[i]}' on '${target.label}'.`);
}
target.popDebugGroup();
}
}
_debugPushAfterStartOfEncoder() {
if (!this._enableGPUDebugMarkers) {
return;
}
// When a render pass is active, re-push its floating groups onto it; otherwise re-push
// encoder-level groups onto the new render encoder.
// Pass-level groups stay floating until the next render pass starts.
const groups = this._currentRenderPass ? this._debugMarkersPassGroups : this._debugMarkersEncoderGroups;
const target = this._currentRenderPass ?? this._renderEncoder;
for (const groupName of groups) {
if (this._showGPUDebugMarkersLog) {
Logger.Log(`[${this.frameId}] [E${this._debugMarkersEncoderGroups.length}|P${this._debugMarkersPassGroups.length}] [automatic] Pushing debug group '${groupName}' on '${target.label}'.`);
}
target.pushDebugGroup(groupName);
}
}
_debugPendingPop(currentRenderPass) {
if (!this._enableGPUDebugMarkers) {
return;
}
// The user popped encoder-level groups while a render pass was active (the pass was the live
// object, so the pops were deferred). Now that the pass has ended we replay them on the render
// encoder. Because _debugMarkersEncoderGroups only ever contains encoder-level entries, popping
// from it here can never accidentally consume a pass-level group.
while (this._debugMarkersPendingEncoderPops-- > 0) {
const groupName = this._debugMarkersEncoderGroups.pop();
if (this._showGPUDebugMarkersLog) {
Logger.Log(`[${this.frameId}] [E${this._debugMarkersEncoderGroups.length}|P${this._debugMarkersPassGroups.length}] [automatic] Popping debug group '${groupName}' on render encoder '${this._renderEncoder.label}' after the end of render pass '${currentRenderPass.label}'.`);
}
this._renderEncoder.popDebugGroup();
}
this._debugMarkersPendingEncoderPops = 0;
}
}
/* eslint-disable @typescript-eslint/naming-convention */
/** @internal */
// eslint-disable-next-line import/export
var PowerPreference;
(function (PowerPreference) {
PowerPreference["LowPower"] = "low-power";
PowerPreference["HighPerformance"] = "high-performance";
})(PowerPreference || (PowerPreference = {}));
/** @internal */
var FeatureName;
(function (FeatureName) {
FeatureName["CoreFeaturesAndLimits"] = "core-features-and-limits";
FeatureName["DepthClipControl"] = "depth-clip-control";
FeatureName["Depth32FloatStencil8"] = "depth32float-stencil8";
FeatureName["TextureCompressionBC"] = "texture-compression-bc";
FeatureName["TextureCompressionBCSliced3D"] = "texture-compression-bc-sliced-3d";
FeatureName["TextureCompressionETC2"] = "texture-compression-etc2";
FeatureName["TextureCompressionASTC"] = "texture-compression-astc";
FeatureName["TextureCompressionASTCSliced3D"] = "texture-compression-astc-sliced-3d";
FeatureName["TimestampQuery"] = "timestamp-query";
FeatureName["IndirectFirstInstance"] = "indirect-first-instance";
FeatureName["ShaderF16"] = "shader-f16";
FeatureName["RG11B10UFloatRenderable"] = "rg11b10ufloat-renderable";
FeatureName["BGRA8UnormStorage"] = "bgra8unorm-storage";
FeatureName["Float32Filterable"] = "float32-filterable";
FeatureName["Float32Blendable"] = "float32-blendable";
FeatureName["ClipDistances"] = "clip-distances";
FeatureName["DualSourceBlending"] = "dual-source-blending";
FeatureName["Subgroups"] = "subgroups";
FeatureName["TextureFormatsTier1"] = "texture-formats-tier1";
FeatureName["TextureFormatsTier2"] = "texture-formats-tier2";
FeatureName["PrimitiveIndex"] = "primitive-index";
FeatureName["TextureComponentSwizzle"] = "texture-component-swizzle";
})(FeatureName || (FeatureName = {}));
/** @internal */
var BufferMapState;
(function (BufferMapState) {
BufferMapState["Unmapped"] = "unmapped";
BufferMapState["Pending"] = "pending";
BufferMapState["Mapped"] = "mapped";
})(BufferMapState || (BufferMapState = {}));
/** @internal */
var BufferUsage;
(function (BufferUsage) {
BufferUsage[BufferUsage["MapRead"] = 1] = "MapRead";
BufferUsage[BufferUsage["MapWrite"] = 2] = "MapWrite";
BufferUsage[BufferUsage["CopySrc"] = 4] = "CopySrc";
BufferUsage[BufferUsage["CopyDst"] = 8] = "CopyDst";
BufferUsage[BufferUsage["Index"] = 16] = "Index";
BufferUsage[BufferUsage["Vertex"] = 32] = "Vertex";
BufferUsage[BufferUsage["Uniform"] = 64] = "Uniform";
BufferUsage[BufferUsage["Storage"] = 128] = "Storage";
BufferUsage[BufferUsage["Indirect"] = 256] = "Indirect";
BufferUsage[BufferUsage["QueryResolve"] = 512] = "QueryResolve";
})(BufferUsage || (BufferUsage = {}));
/** @internal */
var MapMode;
(function (MapMode) {
MapMode[MapMode["Read"] = 1] = "Read";
MapMode[MapMode["Write"] = 2] = "Write";
})(MapMode || (MapMode = {}));
/** @internal */
var TextureDimension;
(function (TextureDimension) {
TextureDimension["E1d"] = "1d";
TextureDimension["E2d"] = "2d";
TextureDimension["E3d"] = "3d";
})(TextureDimension || (TextureDimension = {}));
/** @internal */
var TextureUsage;
(function (TextureUsage) {
TextureUsage[TextureUsage["CopySrc"] = 1] = "CopySrc";
TextureUsage[TextureUsage["CopyDst"] = 2] = "CopyDst";
TextureUsage[TextureUsage["TextureBinding"] = 4] = "TextureBinding";
TextureUsage[TextureUsage["StorageBinding"] = 8] = "StorageBinding";
TextureUsage[TextureUsage["RenderAttachment"] = 16] = "RenderAttachment";
TextureUsage[TextureUsage["TransientAttachment"] = 32] = "TransientAttachment";
})(TextureUsage || (TextureUsage = {}));
/** @internal */
var TextureViewDimension;
(function (TextureViewDimension) {
TextureViewDimension["E1d"] = "1d";
TextureViewDimension["E2d"] = "2d";
TextureViewDimension["E2dArray"] = "2d-array";
TextureViewDimension["Cube"] = "cube";
TextureViewDimension["CubeArray"] = "cube-array";
TextureViewDimension["E3d"] = "3d";
})(TextureViewDimension || (TextureViewDimension = {}));
/** @internal */
var TextureAspect;
(function (TextureAspect) {
TextureAspect["All"] = "all";
TextureAspect["StencilOnly"] = "stencil-only";
TextureAspect["DepthOnly"] = "depth-only";
})(TextureAspect || (TextureAspect = {}));
/**
* Comments taken from https://github.com/gfx-rs/wgpu/blob/master/wgpu-types/src/lib.rs
* @internal
*/
var TextureFormat;
(function (TextureFormat) {
// 8-bit formats
TextureFormat["R8Unorm"] = "r8unorm";
TextureFormat["R8Snorm"] = "r8snorm";
TextureFormat["R8Uint"] = "r8uint";
TextureFormat["R8Sint"] = "r8sint";
// 16-bit formats
TextureFormat["R16Uint"] = "r16uint";
TextureFormat["R16Sint"] = "r16sint";
TextureFormat["R16Float"] = "r16float";
TextureFormat["RG8Unorm"] = "rg8unorm";
TextureFormat["RG8Snorm"] = "rg8snorm";
TextureFormat["RG8Uint"] = "rg8uint";
TextureFormat["RG8Sint"] = "rg8sint";
TextureFormat["R16Unorm"] = "r16unorm";
TextureFormat["R16Snorm"] = "r16snorm";
// 32-bit formats
TextureFormat["R32Uint"] = "r32uint";
TextureFormat["R32Sint"] = "r32sint";
TextureFormat["R32Float"] = "r32float";
TextureFormat["RG16Uint"] = "rg16uint";
TextureFormat["RG16Sint"] = "rg16sint";
TextureFormat["RG16Float"] = "rg16float";
TextureFormat["RGBA8Unorm"] = "rgba8unorm";
TextureFormat["RGBA8UnormSRGB"] = "rgba8unorm-srgb";
TextureFormat["RGBA8Snorm"] = "rgba8snorm";
TextureFormat["RGBA8Uint"] = "rgba8uint";
TextureFormat["RGBA8Sint"] = "rgba8sint";
TextureFormat["BGRA8Unorm"] = "bgra8unorm";
TextureFormat["BGRA8UnormSRGB"] = "bgra8unorm-srgb";
TextureFormat["RG16Unorm"] = "rg16unorm";
TextureFormat["RG16Snorm"] = "rg16snorm";
// Packed 32-bit formats
TextureFormat["RGB9E5UFloat"] = "rgb9e5ufloat";
TextureFormat["RGB10A2UINT"] = "rgb10a2uint";
TextureFormat["RGB10A2Unorm"] = "rgb10a2unorm";
TextureFormat["RG11B10UFloat"] = "rg11b10ufloat";
// 64-bit formats
TextureFormat["RG32Uint"] = "rg32uint";
TextureFormat["RG32Sint"] = "rg32sint";
TextureFormat["RG32Float"] = "rg32float";
TextureFormat["RGBA16Uint"] = "rgba16uint";
TextureFormat["RGBA16Sint"] = "rgba16sint";
TextureFormat["RGBA16Float"] = "rgba16float";
TextureFormat["RGBA16Unorm"] = "rgba16unorm";
TextureFormat["RGBA16Snorm"] = "rgba16snorm";
// 128-bit formats
TextureFormat["RGBA32Uint"] = "rgba32uint";
TextureFormat["RGBA32Sint"] = "rgba32sint";
TextureFormat["RGBA32Float"] = "rgba32float";
// Depth and stencil formats
TextureFormat["Stencil8"] = "stencil8";
TextureFormat["Depth16Unorm"] = "depth16unorm";
TextureFormat["Depth24Plus"] = "depth24plus";
TextureFormat["Depth24PlusStencil8"] = "depth24plus-stencil8";
TextureFormat["Depth32Float"] = "depth32float";
// BC compressed formats usable if "texture-compression-bc" is both
// supported by the device/user agent and enabled in requestDevice.
TextureFormat["BC1RGBAUnorm"] = "bc1-rgba-unorm";
TextureFormat["BC1RGBAUnormSRGB"] = "bc1-rgba-unorm-srgb";
TextureFormat["BC2RGBAUnorm"] = "bc2-rgba-unorm";
TextureFormat["BC2RGBAUnormSRGB"] = "bc2-rgba-unorm-srgb";
TextureFormat["BC3RGBAUnorm"] = "bc3-rgba-unorm";
TextureFormat["BC3RGBAUnormSRGB"] = "bc3-rgba-unorm-srgb";
TextureFormat["BC4RUnorm"] = "bc4-r-unorm";
TextureFormat["BC4RSnorm"] = "bc4-r-snorm";
TextureFormat["BC5RGUnorm"] = "bc5-rg-unorm";
TextureFormat["BC5RGSnorm"] = "bc5-rg-snorm";
TextureFormat["BC6HRGBUFloat"] = "bc6h-rgb-ufloat";
TextureFormat["BC6HRGBFloat"] = "bc6h-rgb-float";
TextureFormat["BC7RGBAUnorm"] = "bc7-rgba-unorm";
TextureFormat["BC7RGBAUnormSRGB"] = "bc7-rgba-unorm-srgb";
// ETC2 compressed formats usable if "texture-compression-etc2" is both
// supported by the device/user agent and enabled in requestDevice.
TextureFormat["ETC2RGB8Unorm"] = "etc2-rgb8unorm";
TextureFormat["ETC2RGB8UnormSRGB"] = "etc2-rgb8unorm-srgb";
TextureFormat["ETC2RGB8A1Unorm"] = "etc2-rgb8a1unorm";
TextureFormat["ETC2RGB8A1UnormSRGB"] = "etc2-rgb8a1unorm-srgb";
TextureFormat["ETC2RGBA8Unorm"] = "etc2-rgba8unorm";
TextureFormat["ETC2RGBA8UnormSRGB"] = "etc2-rgba8unorm-srgb";
TextureFormat["EACR11Unorm"] = "eac-r11unorm";
TextureFormat["EACR11Snorm"] = "eac-r11snorm";
TextureFormat["EACRG11Unorm"] = "eac-rg11unorm";
TextureFormat["EACRG11Snorm"] = "eac-rg11snorm";
// ASTC compressed formats usable if "texture-compression-astc" is both
// supported by the device/user agent and enabled in requestDevice.
TextureFormat["ASTC4x4Unorm"] = "astc-4x4-unorm";
TextureFormat["ASTC4x4UnormSRGB"] = "astc-4x4-unorm-srgb";
TextureFormat["ASTC5x4Unorm"] = "astc-5x4-unorm";
TextureFormat["ASTC5x4UnormSRGB"] = "astc-5x4-unorm-srgb";
TextureFormat["ASTC5x5Unorm"] = "astc-5x5-unorm";
TextureFormat["ASTC5x5UnormSRGB"] = "astc-5x5-unorm-srgb";
TextureFormat["ASTC6x5Unorm"] = "astc-6x5-unorm";
TextureFormat["ASTC6x5UnormSRGB"] = "astc-6x5-unorm-srgb";
TextureFormat["ASTC6x6Unorm"] = "astc-6x6-unorm";
TextureFormat["ASTC6x6UnormSRGB"] = "astc-6x6-unorm-srgb";
TextureFormat["ASTC8x5Unorm"] = "astc-8x5-unorm";
TextureFormat["ASTC8x5UnormSRGB"] = "astc-8x5-unorm-srgb";
TextureFormat["ASTC8x6Unorm"] = "astc-8x6-unorm";
TextureFormat["ASTC8x6UnormSRGB"] = "astc-8x6-unorm-srgb";
TextureFormat["ASTC8x8Unorm"] = "astc-8x8-unorm";
TextureFormat["ASTC8x8UnormSRGB"] = "astc-8x8-unorm-srgb";
TextureFormat["ASTC10x5Unorm"] = "astc-10x5-unorm";
TextureFormat["ASTC10x5UnormSRGB"] = "astc-10x5-unorm-srgb";
TextureFormat["ASTC10x6Unorm"] = "astc-10x6-unorm";
TextureFormat["ASTC10x6UnormSRGB"] = "astc-10x6-unorm-srgb";
TextureFormat["ASTC10x8Unorm"] = "astc-10x8-unorm";
TextureFormat["ASTC10x8UnormSRGB"] = "astc-10x8-unorm-srgb";
TextureFormat["ASTC10x10Unorm"] = "astc-10x10-unorm";
TextureFormat["ASTC10x10UnormSRGB"] = "astc-10x10-unorm-srgb";
TextureFormat["ASTC12x10Unorm"] = "astc-12x10-unorm";
TextureFormat["ASTC12x10UnormSRGB"] = "astc-12x10-unorm-srgb";
TextureFormat["ASTC12x12Unorm"] = "astc-12x12-unorm";
TextureFormat["ASTC12x12UnormSRGB"] = "astc-12x12-unorm-srgb";
// "depth32float-stencil8" feature
TextureFormat["Depth32FloatStencil8"] = "depth32float-stencil8";
})(TextureFormat || (TextureFormat = {}));
/** @internal */
var AddressMode;
(function (AddressMode) {
AddressMode["ClampToEdge"] = "clamp-to-edge";
AddressMode["Repeat"] = "repeat";
AddressMode["MirrorRepeat"] = "mirror-repeat";
})(AddressMode || (AddressMode = {}));
/** @internal */
var FilterMode;
(function (FilterMode) {
FilterMode["Nearest"] = "nearest";
FilterMode["Linear"] = "linear";
})(FilterMode || (FilterMode = {}));
/** @internal */
var MipmapFilterMode;
(function (MipmapFilterMode) {
MipmapFilterMode["Nearest"] = "nearest";
MipmapFilterMode["Linear"] = "linear";
})(MipmapFilterMode || (MipmapFilterMode = {}));
/** @internal */
var CompareFunction;
(function (CompareFunction) {
CompareFunction["Never"] = "never";
CompareFunction["Less"] = "less";
CompareFunction["Equal"] = "equal";
CompareFunction["LessEqual"] = "less-equal";
CompareFunction["Greater"] = "greater";
CompareFunction["NotEqual"] = "not-equal";
CompareFunction["GreaterEqual"] = "greater-equal";
CompareFunction["Always"] = "always";
})(CompareFunction || (CompareFunction = {}));
/** @internal */
var ShaderStage;
(function (ShaderStage) {
ShaderStage[ShaderStage["Vertex"] = 1] = "Vertex";
ShaderStage[ShaderStage["Fragment"] = 2] = "Fragment";
ShaderStage[ShaderStage["Compute"] = 4] = "Compute";
})(ShaderStage || (ShaderStage = {}));
/** @internal */
var BufferBindingType;
(function (BufferBindingType) {
BufferBindingType["Uniform"] = "uniform";
BufferBindingType["Storage"] = "storage";
BufferBindingType["ReadOnlyStorage"] = "read-only-storage";
})(BufferBindingType || (BufferBindingType = {}));
/** @internal */
var SamplerBindingType;
(function (SamplerBindingType) {
SamplerBindingType["Filtering"] = "filtering";
SamplerBindingType["NonFiltering"] = "non-filtering";
SamplerBindingType["Comparison"] = "comparison";
})(SamplerBindingType || (SamplerBindingType = {}));
/** @internal */
var TextureSampleType;
(function (TextureSampleType) {
TextureSampleType["Float"] = "float";
TextureSampleType["UnfilterableFloat"] = "unfilterable-float";
TextureSampleType["Depth"] = "depth";
TextureSampleType["Sint"] = "sint";
TextureSampleType["Uint"] = "uint";
})(TextureSampleType || (TextureSampleType = {}));
/** @internal */
var StorageTextureAccess;
(function (StorageTextureAccess) {
StorageTextureAccess["WriteOnly"] = "write-only";
StorageTextureAccess["ReadOnly"] = "read-only";
StorageTextureAccess["ReadWrite"] = "read-write";
})(StorageTextureAccess || (StorageTextureAccess = {}));
/** @internal */
var CompilationMessageType;
(function (CompilationMessageType) {
CompilationMessageType["Error"] = "error";
CompilationMessageType["Warning"] = "warning";
CompilationMessageType["Info"] = "info";
})(CompilationMessageType || (CompilationMessageType = {}));
/** @internal */
var PipelineErrorReason;
(function (PipelineErrorReason) {
PipelineErrorReason["Validation"] = "validation";
PipelineErrorReason["Internal"] = "internal";
})(PipelineErrorReason || (PipelineErrorReason = {}));
/** @internal */
var AutoLayoutMode;
(function (AutoLayoutMode) {
AutoLayoutMode["Auto"] = "auto";
})(AutoLayoutMode || (AutoLayoutMode = {}));
/** @internal */
var PrimitiveTopology;
(function (PrimitiveTopology) {
PrimitiveTopology["PointList"] = "point-list";
PrimitiveTopology["LineList"] = "line-list";
PrimitiveTopology["LineStrip"] = "line-strip";
PrimitiveTopology["TriangleList"] = "triangle-list";
PrimitiveTopology["TriangleStrip"] = "triangle-strip";
})(PrimitiveTopology || (PrimitiveTopology = {}));
/** @internal */
var FrontFace;
(function (FrontFace) {
FrontFace["CCW"] = "ccw";
FrontFace["CW"] = "cw";
})(FrontFace || (FrontFace = {}));
/** @internal */
var CullMode;
(function (CullMode) {
CullMode["None"] = "none";
CullMode["Front"] = "front";
CullMode["Back"] = "back";
})(CullMode || (CullMode = {}));
/** @internal */
var ColorWrite;
(function (ColorWrite) {
ColorWrite[ColorWrite["Red"] = 1] = "Red";
ColorWrite[ColorWrite["Green"] = 2] = "Green";
ColorWrite[ColorWrite["Blue"] = 4] = "Blue";
ColorWrite[ColorWrite["Alpha"] = 8] = "Alpha";
ColorWrite[ColorWrite["All"] = 15] = "All";
})(ColorWrite || (ColorWrite = {}));
/** @internal */
var BlendFactor;
(function (BlendFactor) {
BlendFactor["Zero"] = "zero";
BlendFactor["One"] = "one";
BlendFactor["Src"] = "src";
BlendFactor["OneMinusSrc"] = "one-minus-src";
BlendFactor["SrcAlpha"] = "src-alpha";
BlendFactor["OneMinusSrcAlpha"] = "one-minus-src-alpha";
BlendFactor["Dst"] = "dst";
BlendFactor["OneMinusDst"] = "one-minus-dst";
BlendFactor["DstAlpha"] = "dst-alpha";
BlendFactor["OneMinusDstAlpha"] = "one-minus-dst-alpha";
BlendFactor["SrcAlphaSaturated"] = "src-alpha-saturated";
BlendFactor["Constant"] = "constant";
BlendFactor["OneMinusConstant"] = "one-minus-constant";
BlendFactor["Src1"] = "src1";
BlendFactor["OneMinusSrc1"] = "one-minus-src1";
BlendFactor["Src1Alpha"] = "src1-alpha";
BlendFactor["OneMinusSrc1Alpha"] = "one-minus-src1-alpha";
})(BlendFactor || (BlendFactor = {}));
/** @internal */
var BlendOperation;
(function (BlendOperation) {
BlendOperation["Add"] = "add";
BlendOperation["Subtract"] = "subtract";
BlendOperation["ReverseSubtract"] = "reverse-subtract";
BlendOperation["Min"] = "min";
BlendOperation["Max"] = "max";
})(BlendOperation || (BlendOperation = {}));
/** @internal */
var StencilOperation;
(function (StencilOperation) {
StencilOperation["Keep"] = "keep";
StencilOperation["Zero"] = "zero";
StencilOperation["Replace"] = "replace";
StencilOperation["Invert"] = "invert";
StencilOperation["IncrementClamp"] = "increment-clamp";
StencilOperation["DecrementClamp"] = "decrement-clamp";
StencilOperation["IncrementWrap"] = "increment-wrap";
StencilOperation["DecrementWrap"] = "decrement-wrap";
})(StencilOperation || (StencilOperation = {}));
/** @internal */
var IndexFormat;
(function (IndexFormat) {
IndexFormat["Uint16"] = "uint16";
IndexFormat["Uint32"] = "uint32";
})(IndexFormat || (IndexFormat = {}));
/** @internal */
var VertexFormat;
(function (VertexFormat) {
VertexFormat["Uint8"] = "uint8";
VertexFormat["Uint8x2"] = "uint8x2";
VertexFormat["Uint8x4"] = "uint8x4";
VertexFormat["Sint8"] = "sint8";
VertexFormat["Sint8x2"] = "sint8x2";
VertexFormat["Sint8x4"] = "sint8x4";
VertexFormat["Unorm8"] = "unorm8";
VertexFormat["Unorm8x2"] = "unorm8x2";
VertexFormat["Unorm8x4"] = "unorm8x4";
VertexFormat["Snorm8"] = "snorm8";
VertexFormat["Snorm8x2"] = "snorm8x2";
VertexFormat["Snorm8x4"] = "snorm8x4";
VertexFormat["Uint16"] = "uint16";
VertexFormat["Uint16x2"] = "uint16x2";
VertexFormat["Uint16x4"] = "uint16x4";
VertexFormat["Sint16"] = "sint16";
VertexFormat["Sint16x2"] = "sint16x2";
VertexFormat["Sint16x4"] = "sint16x4";
VertexFormat["Unorm16"] = "unorm16";
VertexFormat["Unorm16x2"] = "unorm16x2";
VertexFormat["Unorm16x4"] = "unorm16x4";
VertexFormat["Snorm16"] = "snorm16";
VertexFormat["Snorm16x2"] = "snorm16x2";
VertexFormat["Snorm16x4"] = "snorm16x4";
VertexFormat["Float16"] = "float16";
VertexFormat["Float16x2"] = "float16x2";
VertexFormat["Float16x4"] = "float16x4";
VertexFormat["Float32"] = "float32";
VertexFormat["Float32x2"] = "float32x2";
VertexFormat["Float32x3"] = "float32x3";
VertexFormat["Float32x4"] = "float32x4";
VertexFormat["Uint32"] = "uint32";
VertexFormat["Uint32x2"] = "uint32x2";
VertexFormat["Uint32x3"] = "uint32x3";
VertexFormat["Uint32x4"] = "uint32x4";
VertexFormat["Sint32"] = "sint32";
VertexFormat["Sint32x2"] = "sint32x2";
VertexFormat["Sint32x3"] = "sint32x3";
VertexFormat["Sint32x4"] = "sint32x4";
VertexFormat["UNORM10x10x10x2"] = "unorm10-10-10-2";
VertexFormat["UNORM8x4BGRA"] = "unorm8x4-bgra";
})(VertexFormat || (VertexFormat = {}));
/** @internal */
var VertexStepMode;
(function (VertexStepMode) {
VertexStepMode["Vertex"] = "vertex";
VertexStepMode["Instance"] = "instance";
})(VertexStepMode || (VertexStepMode = {}));
/** @internal */
var ComputePassTimestampLocation;
(function (ComputePassTimestampLocation) {
ComputePassTimestampLocation["Beginning"] = "beginning";
ComputePassTimestampLocation["End"] = "end";
})(ComputePassTimestampLocation || (ComputePassTimestampLocation = {}));
/** @internal */
var RenderPassTimestampLocation;
(function (RenderPassTimestampLocation) {
RenderPassTimestampLocation["Beginning"] = "beginning";
RenderPassTimestampLocation["End"] = "end";
})(RenderPassTimestampLocation || (RenderPassTimestampLocation = {}));
/** @internal */
var LoadOp;
(function (LoadOp) {
LoadOp["Load"] = "load";
LoadOp["Clear"] = "clear";
})(LoadOp || (LoadOp = {}));
/** @internal */
var StoreOp;
(function (StoreOp) {
StoreOp["Store"] = "store";
StoreOp["Discard"] = "discard";
})(StoreOp || (StoreOp = {}));
/** @internal */
var QueryType;
(function (QueryType) {
QueryType["Occlusion"] = "occlusion";
QueryType["Timestamp"] = "timestamp";
})(QueryType || (QueryType = {}));
/** @internal */
var CanvasAlphaMode;
(function (CanvasAlphaMode) {
CanvasAlphaMode["Opaque"] = "opaque";
CanvasAlphaMode["Premultiplied"] = "premultiplied";
})(CanvasAlphaMode || (CanvasAlphaMode = {}));
/** @internal */
var CanvasToneMappingMode;
(function (CanvasToneMappingMode) {
CanvasToneMappingMode["Standard"] = "standard";
CanvasToneMappingMode["Extended"] = "extended";
})(CanvasToneMappingMode || (CanvasToneMappingMode = {}));
/** @internal */
var DeviceLostReason;
(function (DeviceLostReason) {
DeviceLostReason["Unknown"] = "unknown";
DeviceLostReason["Destroyed"] = "destroyed";
})(DeviceLostReason || (DeviceLostReason = {}));
/** @internal */
var ErrorFilter;
(function (ErrorFilter) {
ErrorFilter["Validation"] = "validation";
ErrorFilter["OutOfMemory"] = "out-of-memory";
ErrorFilter["Internal"] = "internal";
})(ErrorFilter || (ErrorFilter = {}));
/** @internal */
class WebGPUShaderProcessor {
constructor() {
this.shaderLanguage = 0 /* ShaderLanguage.GLSL */;
}
_addUniformToLeftOverUBO(name, uniformType, preProcessors) {
let length;
[name, uniformType, length] = this._getArraySize(name, uniformType, preProcessors);
for (let i = 0; i < this._webgpuProcessingContext.leftOverUniforms.length; i++) {
if (this._webgpuProcessingContext.leftOverUniforms[i].name === name) {
return;
}
}
this._webgpuProcessingContext.leftOverUniforms.push({
name,
type: uniformType,
length,
});
}
_buildLeftOverUBO() {
if (!this._webgpuProcessingContext.leftOverUniforms.length) {
return "";
}
const name = WebGPUShaderProcessor.LeftOvertUBOName;
let availableUBO = this._webgpuProcessingContext.availableBuffers[name];
if (!availableUBO) {
availableUBO = {
binding: this._webgpuProcessingContext.getNextFreeUBOBinding(),
};
this._webgpuProcessingContext.availableBuffers[name] = availableUBO;
this._addBufferBindingDescription(name, availableUBO, "uniform" /* WebGPUConstants.BufferBindingType.Uniform */, true);
this._addBufferBindingDescription(name, availableUBO, "uniform" /* WebGPUConstants.BufferBindingType.Uniform */, false);
}
return this._generateLeftOverUBOCode(name, availableUBO);
}
_collectBindingNames() {
// collect all the binding names for faster processing in WebGPUCacheBindGroup
for (let i = 0; i < this._webgpuProcessingContext.bindGroupLayoutEntries.length; i++) {
const setDefinition = this._webgpuProcessingContext.bindGroupLayoutEntries[i];
if (setDefinition === undefined) {
this._webgpuProcessingContext.bindGroupLayoutEntries[i] = [];
continue;
}
for (let j = 0; j < setDefinition.length; j++) {
const entry = this._webgpuProcessingContext.bindGroupLayoutEntries[i][j];
const name = this._webgpuProcessingContext.bindGroupLayoutEntryInfo[i][entry.binding].name;
const nameInArrayOfTexture = this._webgpuProcessingContext.bindGroupLayoutEntryInfo[i][entry.binding].nameInArrayOfTexture;
if (entry) {
if (entry.texture || entry.externalTexture || entry.storageTexture) {
this._webgpuProcessingContext.textureNames.push(nameInArrayOfTexture);
}
else if (entry.sampler) {
this._webgpuProcessingContext.samplerNames.push(name);
}
else if (entry.buffer) {
this._webgpuProcessingContext.bufferNames.push(name);
}
}
}
}
}
_preCreateBindGroupEntries() {
const bindGroupEntries = this._webgpuProcessingContext.bindGroupEntries;
for (let i = 0; i < this._webgpuProcessingContext.bindGroupLayoutEntries.length; i++) {
const setDefinition = this._webgpuProcessingContext.bindGroupLayoutEntries[i];
const entries = [];
for (let j = 0; j < setDefinition.length; j++) {
const entry = this._webgpuProcessingContext.bindGroupLayoutEntries[i][j];
if (entry.sampler || entry.texture || entry.storageTexture || entry.externalTexture) {
entries.push({
binding: entry.binding,
resource: undefined,
});
}
else if (entry.buffer) {
entries.push({
binding: entry.binding,
resource: {
buffer: undefined,
offset: 0,
size: 0,
},
});
}
}
bindGroupEntries[i] = entries;
}
}
_addTextureBindingDescription(name, textureInfo, textureIndex, dimension, format, isVertex, storageTextureAccess = "write-only" /* WebGPUConstants.StorageTextureAccess.WriteOnly */) {
// eslint-disable-next-line prefer-const
let { groupIndex, bindingIndex } = textureInfo.textures[textureIndex];
if (!this._webgpuProcessingContext.bindGroupLayoutEntries[groupIndex]) {
this._webgpuProcessingContext.bindGroupLayoutEntries[groupIndex] = [];
this._webgpuProcessingContext.bindGroupLayoutEntryInfo[groupIndex] = [];
}
if (!this._webgpuProcessingContext.bindGroupLayoutEntryInfo[groupIndex][bindingIndex]) {
let len;
if (dimension === null) {
len = this._webgpuProcessingContext.bindGroupLayoutEntries[groupIndex].push({
binding: bindingIndex,
visibility: 0,
externalTexture: {},
});
}
else if (format) {
len = this._webgpuProcessingContext.bindGroupLayoutEntries[groupIndex].push({
binding: bindingIndex,
visibility: 0,
storageTexture: {
access: storageTextureAccess,
format,
viewDimension: dimension,
},
});
}
else {
len = this._webgpuProcessingContext.bindGroupLayoutEntries[groupIndex].push({
binding: bindingIndex,
visibility: 0,
texture: {
sampleType: textureInfo.sampleType,
viewDimension: dimension,
multisampled: false,
},
});
}
const textureName = textureInfo.isTextureArray ? name + textureIndex : name;
this._webgpuProcessingContext.bindGroupLayoutEntryInfo[groupIndex][bindingIndex] = { name, index: len - 1, nameInArrayOfTexture: textureName };
}
bindingIndex = this._webgpuProcessingContext.bindGroupLayoutEntryInfo[groupIndex][bindingIndex].index;
if (isVertex) {
this._webgpuProcessingContext.bindGroupLayoutEntries[groupIndex][bindingIndex].visibility |= 1 /* WebGPUConstants.ShaderStage.Vertex */;
}
else {
this._webgpuProcessingContext.bindGroupLayoutEntries[groupIndex][bindingIndex].visibility |= 2 /* WebGPUConstants.ShaderStage.Fragment */;
}
}
_addSamplerBindingDescription(name, samplerInfo, isVertex) {
// eslint-disable-next-line prefer-const
let { groupIndex, bindingIndex } = samplerInfo.binding;
if (!this._webgpuProcessingContext.bindGroupLayoutEntries[groupIndex]) {
this._webgpuProcessingContext.bindGroupLayoutEntries[groupIndex] = [];
this._webgpuProcessingContext.bindGroupLayoutEntryInfo[groupIndex] = [];
}
if (!this._webgpuProcessingContext.bindGroupLayoutEntryInfo[groupIndex][bindingIndex]) {
const len = this._webgpuProcessingContext.bindGroupLayoutEntries[groupIndex].push({
binding: bindingIndex,
visibility: 0,
sampler: {
type: samplerInfo.type,
},
});
this._webgpuProcessingContext.bindGroupLayoutEntryInfo[groupIndex][bindingIndex] = { name, index: len - 1 };
}
bindingIndex = this._webgpuProcessingContext.bindGroupLayoutEntryInfo[groupIndex][bindingIndex].index;
if (isVertex) {
this._webgpuProcessingContext.bindGroupLayoutEntries[groupIndex][bindingIndex].visibility |= 1 /* WebGPUConstants.ShaderStage.Vertex */;
}
else {
this._webgpuProcessingContext.bindGroupLayoutEntries[groupIndex][bindingIndex].visibility |= 2 /* WebGPUConstants.ShaderStage.Fragment */;
}
}
_addBufferBindingDescription(name, uniformBufferInfo, bufferType, isVertex) {
// eslint-disable-next-line prefer-const
let { groupIndex, bindingIndex } = uniformBufferInfo.binding;
if (!this._webgpuProcessingContext.bindGroupLayoutEntries[groupIndex]) {
this._webgpuProcessingContext.bindGroupLayoutEntries[groupIndex] = [];
this._webgpuProcessingContext.bindGroupLayoutEntryInfo[groupIndex] = [];
}
if (!this._webgpuProcessingContext.bindGroupLayoutEntryInfo[groupIndex][bindingIndex]) {
const len = this._webgpuProcessingContext.bindGroupLayoutEntries[groupIndex].push({
binding: bindingIndex,
visibility: 0,
buffer: {
type: bufferType,
},
});
this._webgpuProcessingContext.bindGroupLayoutEntryInfo[groupIndex][bindingIndex] = { name, index: len - 1 };
}
bindingIndex = this._webgpuProcessingContext.bindGroupLayoutEntryInfo[groupIndex][bindingIndex].index;
if (isVertex) {
this._webgpuProcessingContext.bindGroupLayoutEntries[groupIndex][bindingIndex].visibility |= 1 /* WebGPUConstants.ShaderStage.Vertex */;
}
else {
this._webgpuProcessingContext.bindGroupLayoutEntries[groupIndex][bindingIndex].visibility |= 2 /* WebGPUConstants.ShaderStage.Fragment */;
}
}
}
WebGPUShaderProcessor.LeftOvertUBOName = "LeftOver";
WebGPUShaderProcessor.InternalsUBOName = "Internals";
WebGPUShaderProcessor.UniformSizes = {
// GLSL types
bool: 1,
int: 1,
float: 1,
vec2: 2,
ivec2: 2,
uvec2: 2,
vec3: 3,
ivec3: 3,
uvec3: 3,
vec4: 4,
ivec4: 4,
uvec4: 4,
mat2: 4,
mat3: 12,
mat4: 16,
// WGSL types
i32: 1,
u32: 1,
f32: 1,
mat2x2: 4,
mat3x3: 12,
mat4x4: 16,
mat2x2f: 4,
mat3x3f: 12,
mat4x4f: 16,
vec2i: 2,
vec3i: 3,
vec4i: 4,
vec2u: 2,
vec3u: 3,
vec4u: 4,
vec2f: 2,
vec3f: 3,
vec4f: 4,
vec2h: 1,
vec3h: 2,
vec4h: 2,
};
// eslint-disable-next-line @typescript-eslint/naming-convention
WebGPUShaderProcessor._SamplerFunctionByWebGLSamplerType = {
sampler2D: "sampler2D",
sampler2DArray: "sampler2DArray",
sampler2DShadow: "sampler2DShadow",
sampler2DArrayShadow: "sampler2DArrayShadow",
samplerCube: "samplerCube",
sampler3D: "sampler3D",
};
// eslint-disable-next-line @typescript-eslint/naming-convention
WebGPUShaderProcessor._TextureTypeByWebGLSamplerType = {
sampler2D: "texture2D",
sampler2DArray: "texture2DArray",
sampler2DShadow: "texture2D",
sampler2DArrayShadow: "texture2DArray",
samplerCube: "textureCube",
samplerCubeArray: "textureCubeArray",
sampler3D: "texture3D",
};
// eslint-disable-next-line @typescript-eslint/naming-convention
WebGPUShaderProcessor._GpuTextureViewDimensionByWebGPUTextureType = {
textureCube: "cube" /* WebGPUConstants.TextureViewDimension.Cube */,
textureCubeArray: "cube-array" /* WebGPUConstants.TextureViewDimension.CubeArray */,
texture2D: "2d" /* WebGPUConstants.TextureViewDimension.E2d */,
texture2DArray: "2d-array" /* WebGPUConstants.TextureViewDimension.E2dArray */,
texture3D: "3d" /* WebGPUConstants.TextureViewDimension.E3d */,
};
// if the webgl sampler type is not listed in this array, "sampler" is taken by default
// eslint-disable-next-line @typescript-eslint/naming-convention
WebGPUShaderProcessor._SamplerTypeByWebGLSamplerType = {
sampler2DShadow: "samplerShadow",
sampler2DArrayShadow: "samplerShadow",
};
// eslint-disable-next-line @typescript-eslint/naming-convention
WebGPUShaderProcessor._IsComparisonSamplerByWebGPUSamplerType = {
samplerShadow: true,
samplerArrayShadow: true,
sampler: false,
};
/** @internal */
class WebGPUPipelineContext {
// eslint-disable-next-line no-restricted-syntax
get isAsync() {
return false;
}
get isReady() {
if (this.stages) {
return true;
}
return false;
}
constructor(shaderProcessingContext, engine) {
// The field is indexed by textureState. See @WebGPUMaterialContext.textureState for more information.
this.bindGroupLayouts = {};
this._name = "unnamed";
this.shaderProcessingContext = shaderProcessingContext;
this._leftOverUniformsByName = {};
this.engine = engine;
this.vertexBufferKindToType = {};
}
_handlesSpectorRebuildCallback() {
// Nothing to do yet for spector.
}
_fillEffectInformation(effect, uniformBuffersNames, uniformsNames, uniforms, samplerList, samplers, attributesNames, attributes) {
const engine = this.engine;
if (engine._doNotHandleContextLost) {
effect._fragmentSourceCode = "";
effect._vertexSourceCode = "";
}
const foundSamplers = this.shaderProcessingContext.availableTextures;
let index;
for (index = 0; index < samplerList.length; index++) {
const name = samplerList[index];
const sampler = foundSamplers[samplerList[index]];
if (sampler == null || sampler == undefined) {
samplerList.splice(index, 1);
index--;
}
else {
samplers[name] = index;
}
}
for (const attr of engine.getAttributes(this, attributesNames)) {
attributes.push(attr);
}
// Build the uniform layout for the left over uniforms.
this.buildUniformLayout();
const attributeNamesFromEffect = [];
const attributeLocationsFromEffect = [];
for (index = 0; index < attributesNames.length; index++) {
const location = attributes[index];
if (location >= 0) {
attributeNamesFromEffect.push(attributesNames[index]);
attributeLocationsFromEffect.push(location);
}
}
this.shaderProcessingContext.attributeNamesFromEffect = attributeNamesFromEffect;
this.shaderProcessingContext.attributeLocationsFromEffect = attributeLocationsFromEffect;
}
/** @internal */
/**
* Build the uniform buffer used in the material.
*/
buildUniformLayout() {
if (!this.shaderProcessingContext.leftOverUniforms.length) {
return;
}
this.uniformBuffer?.dispose();
this.uniformBuffer = new UniformBuffer(this.engine, undefined, undefined, "leftOver-" + this._name);
for (const leftOverUniform of this.shaderProcessingContext.leftOverUniforms) {
const type = leftOverUniform.type.replace(/^(.*?)(<.*>)?$/, "$1");
const size = WebGPUShaderProcessor.UniformSizes[type];
this.uniformBuffer.addUniform(leftOverUniform.name, size, leftOverUniform.length);
this._leftOverUniformsByName[leftOverUniform.name] = leftOverUniform.type;
}
this.uniformBuffer.create();
}
setEngine(engine) {
this.engine = engine;
}
/**
* Release all associated resources.
**/
dispose() {
if (this.uniformBuffer) {
this.uniformBuffer.dispose();
}
}
/**
* Sets an integer value on a uniform variable.
* @param uniformName Name of the variable.
* @param value Value to be set.
*/
setInt(uniformName, value) {
if (!this.uniformBuffer || !this._leftOverUniformsByName[uniformName]) {
return;
}
this.uniformBuffer.updateInt(uniformName, value);
}
/**
* Sets an int2 value on a uniform variable.
* @param uniformName Name of the variable.
* @param x First int in int2.
* @param y Second int in int2.
*/
setInt2(uniformName, x, y) {
if (!this.uniformBuffer || !this._leftOverUniformsByName[uniformName]) {
return;
}
this.uniformBuffer.updateInt2(uniformName, x, y);
}
/**
* Sets an int3 value on a uniform variable.
* @param uniformName Name of the variable.
* @param x First int in int3.
* @param y Second int in int3.
* @param z Third int in int3.
*/
setInt3(uniformName, x, y, z) {
if (!this.uniformBuffer || !this._leftOverUniformsByName[uniformName]) {
return;
}
this.uniformBuffer.updateInt3(uniformName, x, y, z);
}
/**
* Sets an int4 value on a uniform variable.
* @param uniformName Name of the variable.
* @param x First int in int4.
* @param y Second int in int4.
* @param z Third int in int4.
* @param w Fourth int in int4.
*/
setInt4(uniformName, x, y, z, w) {
if (!this.uniformBuffer || !this._leftOverUniformsByName[uniformName]) {
return;
}
this.uniformBuffer.updateInt4(uniformName, x, y, z, w);
}
/**
* Sets an int array on a uniform variable.
* @param uniformName Name of the variable.
* @param array array to be set.
*/
setIntArray(uniformName, array) {
if (!this.uniformBuffer || !this._leftOverUniformsByName[uniformName]) {
return;
}
this.uniformBuffer.updateIntArray(uniformName, array);
}
/**
* Sets an int array 2 on a uniform variable. (Array is specified as single array eg. [1,2,3,4] will result in [[1,2],[3,4]] in the shader)
* @param uniformName Name of the variable.
* @param array array to be set.
*/
setIntArray2(uniformName, array) {
this.setIntArray(uniformName, array);
}
/**
* Sets an int array 3 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6] will result in [[1,2,3],[4,5,6]] in the shader)
* @param uniformName Name of the variable.
* @param array array to be set.
*/
setIntArray3(uniformName, array) {
this.setIntArray(uniformName, array);
}
/**
* Sets an int array 4 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6,7,8] will result in [[1,2,3,4],[5,6,7,8]] in the shader)
* @param uniformName Name of the variable.
* @param array array to be set.
*/
setIntArray4(uniformName, array) {
this.setIntArray(uniformName, array);
}
/**
* Sets an unsigned integer value on a uniform variable.
* @param uniformName Name of the variable.
* @param value Value to be set.
*/
setUInt(uniformName, value) {
if (!this.uniformBuffer || !this._leftOverUniformsByName[uniformName]) {
return;
}
this.uniformBuffer.updateUInt(uniformName, value);
}
/**
* Sets an unsigned int2 value on a uniform variable.
* @param uniformName Name of the variable.
* @param x First unsigned int in uint2.
* @param y Second unsigned int in uint2.
*/
setUInt2(uniformName, x, y) {
if (!this.uniformBuffer || !this._leftOverUniformsByName[uniformName]) {
return;
}
this.uniformBuffer.updateUInt2(uniformName, x, y);
}
/**
* Sets an unsigned int3 value on a uniform variable.
* @param uniformName Name of the variable.
* @param x First unsigned int in uint3.
* @param y Second unsigned int in uint3.
* @param z Third unsigned int in uint3.
*/
setUInt3(uniformName, x, y, z) {
if (!this.uniformBuffer || !this._leftOverUniformsByName[uniformName]) {
return;
}
this.uniformBuffer.updateUInt3(uniformName, x, y, z);
}
/**
* Sets an unsigned int4 value on a uniform variable.
* @param uniformName Name of the variable.
* @param x First unsigned int in uint4.
* @param y Second unsigned int in uint4.
* @param z Third unsigned int in uint4.
* @param w Fourth unsigned int in uint4.
*/
setUInt4(uniformName, x, y, z, w) {
if (!this.uniformBuffer || !this._leftOverUniformsByName[uniformName]) {
return;
}
this.uniformBuffer.updateUInt4(uniformName, x, y, z, w);
}
/**
* Sets an unsigned int array on a uniform variable.
* @param uniformName Name of the variable.
* @param array array to be set.
*/
setUIntArray(uniformName, array) {
if (!this.uniformBuffer || !this._leftOverUniformsByName[uniformName]) {
return;
}
this.uniformBuffer.updateUIntArray(uniformName, array);
}
/**
* Sets an unsigned int array 2 on a uniform variable. (Array is specified as single array eg. [1,2,3,4] will result in [[1,2],[3,4]] in the shader)
* @param uniformName Name of the variable.
* @param array array to be set.
*/
setUIntArray2(uniformName, array) {
this.setUIntArray(uniformName, array);
}
/**
* Sets an unsigned int array 3 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6] will result in [[1,2,3],[4,5,6]] in the shader)
* @param uniformName Name of the variable.
* @param array array to be set.
*/
setUIntArray3(uniformName, array) {
this.setUIntArray(uniformName, array);
}
/**
* Sets an unsigned int array 4 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6,7,8] will result in [[1,2,3,4],[5,6,7,8]] in the shader)
* @param uniformName Name of the variable.
* @param array array to be set.
*/
setUIntArray4(uniformName, array) {
this.setUIntArray(uniformName, array);
}
/**
* Sets an array on a uniform variable.
* @param uniformName Name of the variable.
* @param array array to be set.
*/
setArray(uniformName, array) {
if (!this.uniformBuffer || !this._leftOverUniformsByName[uniformName]) {
return;
}
this.uniformBuffer.updateArray(uniformName, array);
}
/**
* Sets an array 2 on a uniform variable. (Array is specified as single array eg. [1,2,3,4] will result in [[1,2],[3,4]] in the shader)
* @param uniformName Name of the variable.
* @param array array to be set.
*/
setArray2(uniformName, array) {
this.setArray(uniformName, array);
}
/**
* Sets an array 3 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6] will result in [[1,2,3],[4,5,6]] in the shader)
* @param uniformName Name of the variable.
* @param array array to be set.
*/
setArray3(uniformName, array) {
this.setArray(uniformName, array);
}
/**
* Sets an array 4 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6,7,8] will result in [[1,2,3,4],[5,6,7,8]] in the shader)
* @param uniformName Name of the variable.
* @param array array to be set.
*/
setArray4(uniformName, array) {
this.setArray(uniformName, array);
}
/**
* Sets matrices on a uniform variable.
* @param uniformName Name of the variable.
* @param matrices matrices to be set.
*/
setMatrices(uniformName, matrices) {
if (!this.uniformBuffer || !this._leftOverUniformsByName[uniformName]) {
return;
}
this.uniformBuffer.updateMatrices(uniformName, matrices);
}
/**
* Sets matrix on a uniform variable.
* @param uniformName Name of the variable.
* @param matrix matrix to be set.
*/
setMatrix(uniformName, matrix) {
if (!this.uniformBuffer || !this._leftOverUniformsByName[uniformName]) {
return;
}
this.uniformBuffer.updateMatrix(uniformName, matrix);
}
/**
* Sets a 3x3 matrix on a uniform variable. (Specified as [1,2,3,4,5,6,7,8,9] will result in [1,2,3][4,5,6][7,8,9] matrix)
* @param uniformName Name of the variable.
* @param matrix matrix to be set.
*/
setMatrix3x3(uniformName, matrix) {
if (!this.uniformBuffer || !this._leftOverUniformsByName[uniformName]) {
return;
}
this.uniformBuffer.updateMatrix3x3(uniformName, matrix);
}
/**
* Sets a 2x2 matrix on a uniform variable. (Specified as [1,2,3,4] will result in [1,2][3,4] matrix)
* @param uniformName Name of the variable.
* @param matrix matrix to be set.
*/
setMatrix2x2(uniformName, matrix) {
if (!this.uniformBuffer || !this._leftOverUniformsByName[uniformName]) {
return;
}
this.uniformBuffer.updateMatrix2x2(uniformName, matrix);
}
/**
* Sets a float on a uniform variable.
* @param uniformName Name of the variable.
* @param value value to be set.
*/
setFloat(uniformName, value) {
if (!this.uniformBuffer || !this._leftOverUniformsByName[uniformName]) {
return;
}
this.uniformBuffer.updateFloat(uniformName, value);
}
/**
* Sets a Vector2 on a uniform variable.
* @param uniformName Name of the variable.
* @param vector2 vector2 to be set.
*/
setVector2(uniformName, vector2) {
this.setFloat2(uniformName, vector2.x, vector2.y);
}
/**
* Sets a float2 on a uniform variable.
* @param uniformName Name of the variable.
* @param x First float in float2.
* @param y Second float in float2.
*/
setFloat2(uniformName, x, y) {
if (!this.uniformBuffer || !this._leftOverUniformsByName[uniformName]) {
return;
}
this.uniformBuffer.updateFloat2(uniformName, x, y);
}
/**
* Sets a Vector3 on a uniform variable.
* @param uniformName Name of the variable.
* @param vector3 Value to be set.
*/
setVector3(uniformName, vector3) {
this.setFloat3(uniformName, vector3.x, vector3.y, vector3.z);
}
/**
* Sets a float3 on a uniform variable.
* @param uniformName Name of the variable.
* @param x First float in float3.
* @param y Second float in float3.
* @param z Third float in float3.
*/
setFloat3(uniformName, x, y, z) {
if (!this.uniformBuffer || !this._leftOverUniformsByName[uniformName]) {
return;
}
this.uniformBuffer.updateFloat3(uniformName, x, y, z);
}
/**
* Sets a Vector4 on a uniform variable.
* @param uniformName Name of the variable.
* @param vector4 Value to be set.
*/
setVector4(uniformName, vector4) {
this.setFloat4(uniformName, vector4.x, vector4.y, vector4.z, vector4.w);
}
/**
* Sets a Quaternion on a uniform variable.
* @param uniformName Name of the variable.
* @param quaternion Value to be set.
*/
setQuaternion(uniformName, quaternion) {
this.setFloat4(uniformName, quaternion.x, quaternion.y, quaternion.z, quaternion.w);
}
/**
* Sets a float4 on a uniform variable.
* @param uniformName Name of the variable.
* @param x First float in float4.
* @param y Second float in float4.
* @param z Third float in float4.
* @param w Fourth float in float4.
*/
setFloat4(uniformName, x, y, z, w) {
if (!this.uniformBuffer || !this._leftOverUniformsByName[uniformName]) {
return;
}
this.uniformBuffer.updateFloat4(uniformName, x, y, z, w);
}
/**
* Sets a Color3 on a uniform variable.
* @param uniformName Name of the variable.
* @param color3 Value to be set.
*/
setColor3(uniformName, color3) {
this.setFloat3(uniformName, color3.r, color3.g, color3.b);
}
/**
* Sets a Color4 on a uniform variable.
* @param uniformName Name of the variable.
* @param color3 Value to be set.
* @param alpha Alpha value to be set.
*/
setColor4(uniformName, color3, alpha) {
this.setFloat4(uniformName, color3.r, color3.g, color3.b, alpha);
}
/**
* Sets a Color4 on a uniform variable
* @param uniformName defines the name of the variable
* @param color4 defines the value to be set
*/
setDirectColor4(uniformName, color4) {
this.setFloat4(uniformName, color4.r, color4.g, color4.b, color4.a);
}
_getVertexShaderCode() {
return this.sources?.vertex;
}
_getFragmentShaderCode() {
return this.sources?.fragment;
}
}
const MaxGroups = 4;
const MaxBindingsPerGroup = 1 << 16;
// all types not listed are assumed to consume 1 location
const TypeToLocationSize = {
// GLSL types
mat2: 2,
mat3: 3,
mat4: 4,
// WGSL types
mat2x2: 2,
mat3x3: 3,
mat4x4: 4,
};
/**
* @internal
*/
class WebGPUShaderProcessingContext {
static get KnownUBOs() {
return WebGPUShaderProcessingContext._SimplifiedKnownBindings ? WebGPUShaderProcessingContext._SimplifiedKnownUBOs : WebGPUShaderProcessingContext._KnownUBOs;
}
constructor(shaderLanguage, pureMode = false) {
this.vertexBufferKindToNumberOfComponents = {};
this.shaderLanguage = shaderLanguage;
this._attributeNextLocation = 0;
this._varyingNextLocation = 0;
this.freeGroupIndex = 0;
this.freeBindingIndex = 0;
this.availableVaryings = {};
this.availableAttributes = {};
this.availableBuffers = {};
this.availableTextures = {};
this.availableSamplers = {};
this.orderedAttributes = [];
this.bindGroupLayoutEntries = [];
this.bindGroupLayoutEntryInfo = [];
this.bindGroupEntries = [];
this.bufferNames = [];
this.textureNames = [];
this.samplerNames = [];
this.leftOverUniforms = [];
if (!pureMode) {
this._findStartingGroupBinding();
}
}
_findStartingGroupBinding() {
const knownUBOs = WebGPUShaderProcessingContext.KnownUBOs;
const groups = [];
for (const name in knownUBOs) {
const binding = knownUBOs[name].binding;
if (binding.groupIndex === -1) {
continue;
}
if (groups[binding.groupIndex] === undefined) {
groups[binding.groupIndex] = binding.bindingIndex;
}
else {
groups[binding.groupIndex] = Math.max(groups[binding.groupIndex], binding.bindingIndex);
}
}
this.freeGroupIndex = groups.length - 1;
if (this.freeGroupIndex === 0) {
this.freeGroupIndex++;
this.freeBindingIndex = 0;
}
else {
this.freeBindingIndex = groups[groups.length - 1] + 1;
}
}
getAttributeNextLocation(dataType, arrayLength = 0) {
const index = this._attributeNextLocation;
this._attributeNextLocation += (TypeToLocationSize[dataType] ?? 1) * (arrayLength || 1);
return index;
}
getVaryingNextLocation(dataType, arrayLength = 0) {
const index = this._varyingNextLocation;
this._varyingNextLocation += (TypeToLocationSize[dataType] ?? 1) * (arrayLength || 1);
return index;
}
getNextFreeUBOBinding() {
return this._getNextFreeBinding(1);
}
_getNextFreeBinding(bindingCount) {
if (this.freeBindingIndex > MaxBindingsPerGroup - bindingCount) {
this.freeGroupIndex++;
this.freeBindingIndex = 0;
}
if (this.freeGroupIndex === MaxGroups) {
// eslint-disable-next-line no-throw-literal
throw "Too many textures or UBOs have been declared and it is not supported in WebGPU.";
}
const returnValue = {
groupIndex: this.freeGroupIndex,
bindingIndex: this.freeBindingIndex,
};
this.freeBindingIndex += bindingCount;
return returnValue;
}
}
/** @internal */
WebGPUShaderProcessingContext._SimplifiedKnownBindings = true; // if true, use only group=0,binding=0 as a known group/binding for the Scene ubo and use group=1,binding=X for all other bindings
// if false, see _KnownUBOs for the known groups/bindings used
WebGPUShaderProcessingContext._SimplifiedKnownUBOs = {
Scene: { binding: { groupIndex: 0, bindingIndex: 0 } },
Light0: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light1: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light2: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light3: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light4: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light5: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light6: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light7: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light8: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light9: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light10: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light11: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light12: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light13: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light14: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light15: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light16: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light17: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light18: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light19: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light20: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light21: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light22: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light23: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light24: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light25: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light26: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light27: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light28: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light29: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light30: { binding: { groupIndex: -1, bindingIndex: -1 } },
Light31: { binding: { groupIndex: -1, bindingIndex: -1 } },
Material: { binding: { groupIndex: -1, bindingIndex: -1 } },
Mesh: { binding: { groupIndex: -1, bindingIndex: -1 } },
Internals: { binding: { groupIndex: -1, bindingIndex: -1 } },
};
WebGPUShaderProcessingContext._KnownUBOs = {
Scene: { binding: { groupIndex: 0, bindingIndex: 0 } },
Light0: { binding: { groupIndex: 1, bindingIndex: 0 } },
Light1: { binding: { groupIndex: 1, bindingIndex: 1 } },
Light2: { binding: { groupIndex: 1, bindingIndex: 2 } },
Light3: { binding: { groupIndex: 1, bindingIndex: 3 } },
Light4: { binding: { groupIndex: 1, bindingIndex: 4 } },
Light5: { binding: { groupIndex: 1, bindingIndex: 5 } },
Light6: { binding: { groupIndex: 1, bindingIndex: 6 } },
Light7: { binding: { groupIndex: 1, bindingIndex: 7 } },
Light8: { binding: { groupIndex: 1, bindingIndex: 8 } },
Light9: { binding: { groupIndex: 1, bindingIndex: 9 } },
Light10: { binding: { groupIndex: 1, bindingIndex: 10 } },
Light11: { binding: { groupIndex: 1, bindingIndex: 11 } },
Light12: { binding: { groupIndex: 1, bindingIndex: 12 } },
Light13: { binding: { groupIndex: 1, bindingIndex: 13 } },
Light14: { binding: { groupIndex: 1, bindingIndex: 14 } },
Light15: { binding: { groupIndex: 1, bindingIndex: 15 } },
Light16: { binding: { groupIndex: 1, bindingIndex: 16 } },
Light17: { binding: { groupIndex: 1, bindingIndex: 17 } },
Light18: { binding: { groupIndex: 1, bindingIndex: 18 } },
Light19: { binding: { groupIndex: 1, bindingIndex: 19 } },
Light20: { binding: { groupIndex: 1, bindingIndex: 20 } },
Light21: { binding: { groupIndex: 1, bindingIndex: 21 } },
Light22: { binding: { groupIndex: 1, bindingIndex: 22 } },
Light23: { binding: { groupIndex: 1, bindingIndex: 23 } },
Light24: { binding: { groupIndex: 1, bindingIndex: 24 } },
Light25: { binding: { groupIndex: 1, bindingIndex: 25 } },
Light26: { binding: { groupIndex: 1, bindingIndex: 26 } },
Light27: { binding: { groupIndex: 1, bindingIndex: 27 } },
Light28: { binding: { groupIndex: 1, bindingIndex: 28 } },
Light29: { binding: { groupIndex: 1, bindingIndex: 29 } },
Light30: { binding: { groupIndex: 1, bindingIndex: 30 } },
Light31: { binding: { groupIndex: 1, bindingIndex: 31 } },
Material: { binding: { groupIndex: 2, bindingIndex: 0 } },
Mesh: { binding: { groupIndex: 2, bindingIndex: 1 } },
Internals: { binding: { groupIndex: 2, bindingIndex: 2 } },
};
/**
* Extracts the characters between two markers (for eg, between "(" and ")"). The function handles nested markers as well as markers inside strings (delimited by ", ' or `) and comments
* @param markerOpen opening marker
* @param markerClose closing marker
* @param block code block to parse
* @param startIndex starting index in block where the extraction must start. The character at block[startIndex] should be the markerOpen character!
* @returns index of the last character for the extraction (or -1 if the string is invalid - no matching closing marker found). The string to extract (without the markers) is the string between startIndex + 1 and the returned value (exclusive)
*/
function ExtractBetweenMarkers(markerOpen, markerClose, block, startIndex) {
let currPos = startIndex, openMarkers = 0, waitForChar = "";
while (currPos < block.length) {
const currChar = block.charAt(currPos);
if (!waitForChar) {
switch (currChar) {
case markerOpen:
openMarkers++;
break;
case markerClose:
openMarkers--;
break;
case '"':
case "'":
case "`":
waitForChar = currChar;
break;
case "/":
if (currPos + 1 < block.length) {
const nextChar = block.charAt(currPos + 1);
if (nextChar === "/") {
waitForChar = "\n";
}
else if (nextChar === "*") {
waitForChar = "*/";
}
}
break;
}
}
else {
if (currChar === waitForChar) {
if (waitForChar === '"' || waitForChar === "'") {
block.charAt(currPos - 1) !== "\\" && (waitForChar = "");
}
else {
waitForChar = "";
}
}
else if (waitForChar === "*/" && currChar === "*" && currPos + 1 < block.length) {
block.charAt(currPos + 1) === "/" && (waitForChar = "");
if (waitForChar === "") {
currPos++;
}
}
}
currPos++;
if (openMarkers === 0) {
break;
}
}
return openMarkers === 0 ? currPos - 1 : -1;
}
/**
* Parses a string and skip whitespaces
* @param s string to parse
* @param index index where to start parsing
* @returns the index after all whitespaces have been skipped
*/
function SkipWhitespaces(s, index) {
while (index < s.length) {
const c = s[index];
if (c !== " " && c !== "\n" && c !== "\r" && c !== "\t" && c !== "\u000a" && c !== "\u00a0") {
break;
}
index++;
}
return index;
}
/**
* Checks if a character is an identifier character (meaning, if it is 0-9, A-Z, a-z or _)
* @param c character to check
* @returns true if the character is an identifier character
*/
function IsIdentifierChar(c) {
const v = c.charCodeAt(0);
return ((v >= 48 && v <= 57) || // 0-9
(v >= 65 && v <= 90) || // A-Z
(v >= 97 && v <= 122) || // a-z
v == 95); // _
}
/**
* Removes the comments of a code block
* @param block code block to parse
* @returns block with the comments removed
*/
function RemoveComments(block) {
let currPos = 0, waitForChar = "", inComments = false;
const s = [];
while (currPos < block.length) {
const currChar = block.charAt(currPos);
if (!waitForChar) {
switch (currChar) {
case '"':
case "'":
case "`":
waitForChar = currChar;
break;
case "/":
if (currPos + 1 < block.length) {
const nextChar = block.charAt(currPos + 1);
if (nextChar === "/") {
waitForChar = "\n";
inComments = true;
}
else if (nextChar === "*") {
waitForChar = "*/";
inComments = true;
}
}
break;
}
if (!inComments) {
s.push(currChar);
}
}
else {
if (currChar === waitForChar) {
if (waitForChar === '"' || waitForChar === "'") {
block.charAt(currPos - 1) !== "\\" && (waitForChar = "");
s.push(currChar);
}
else {
waitForChar = "";
inComments = false;
}
}
else if (waitForChar === "*/" && currChar === "*" && currPos + 1 < block.length) {
block.charAt(currPos + 1) === "/" && (waitForChar = "");
if (waitForChar === "") {
inComments = false;
currPos++;
}
}
else {
if (!inComments) {
s.push(currChar);
}
}
}
currPos++;
}
return s.join("");
}
/**
* Finds the first occurrence of a character in a string going backward
* @param s the string to parse
* @param index starting index in the string
* @param c the character to find
* @param c2 an optional second character to find
* @returns the index of the character if found, else -1
*/
function FindBackward(s, index, c, c2) {
while (index >= 0 && s.charAt(index) !== c && (s.charAt(index) !== c2)) {
index--;
}
return index;
}
/**
* Escapes a string so that it is usable as a regular expression
* @param s string to escape
* @returns escaped string
*/
function EscapeRegExp(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
/**
* Injects code at the beginning and/or end of a function.
* The function is identified by "mainFuncDecl". The starting code is injected just after the first "\{" found after the mainFuncDecl.
* The ending code is injected just before the last "\}" of the whole block of code (so, it is assumed that the function is the last of the block of code).
* @param code code to inject into
* @param mainFuncDecl Function declaration to find in the code (for eg: "void main")
* @param startingCode The code to inject at the beginning of the function
* @param endingCode The code to inject at the end of the function
* @returns The code with the injected code
*/
function InjectStartingAndEndingCode(code, mainFuncDecl, startingCode, endingCode) {
let idx = code.indexOf(mainFuncDecl);
if (idx < 0) {
return code;
}
if (startingCode) {
// eslint-disable-next-line no-empty
while (idx++ < code.length && code.charAt(idx) != "{") { }
if (idx < code.length) {
const part1 = code.substring(0, idx + 1);
const part2 = code.substring(idx + 1);
code = part1 + startingCode + part2;
}
}
if (endingCode) {
const lastClosingCurly = code.lastIndexOf("}");
code = code.substring(0, lastClosingCurly);
code += endingCode + "\n}";
}
return code;
}
/** @internal */
class WebGPUShaderProcessorGLSL extends WebGPUShaderProcessor {
constructor() {
super(...arguments);
this._missingVaryings = [];
this._textureArrayProcessing = [];
this._vertexIsGLES3 = false;
this._fragmentIsGLES3 = false;
this.shaderLanguage = 0 /* ShaderLanguage.GLSL */;
this.parseGLES3 = true;
}
_getArraySize(name, type, preProcessors) {
let length = 0;
const startArray = name.indexOf("[");
const endArray = name.indexOf("]");
if (startArray > 0 && endArray > 0) {
const lengthInString = name.substring(startArray + 1, endArray);
length = +lengthInString;
if (isNaN(length)) {
length = +preProcessors[lengthInString.trim()];
}
name = name.substring(0, startArray);
}
return [name, type, length];
}
initializeShaders(processingContext) {
this._webgpuProcessingContext = processingContext;
this._missingVaryings.length = 0;
this._textureArrayProcessing.length = 0;
this.attributeKeywordName = undefined;
this.varyingVertexKeywordName = undefined;
this.varyingFragmentKeywordName = undefined;
}
preProcessShaderCode(code, isFragment) {
const ubDeclaration = `// Internals UBO\nuniform ${WebGPUShaderProcessor.InternalsUBOName} {\nfloat yFactor_;\nfloat textureOutputHeight_;\n};\n`;
const alreadyInjected = code.indexOf("// Internals UBO") !== -1;
if (isFragment) {
this._fragmentIsGLES3 = code.indexOf("#version 3") !== -1;
if (this._fragmentIsGLES3) {
this.varyingFragmentKeywordName = "in";
}
return alreadyInjected ? code : ubDeclaration + "##INJECTCODE##\n" + code;
}
this._vertexIsGLES3 = code.indexOf("#version 3") !== -1;
if (this._vertexIsGLES3) {
this.attributeKeywordName = "in";
this.varyingVertexKeywordName = "out";
}
return alreadyInjected ? code : ubDeclaration + code;
}
varyingCheck(varying, isFragment) {
const outRegex = /(flat\s)?\s*\bout\b/;
const inRegex = /(flat\s)?\s*\bin\b/;
const varyingRegex = /(flat\s)?\s*\bvarying\b/;
const regex = isFragment && this._fragmentIsGLES3 ? inRegex : !isFragment && this._vertexIsGLES3 ? outRegex : varyingRegex;
return regex.test(varying);
}
varyingProcessor(varying, isFragment, preProcessors) {
this._preProcessors = preProcessors;
const outRegex = /\s*(flat)?\s*out\s+(?:(?:highp)?|(?:lowp)?)\s*(\S+)\s+(\S+)\s*;/gm;
const inRegex = /\s*(flat)?\s*in\s+(?:(?:highp)?|(?:lowp)?)\s*(\S+)\s+(\S+)\s*;/gm;
const varyingRegex = /\s*(flat)?\s*varying\s+(?:(?:highp)?|(?:lowp)?)\s*(\S+)\s+(\S+)\s*;/gm;
const regex = isFragment && this._fragmentIsGLES3 ? inRegex : !isFragment && this._vertexIsGLES3 ? outRegex : varyingRegex;
const match = regex.exec(varying);
if (match !== null) {
const interpolationQualifier = match[1] ?? "";
const varyingType = match[2];
const name = match[3];
let location;
if (isFragment) {
location = this._webgpuProcessingContext.availableVaryings[name];
this._missingVaryings[location] = "";
if (location === undefined) {
Logger.Warn(`Invalid fragment shader: The varying named "${name}" is not declared in the vertex shader! This declaration will be ignored.`);
}
}
else {
location = this._webgpuProcessingContext.getVaryingNextLocation(varyingType, this._getArraySize(name, varyingType, preProcessors)[2]);
this._webgpuProcessingContext.availableVaryings[name] = location;
this._missingVaryings[location] = `layout(location = ${location}) ${interpolationQualifier} in ${varyingType} ${name};`;
}
varying = varying.replace(match[0], location === undefined ? "" : `layout(location = ${location}) ${interpolationQualifier} ${isFragment ? "in" : "out"} ${varyingType} ${name};`);
}
return varying;
}
attributeProcessor(attribute, preProcessors) {
this._preProcessors = preProcessors;
const inRegex = /\s*in\s+(\S+)\s+(\S+)\s*;/gm;
const attribRegex = /\s*attribute\s+(\S+)\s+(\S+)\s*;/gm;
const regex = this._vertexIsGLES3 ? inRegex : attribRegex;
const match = regex.exec(attribute);
if (match !== null) {
const attributeType = match[1];
const name = match[2];
const location = this._webgpuProcessingContext.getAttributeNextLocation(attributeType, this._getArraySize(name, attributeType, preProcessors)[2]);
this._webgpuProcessingContext.availableAttributes[name] = location;
this._webgpuProcessingContext.orderedAttributes[location] = name;
const numComponents = this._webgpuProcessingContext.vertexBufferKindToNumberOfComponents[name];
if (numComponents !== undefined) {
// Special case for an int/ivecX vertex buffer that is used as a float/vecX attribute in the shader.
const newType = numComponents < 0 ? (numComponents === -1 ? "int" : "ivec" + -numComponents) : numComponents === 1 ? "uint" : "uvec" + numComponents;
const newName = `_int_${name}_`;
attribute = attribute.replace(match[0], `layout(location = ${location}) in ${newType} ${newName}; ${attributeType} ${name} = ${attributeType}(${newName});`);
}
else {
attribute = attribute.replace(match[0], `layout(location = ${location}) in ${attributeType} ${name};`);
}
}
return attribute;
}
uniformProcessor(uniform, isFragment, preProcessors) {
this._preProcessors = preProcessors;
const uniformRegex = /\s*uniform\s+(?:(?:highp)?|(?:lowp)?)\s*(\S+)\s+(\S+)\s*;/gm;
const match = uniformRegex.exec(uniform);
if (match !== null) {
let uniformType = match[1];
let name = match[2];
if (uniformType.indexOf("sampler") === 0 || uniformType.indexOf("sampler") === 1) {
let arraySize; // 0 means the texture is not declared as an array
[name, uniformType, arraySize] = this._getArraySize(name, uniformType, preProcessors);
let textureInfo = this._webgpuProcessingContext.availableTextures[name];
if (!textureInfo) {
textureInfo = {
autoBindSampler: true,
isTextureArray: arraySize > 0,
isStorageTexture: false,
textures: [],
sampleType: "float" /* WebGPUConstants.TextureSampleType.Float */,
};
for (let i = 0; i < (arraySize || 1); ++i) {
textureInfo.textures.push(this._webgpuProcessingContext.getNextFreeUBOBinding());
}
}
const samplerType = WebGPUShaderProcessor._SamplerTypeByWebGLSamplerType[uniformType] ?? "sampler";
const isComparisonSampler = !!WebGPUShaderProcessor._IsComparisonSamplerByWebGPUSamplerType[samplerType];
const samplerBindingType = isComparisonSampler ? "comparison" /* WebGPUConstants.SamplerBindingType.Comparison */ : "filtering" /* WebGPUConstants.SamplerBindingType.Filtering */;
const samplerName = name + Constants.AUTOSAMPLERSUFFIX;
let samplerInfo = this._webgpuProcessingContext.availableSamplers[samplerName];
if (!samplerInfo) {
samplerInfo = {
binding: this._webgpuProcessingContext.getNextFreeUBOBinding(),
type: samplerBindingType,
};
}
const componentType = uniformType.charAt(0) === "u" ? "u" : uniformType.charAt(0) === "i" ? "i" : "";
if (componentType) {
uniformType = uniformType.substring(1);
}
const sampleType = isComparisonSampler
? "depth" /* WebGPUConstants.TextureSampleType.Depth */
: componentType === "u"
? "uint" /* WebGPUConstants.TextureSampleType.Uint */
: componentType === "i"
? "sint" /* WebGPUConstants.TextureSampleType.Sint */
: "float" /* WebGPUConstants.TextureSampleType.Float */;
textureInfo.sampleType = sampleType;
const isTextureArray = arraySize > 0;
const samplerGroupIndex = samplerInfo.binding.groupIndex;
const samplerBindingIndex = samplerInfo.binding.bindingIndex;
const samplerFunction = WebGPUShaderProcessor._SamplerFunctionByWebGLSamplerType[uniformType];
const textureType = WebGPUShaderProcessor._TextureTypeByWebGLSamplerType[uniformType];
const textureDimension = WebGPUShaderProcessor._GpuTextureViewDimensionByWebGPUTextureType[textureType];
// Manage textures and samplers.
if (!isTextureArray) {
arraySize = 1;
uniform = `layout(set = ${samplerGroupIndex}, binding = ${samplerBindingIndex}) uniform ${samplerType} ${samplerName};
layout(set = ${textureInfo.textures[0].groupIndex}, binding = ${textureInfo.textures[0].bindingIndex}) uniform ${componentType}${textureType} ${name}Texture;
#define ${name} ${componentType}${samplerFunction}(${name}Texture, ${samplerName})`;
}
else {
const layouts = [];
layouts.push(`layout(set = ${samplerGroupIndex}, binding = ${samplerBindingIndex}) uniform ${componentType}${samplerType} ${samplerName};`);
uniform = `\n`;
for (let i = 0; i < arraySize; ++i) {
const textureSetIndex = textureInfo.textures[i].groupIndex;
const textureBindingIndex = textureInfo.textures[i].bindingIndex;
layouts.push(`layout(set = ${textureSetIndex}, binding = ${textureBindingIndex}) uniform ${textureType} ${name}Texture${i};`);
uniform += `${i > 0 ? "\n" : ""}#define ${name}${i} ${componentType}${samplerFunction}(${name}Texture${i}, ${samplerName})`;
}
uniform = layouts.join("\n") + uniform;
this._textureArrayProcessing.push(name);
}
this._webgpuProcessingContext.availableTextures[name] = textureInfo;
this._webgpuProcessingContext.availableSamplers[samplerName] = samplerInfo;
this._addSamplerBindingDescription(samplerName, samplerInfo, !isFragment);
for (let i = 0; i < arraySize; ++i) {
this._addTextureBindingDescription(name, textureInfo, i, textureDimension, null, !isFragment);
}
}
else {
this._addUniformToLeftOverUBO(name, uniformType, preProcessors);
uniform = "";
}
}
return uniform;
}
uniformBufferProcessor(uniformBuffer, isFragment) {
const uboRegex = /uniform\s+(\w+)/gm;
const match = uboRegex.exec(uniformBuffer);
if (match !== null) {
const name = match[1];
let uniformBufferInfo = this._webgpuProcessingContext.availableBuffers[name];
if (!uniformBufferInfo) {
const knownUBO = WebGPUShaderProcessingContext.KnownUBOs[name];
let binding;
if (knownUBO && knownUBO.binding.groupIndex !== -1) {
binding = knownUBO.binding;
}
else {
binding = this._webgpuProcessingContext.getNextFreeUBOBinding();
}
uniformBufferInfo = { binding };
this._webgpuProcessingContext.availableBuffers[name] = uniformBufferInfo;
}
this._addBufferBindingDescription(name, uniformBufferInfo, "uniform" /* WebGPUConstants.BufferBindingType.Uniform */, !isFragment);
uniformBuffer = uniformBuffer.replace("uniform", `layout(set = ${uniformBufferInfo.binding.groupIndex}, binding = ${uniformBufferInfo.binding.bindingIndex}) uniform`);
}
return uniformBuffer;
}
postProcessor(code, defines, isFragment, _processingContext, _parameters, preProcessors) {
const hasDrawBuffersExtension = code.search(/#extension.+GL_EXT_draw_buffers.+require/) !== -1;
// Remove extensions
const regex = /#extension.+(GL_OVR_multiview2|GL_OES_standard_derivatives|GL_EXT_shader_texture_lod|GL_EXT_frag_depth|GL_EXT_draw_buffers).+(enable|require)/g;
code = code.replace(regex, "");
// Replace instructions
code = code.replace(/texture2D\s*\(/g, "texture(");
if (isFragment) {
const hasFragCoord = code.indexOf("gl_FragCoord") >= 0;
const fragCoordCode = `
glFragCoord_ = gl_FragCoord;
if (yFactor_ == 1.) {
glFragCoord_.y = textureOutputHeight_ - glFragCoord_.y;
}
`;
const injectCode = hasFragCoord ? "vec4 glFragCoord_;\n" : "";
const hasOutput = code.search(/layout *\(location *= *0\) *out/g) !== -1;
code = code.replace(/texture2DLodEXT\s*\(/g, "textureLod(");
code = code.replace(/textureCubeLodEXT\s*\(/g, "textureLod(");
code = code.replace(/textureCube\s*\(/g, "texture(");
code = code.replace(/gl_FragDepthEXT/g, "gl_FragDepth");
code = code.replace(/gl_FragColor/g, "glFragColor");
code = code.replace(/gl_FragData/g, "glFragData");
code = code.replace(/gl_FragCoord/g, "glFragCoord_");
if (!this._fragmentIsGLES3) {
code = code.replace(/void\s+?main\s*\(/g, (hasDrawBuffersExtension || hasOutput ? "" : "layout(location = 0) out vec4 glFragColor;\n") + "void main(");
}
else {
const match = /^\s*out\s+\S+\s+\S+\s*;/gm.exec(code);
if (match !== null) {
code = code.substring(0, match.index) + "layout(location = 0) " + code.substring(match.index);
}
}
code = code.replace(/dFdy/g, "(-yFactor_)*dFdy"); // will also handle dFdyCoarse and dFdyFine
code = code.replace("##INJECTCODE##", injectCode);
if (hasFragCoord) {
code = InjectStartingAndEndingCode(code, "void main", fragCoordCode);
}
}
else {
if ("VERTEXOUTPUT_INVARIANT" in preProcessors) {
code = "invariant gl_Position;\n" + code;
}
code = code.replace(/gl_InstanceID/g, "gl_InstanceIndex");
code = code.replace(/gl_VertexID/g, "gl_VertexIndex");
const hasMultiviewExtension = defines.indexOf("#define MULTIVIEW") !== -1;
if (hasMultiviewExtension) {
return "#extension GL_OVR_multiview2 : require\nlayout (num_views = 2) in;\n" + code;
}
}
// Flip Y + convert z range from [-1,1] to [0,1]
if (!isFragment) {
const lastClosingCurly = code.lastIndexOf("}");
code = code.substring(0, lastClosingCurly);
code += "gl_Position.y *= yFactor_;\n";
// isNDCHalfZRange is always true in WebGPU
code += "}";
}
return code;
}
_applyTextureArrayProcessing(code, name) {
// Replaces the occurrences of name[XX] by nameXX
const regex = new RegExp(name + "\\s*\\[(.+)?\\]", "gm");
let match = regex.exec(code);
while (match !== null) {
const index = match[1];
let iindex = +index;
if (this._preProcessors && isNaN(iindex)) {
iindex = +this._preProcessors[index.trim()];
}
code = code.replace(match[0], name + iindex);
match = regex.exec(code);
}
return code;
}
_generateLeftOverUBOCode(name, uniformBufferDescription) {
let ubo = `layout(set = ${uniformBufferDescription.binding.groupIndex}, binding = ${uniformBufferDescription.binding.bindingIndex}) uniform ${name} {\n `;
for (const leftOverUniform of this._webgpuProcessingContext.leftOverUniforms) {
if (leftOverUniform.length > 0) {
ubo += ` ${leftOverUniform.type} ${leftOverUniform.name}[${leftOverUniform.length}];\n`;
}
else {
ubo += ` ${leftOverUniform.type} ${leftOverUniform.name};\n`;
}
}
ubo += "};\n\n";
return ubo;
}
finalizeShaders(vertexCode, fragmentCode) {
// make replacements for texture names in the texture array case
for (let i = 0; i < this._textureArrayProcessing.length; ++i) {
const name = this._textureArrayProcessing[i];
vertexCode = this._applyTextureArrayProcessing(vertexCode, name);
fragmentCode = this._applyTextureArrayProcessing(fragmentCode, name);
}
// inject the missing varying in the fragment shader
for (let i = 0; i < this._missingVaryings.length; ++i) {
const decl = this._missingVaryings[i];
if (decl && decl.length > 0) {
fragmentCode = decl + "\n" + fragmentCode;
}
}
// Builds the leftover UBOs.
const leftOverUBO = this._buildLeftOverUBO();
vertexCode = leftOverUBO + vertexCode;
fragmentCode = leftOverUBO + fragmentCode;
this._collectBindingNames();
this._preCreateBindGroupEntries();
this._preProcessors = null;
this._webgpuProcessingContext.vertexBufferKindToNumberOfComponents = {};
return { vertexCode, fragmentCode };
}
}
/** This file must only contain pure code and pure imports */
const builtInName_frag_depth = "fragmentOutputs.fragDepth";
const leftOverVarName = "uniforms";
const internalsVarName = "internals";
const gpuTextureViewDimensionByWebGPUTextureFunction = {
texture_1d: "1d" /* WebGPUConstants.TextureViewDimension.E1d */,
texture_2d: "2d" /* WebGPUConstants.TextureViewDimension.E2d */,
texture_2d_array: "2d-array" /* WebGPUConstants.TextureViewDimension.E2dArray */,
texture_3d: "3d" /* WebGPUConstants.TextureViewDimension.E3d */,
texture_cube: "cube" /* WebGPUConstants.TextureViewDimension.Cube */,
texture_cube_array: "cube-array" /* WebGPUConstants.TextureViewDimension.CubeArray */,
texture_multisampled_2d: "2d" /* WebGPUConstants.TextureViewDimension.E2d */,
texture_depth_2d: "2d" /* WebGPUConstants.TextureViewDimension.E2d */,
texture_depth_2d_array: "2d-array" /* WebGPUConstants.TextureViewDimension.E2dArray */,
texture_depth_cube: "cube" /* WebGPUConstants.TextureViewDimension.Cube */,
texture_depth_cube_array: "cube-array" /* WebGPUConstants.TextureViewDimension.CubeArray */,
texture_depth_multisampled_2d: "2d" /* WebGPUConstants.TextureViewDimension.E2d */,
texture_storage_1d: "1d" /* WebGPUConstants.TextureViewDimension.E1d */,
texture_storage_2d: "2d" /* WebGPUConstants.TextureViewDimension.E2d */,
texture_storage_2d_array: "2d-array" /* WebGPUConstants.TextureViewDimension.E2dArray */,
texture_storage_3d: "3d" /* WebGPUConstants.TextureViewDimension.E3d */,
texture_external: null,
};
/** @internal */
class WebGPUShaderProcessorWGSL extends WebGPUShaderProcessor {
constructor() {
super(...arguments);
this.shaderLanguage = 1 /* ShaderLanguage.WGSL */;
this.uniformRegexp = /uniform\s+(\w+)\s*:\s*(.+)\s*;/;
this.textureRegexp = /var\s+(\w+)\s*:\s*((array<\s*)?(texture_\w+)\s*(<\s*(.+)\s*>)?\s*(,\s*\w+\s*>\s*)?);/;
this.noPrecision = true;
this.pureMode = false;
}
_getArraySize(name, uniformType, preProcessors) {
let length = 0;
const endArray = uniformType.lastIndexOf(">");
if (uniformType.indexOf("array") >= 0 && endArray > 0) {
let startArray = endArray;
while (startArray > 0 && uniformType.charAt(startArray) !== " " && uniformType.charAt(startArray) !== ",") {
startArray--;
}
const lengthInString = uniformType.substring(startArray + 1, endArray);
length = +lengthInString;
if (isNaN(length)) {
length = +preProcessors[lengthInString.trim()];
}
while (startArray > 0 && (uniformType.charAt(startArray) === " " || uniformType.charAt(startArray) === ",")) {
startArray--;
}
uniformType = uniformType.substring(uniformType.indexOf("<") + 1, startArray + 1);
}
return [name, uniformType, length];
}
initializeShaders(processingContext) {
this._webgpuProcessingContext = processingContext;
this._attributesInputWGSL = [];
this._attributesWGSL = [];
this._attributesConversionCodeWGSL = [];
this._hasNonFloatAttribute = false;
this._varyingsWGSL = [];
this._varyingNamesWGSL = [];
this._stridedUniformArrays = [];
}
preProcessShaderCode(code) {
// Same check as in webgpuShaderProcessorsGLSL to avoid same ubDelcaration to be injected twice.
const ubDeclaration = this.pureMode
? ""
: `struct ${WebGPUShaderProcessor.InternalsUBOName} {\n yFactor_: f32,\n textureOutputHeight_: f32,\n};\nvar<uniform> ${internalsVarName} : ${WebGPUShaderProcessor.InternalsUBOName};\n`;
const alreadyInjected = code.indexOf(ubDeclaration) !== -1;
return alreadyInjected ? code : ubDeclaration + RemoveComments(code);
}
varyingCheck(varying) {
const regex = /(flat|linear|perspective)?\s*(center|centroid|sample)?\s*\bvarying\b/;
return regex.test(varying);
}
varyingProcessor(varying, isFragment, preProcessors) {
const varyingRegex = /\s*(flat|linear|perspective)?\s*(center|centroid|sample)?\s*varying\s+(?:(?:highp)?|(?:lowp)?)\s*(\S+)\s*:\s*(.+)\s*;/gm;
const match = varyingRegex.exec(varying);
if (match !== null) {
const interpolationType = match[1] ?? "perspective";
const interpolationSampling = match[2] ?? "center";
const varyingType = match[4];
const name = match[3];
const interpolation = interpolationType === "flat" ? `@interpolate(${interpolationType})` : `@interpolate(${interpolationType}, ${interpolationSampling})`;
let location;
if (isFragment) {
location = this._webgpuProcessingContext.availableVaryings[name];
if (location === undefined) {
Logger.Warn(`Invalid fragment shader: The varying named "${name}" is not declared in the vertex shader! This declaration will be ignored.`);
}
}
else {
location = this._webgpuProcessingContext.getVaryingNextLocation(varyingType, this._getArraySize(name, varyingType, preProcessors)[2]);
this._webgpuProcessingContext.availableVaryings[name] = location;
this._varyingsWGSL.push(` @location(${location}) ${interpolation} ${name} : ${varyingType},`);
this._varyingNamesWGSL.push(name);
}
varying = "";
}
return varying;
}
attributeProcessor(attribute, preProcessors) {
const attribRegex = /\s*attribute\s+(\S+)\s*:\s*(.+)\s*;/gm;
const match = attribRegex.exec(attribute);
if (match !== null) {
const attributeType = match[2];
const name = match[1];
const location = this._webgpuProcessingContext.getAttributeNextLocation(attributeType, this._getArraySize(name, attributeType, preProcessors)[2]);
this._webgpuProcessingContext.availableAttributes[name] = location;
this._webgpuProcessingContext.orderedAttributes[location] = name;
const numComponents = this._webgpuProcessingContext.vertexBufferKindToNumberOfComponents[name];
if (numComponents !== undefined) {
// Special case for an int/ivecX vertex buffer that is used as a float/vecX attribute in the shader.
const newType = numComponents < 0 ? (numComponents === -1 ? "i32" : "vec" + -numComponents + "<i32>") : numComponents === 1 ? "u32" : "vec" + numComponents + "<u32>";
const newName = `_int_${name}_`;
this._attributesInputWGSL.push(`@location(${location}) ${newName} : ${newType},`);
this._attributesWGSL.push(`${name} : ${attributeType},`);
this._attributesConversionCodeWGSL.push(`vertexInputs.${name} = ${attributeType}(vertexInputs_.${newName});`);
this._hasNonFloatAttribute = true;
}
else {
this._attributesInputWGSL.push(`@location(${location}) ${name} : ${attributeType},`);
this._attributesWGSL.push(`${name} : ${attributeType},`);
this._attributesConversionCodeWGSL.push(`vertexInputs.${name} = vertexInputs_.${name};`);
}
attribute = "";
}
return attribute;
}
uniformProcessor(uniform, isFragment, preProcessors) {
const match = this.uniformRegexp.exec(uniform);
if (match !== null) {
const uniformType = match[2];
const name = match[1];
this._addUniformToLeftOverUBO(name, uniformType, preProcessors);
uniform = "";
}
return uniform;
}
textureProcessor(texture, isFragment, preProcessors) {
const match = this.textureRegexp.exec(texture);
if (match !== null) {
const name = match[1]; // name of the variable
const type = match[2]; // texture_2d<f32> or array<texture_2d_array<f32>, 5> for eg
const isArrayOfTexture = !!match[3];
const textureFunc = match[4]; // texture_2d, texture_depth_2d, etc
const isStorageTexture = textureFunc.indexOf("storage") > 0;
const componentType = match[6]; // f32 or i32 or u32 or undefined
let storageTextureFormat = null;
let storageTextureAccess = "write-only" /* WebGPUConstants.StorageTextureAccess.WriteOnly */;
if (isStorageTexture) {
const commaIndex = componentType.indexOf(",");
storageTextureFormat = componentType.substring(0, commaIndex).trim();
const accessMode = componentType.substring(commaIndex + 1).trim();
switch (accessMode) {
case "read":
storageTextureAccess = "read-only" /* WebGPUConstants.StorageTextureAccess.ReadOnly */;
break;
case "read_write":
storageTextureAccess = "read-write" /* WebGPUConstants.StorageTextureAccess.ReadWrite */;
break;
default:
storageTextureAccess = "write-only" /* WebGPUConstants.StorageTextureAccess.WriteOnly */;
break;
}
}
let arraySize = isArrayOfTexture ? this._getArraySize(name, type, preProcessors)[2] : 0;
let textureInfo = this._webgpuProcessingContext.availableTextures[name];
if (!textureInfo) {
textureInfo = {
isTextureArray: arraySize > 0,
isStorageTexture,
storageTextureAccess: isStorageTexture ? storageTextureAccess : undefined,
textures: [],
sampleType: "float" /* WebGPUConstants.TextureSampleType.Float */,
};
arraySize = arraySize || 1;
for (let i = 0; i < arraySize; ++i) {
textureInfo.textures.push(this._webgpuProcessingContext.getNextFreeUBOBinding());
}
}
else {
arraySize = textureInfo.textures.length;
}
this._webgpuProcessingContext.availableTextures[name] = textureInfo;
const isDepthTexture = textureFunc.indexOf("depth") > 0;
const textureDimension = gpuTextureViewDimensionByWebGPUTextureFunction[textureFunc];
const sampleType = isDepthTexture
? "depth" /* WebGPUConstants.TextureSampleType.Depth */
: componentType === "u32"
? "uint" /* WebGPUConstants.TextureSampleType.Uint */
: componentType === "i32"
? "sint" /* WebGPUConstants.TextureSampleType.Sint */
: "float" /* WebGPUConstants.TextureSampleType.Float */;
textureInfo.sampleType = sampleType;
if (textureDimension === undefined) {
// eslint-disable-next-line no-throw-literal
throw `Can't get the texture dimension corresponding to the texture function "${textureFunc}"!`;
}
for (let i = 0; i < arraySize; ++i) {
const { groupIndex, bindingIndex } = textureInfo.textures[i];
if (i === 0) {
texture = `@group(${groupIndex}) @binding(${bindingIndex}) ${texture}`;
}
this._addTextureBindingDescription(name, textureInfo, i, textureDimension, storageTextureFormat, !isFragment, storageTextureAccess);
}
}
return texture;
}
_convertDefinesToConst(preProcessors) {
let code = "";
for (const key in preProcessors) {
const value = preProcessors[key];
if (key.startsWith("__")) {
continue;
}
if (!isNaN(parseInt(value)) || !isNaN(parseFloat(value))) {
code += `const ${key} = ${value};\n`;
}
else if (key && value === "") {
code += `const ${key} = true;\n`;
}
}
return code;
}
postProcessor(code, _defines, _isFragment, _processingContext, _parameters, preProcessors, preProcessorsFromCode) {
// Collect the preprocessor names (coming from a "#define NAME VALUE" declaration) directly defined in the shader code (preProcessorsFromCode) and not defined at the material level (preProcessors).
// This is because we will have to perform a replace on the code to replace the defines with their values.
//
// We don't have to do it for preprocessor names defined at the material level because replacing them by "const NAME = VALUE;" will take care of it (see _convertDefinesToConst()) and is faster than doing a search/replace for each of them.
//
// The reason why doing "const NAME = VALUE;" doesn't work for preprocessor names defined in the code is that VALUE can be any string and not only numbers or booleans.
// So, if we have this code:
//
// #define vDiffuseUV vMainUV
// textureSample(..., fragmentInputs.vDiffuseUV)
///
// only a search/replace will work, 'const vDiffuseUV = "vMainUV";' will not work
//
// Note that the search/replace text processing will also apply to the "#define NAME VALUE" definition itself, so it will become "#define VALUE VALUE"
// It's not a problem, though, because all #define XXX will be commented out in the final code.
const defineList = [];
for (const key in preProcessorsFromCode) {
const value = preProcessorsFromCode[key];
// Excludes the defines that are booleans (note that there aren't "false" booleans: we simply don't add them in the preProcessorsFromCode object).
// That's because we need (at least some of) them to stay untouched, like #define DISABLE_UNIFORMTY_ANALYSIS or #define CUSTOM_VERTEX_BEGIN (else, they would be replaced with "#define true" after the search/replace processing)
if (value !== "true") {
defineList.push(key);
}
}
// We must sort the define names by length to avoid replacing a define with a longer name (ex: #define A 1 and #define AB 2, if we replace A with 1, we will have #define 1B 2)
// So, we start by longest names and we finish with the shortest ones.
defineList.sort((a, b) => (a.length - b.length > 0 ? -1 : a.length === b.length ? 0 : 1));
for (const name of defineList) {
// Let's retrieve the value of the define from the code
// Note that we can't use the value from preProcessorsFromCode[name] because this value could have been changed from a previous replace
// For example:
// #define IOR 1.333
// #define ETA 1.0/IOR
//
// After IOR replacement is performed, we will have:
// #define 1.333 1.333
// #define ETA 1.0/1.333
//
// but preProcessorsFromCode["ETA"] is still "1.0/IOR" and not "1.0/1.333", so we must retrieve the value for ETA from the current code
const i0 = code.indexOf("#define " + name);
let i1 = code.indexOf("\n", i0);
if (i1 === -1) {
i1 = code.length;
}
const value = code.substring(i0 + 8 + name.length + 1, i1);
code = code.replace(new RegExp(name, "g"), value);
}
code = this._convertDefinesToConst(preProcessors) + code;
if ("VERTEXOUTPUT_INVARIANT" in preProcessors) {
code = "#define VERTEXOUTPUT_INVARIANT\n" + code;
}
return code;
}
finalizeShaders(vertexCode, fragmentCode) {
const enabledExtensions = [];
const fragCoordCode = fragmentCode.indexOf("fragmentInputs.position") >= 0 && !this.pureMode
? `
if (internals.yFactor_ == 1.) {
fragmentInputs.position.y = internals.textureOutputHeight_ - fragmentInputs.position.y;
}
`
: "";
// Add the group/binding info to the sampler declaration (var xxx: sampler|sampler_comparison)
vertexCode = this._processSamplers(vertexCode, true);
fragmentCode = this._processSamplers(fragmentCode, false);
// Add the group/binding info to the uniform/storage buffer declarations (var<uniform> XXX:YYY or var<storage(,read_write|read)> XXX:YYY)
vertexCode = this._processCustomBuffers(vertexCode, true);
fragmentCode = this._processCustomBuffers(fragmentCode, false);
// Builds the leftover UBOs.
const leftOverUBO = this._buildLeftOverUBO();
vertexCode = leftOverUBO + vertexCode;
fragmentCode = leftOverUBO + fragmentCode;
// Vertex code
vertexCode = vertexCode.replace(/#define /g, "//#define ");
vertexCode = this._processStridedUniformArrays(vertexCode);
let vertexInputs = "struct VertexInputs {\n @builtin(vertex_index) vertexIndex : u32,\n @builtin(instance_index) instanceIndex : u32,\n";
if (this._attributesInputWGSL.length > 0) {
vertexInputs += this._attributesInputWGSL.join("\n");
}
vertexInputs += "\n};\nvar<private> vertexInputs" + (this._hasNonFloatAttribute ? "_" : "") + " : VertexInputs;\n";
if (this._hasNonFloatAttribute) {
vertexInputs += "struct VertexInputs_ {\n vertexIndex : u32, instanceIndex : u32,\n";
vertexInputs += this._attributesWGSL.join("\n");
vertexInputs += "\n};\nvar<private> vertexInputs : VertexInputs_;\n";
}
let vertexOutputs = "struct FragmentInputs {\n @builtin(position)" + (vertexCode.indexOf("#define VERTEXOUTPUT_INVARIANT") >= 0 ? " @invariant" : "") + " position : vec4<f32>,\n";
if (this._varyingsWGSL.length > 0) {
vertexOutputs += this._varyingsWGSL.join("\n");
}
vertexOutputs += "\n};\nvar<private> vertexOutputs : FragmentInputs;\n";
vertexCode = vertexInputs + vertexOutputs + vertexCode;
let vertexMainStartingCode = `\n vertexInputs${this._hasNonFloatAttribute ? "_" : ""} = input;\n`;
if (this._hasNonFloatAttribute) {
vertexMainStartingCode += "vertexInputs.vertexIndex = vertexInputs_.vertexIndex;\nvertexInputs.instanceIndex = vertexInputs_.instanceIndex;\n";
vertexMainStartingCode += this._attributesConversionCodeWGSL.join("\n");
vertexMainStartingCode += "\n";
}
const vertexMainEndingCode = this.pureMode
? ` return vertexOutputs;`
: ` vertexOutputs.position.y = vertexOutputs.position.y * internals.yFactor_;\n return vertexOutputs;`;
let needDiagnosticOff = vertexCode.indexOf(Constants.DISABLEUA) !== -1;
vertexCode =
(needDiagnosticOff ? "diagnostic(off, derivative_uniformity);\n" : "") +
"diagnostic(off, chromium.unreachable_code);\n" +
InjectStartingAndEndingCode(vertexCode, "fn main", vertexMainStartingCode, vertexMainEndingCode);
// fragment code
fragmentCode = fragmentCode.replace(/#define /g, "//#define ");
fragmentCode = this._processStridedUniformArrays(fragmentCode);
if (!this.pureMode) {
fragmentCode = fragmentCode.replace(/dpdy/g, "(-internals.yFactor_)*dpdy"); // will also handle dpdyCoarse and dpdyFine
}
let fragmentInputs = "struct FragmentInputs {\n @builtin(position) position : vec4<f32>,\n @builtin(front_facing) frontFacing : bool,\n";
if (this._varyingsWGSL.length > 0) {
fragmentInputs += this._varyingsWGSL.join("\n");
}
fragmentInputs += "\n};\nvar<private> fragmentInputs : FragmentInputs;\n";
let fragmentOutputs = "struct FragmentOutputs {\n";
// A fragData location is float (vec4<f32>) unless the shader writes an integer vector to it — a shader
// rendering into an integer color target (e.g. RGBA_INTEGER u32) must declare a matching integer output.
// Detect the integer type from the assignment's right-hand side: an inline vec4<u32>/vec4<i32> constructor,
// or a plain identifier whose declaration is an integer vec4. Defaults to vec4<f32> (backward compatible).
// LIMITATION: this is a lightweight regex scan, not a type checker. Any other RHS shape — a function call, a
// `select(...)`, or a conditional expression that yields an integer vec4 — is NOT recognized and defaults to
// vec4<f32>; that surfaces later as a WGSL "cannot assign vec4<u32> to vec4<f32>" compile error rather than
// here. Shaders that render to integer targets should assign an inline vec4<u32>/vec4<i32> (constructor or a
// directly-typed local) so the output type is detected. Extend the patterns below if new forms are needed.
// Scan a comment-stripped copy so a commented-out integer write can't flip a real float output.
const scanCode = fragmentCode.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/\/\/[^\n]*/g, " ");
// fragData writes are in `fn main`; scope the RHS-identifier declaration lookup there so a same-named integer
// local in a helper function can't win. Match the entry point exactly (not a substring, which `fn mainHelper`
// would satisfy); a no-match (-1) bails to the vec4<f32> default rather than scanning the whole source.
const mainBodyStart = scanCode.search(/\bfn\s+main\s*\(/);
const detectIntVec = (expr) => {
if (/^\s*(vec4<u32>|vec4u\s*\()/.test(expr)) {
return "vec4<u32>";
}
if (/^\s*(vec4<i32>|vec4i\s*\()/.test(expr)) {
return "vec4<i32>";
}
return null;
};
const fragDataOutputType = (index) => {
const assign = scanCode.match(new RegExp(`fragmentOutputs\\.fragData${index}\\s*=\\s*([^;]+);`));
if (!assign) {
return "vec4<f32>";
}
const rhs = assign[1].trim();
const direct = detectIntVec(rhs);
if (direct) {
return direct;
}
// Bare identifier RHS (e.g. `fragData0 = computedUintColor;`): resolve its type from a `var/let NAME :
// vec4<u32>` or `var/let NAME = vec4<u32>(...)` declaration, searched only in main up to this write.
const id = rhs.match(/^([A-Za-z_]\w*)$/);
if (id && mainBodyStart >= 0) {
const declScope = scanCode.slice(mainBodyStart, assign.index ?? scanCode.length);
const typed = declScope.match(new RegExp(`(?:var|let)\\s+${id[1]}\\s*:\\s*(vec4<u32>|vec4<i32>)`));
if (typed) {
return typed[1];
}
const inited = declScope.match(new RegExp(`(?:var|let)\\s+${id[1]}\\s*=\\s*([^;]+);`));
if (inited) {
const t = detectIntVec(inited[1].trim());
if (t) {
return t;
}
}
}
return "vec4<f32>";
};
// Adding fragData output locations
const regexRoot = "fragmentOutputs\\.fragData";
let match = scanCode.match(new RegExp(regexRoot + "0", "g"));
let indexLocation = 0;
if (match) {
fragmentOutputs += ` @location(${indexLocation}) fragData0 : ${fragDataOutputType(0)},\n`;
indexLocation++;
for (let index = 1; index < 8; index++) {
match = scanCode.match(new RegExp(regexRoot + index, "g"));
if (match) {
fragmentOutputs += ` @location(${indexLocation}) fragData${indexLocation} : ${fragDataOutputType(index)},\n`;
indexLocation++;
}
}
if (fragmentCode.indexOf("MRT_AND_COLOR") !== -1) {
fragmentOutputs += ` @location(${indexLocation}) color : vec4<f32>,\n`;
indexLocation++;
}
}
// Adding fragData output locations
const regex = /oitDepthSampler/;
match = fragmentCode.match(regex);
if (match) {
fragmentOutputs += ` @location(${indexLocation++}) depth : vec2<f32>,\n`;
fragmentOutputs += ` @location(${indexLocation++}) frontColor : vec4<f32>,\n`;
fragmentOutputs += ` @location(${indexLocation++}) backColor : vec4<f32>,\n`;
}
if (indexLocation === 0) {
const useDualSourceBlending = fragmentCode.indexOf("DUAL_SOURCE_BLENDING") !== -1;
if (useDualSourceBlending) {
enabledExtensions.push("dual_source_blending");
fragmentOutputs += " @location(0) @blend_src(0) color : vec4<f32>,\n";
fragmentOutputs += " @location(0) @blend_src(1) color2 : vec4<f32>,\n";
}
else {
fragmentOutputs += " @location(0) color : vec4<f32>,\n";
}
// indexLocation++; // if you need this variable after this block, uncomment this
}
// FragDepth
let hasFragDepth = false;
let idx = 0;
while (!hasFragDepth) {
idx = fragmentCode.indexOf(builtInName_frag_depth, idx);
if (idx < 0) {
break;
}
const saveIndex = idx;
hasFragDepth = true;
while (idx > 1 && fragmentCode.charAt(idx) !== "\n") {
if (fragmentCode.charAt(idx) === "/" && fragmentCode.charAt(idx - 1) === "/") {
hasFragDepth = false;
break;
}
idx--;
}
idx = saveIndex + builtInName_frag_depth.length;
}
if (hasFragDepth) {
fragmentOutputs += " @builtin(frag_depth) fragDepth: f32,\n";
}
fragmentOutputs += "};\nvar<private> fragmentOutputs : FragmentOutputs;\n";
fragmentCode = fragmentInputs + fragmentOutputs + fragmentCode;
const fragmentStartingCode = " fragmentInputs = input;\n " + fragCoordCode;
const fragmentEndingCode = " return fragmentOutputs;";
needDiagnosticOff = fragmentCode.indexOf(Constants.DISABLEUA) !== -1;
if (enabledExtensions.length > 0) {
fragmentCode = "enable " + enabledExtensions.join(";\nenable ") + ";\n" + fragmentCode;
}
fragmentCode =
(needDiagnosticOff ? "diagnostic(off, derivative_uniformity);\n" : "") +
"diagnostic(off, chromium.unreachable_code);\n" +
InjectStartingAndEndingCode(fragmentCode, "fn main", fragmentStartingCode, fragmentEndingCode);
this._collectBindingNames();
this._preCreateBindGroupEntries();
this._webgpuProcessingContext.vertexBufferKindToNumberOfComponents = {};
return { vertexCode, fragmentCode };
}
_generateLeftOverUBOCode(name, uniformBufferDescription) {
let stridedArrays = "";
let ubo = `struct ${name} {\n`;
for (const leftOverUniform of this._webgpuProcessingContext.leftOverUniforms) {
const type = leftOverUniform.type.replace(/^(.*?)(<.*>)?$/, "$1");
const size = WebGPUShaderProcessor.UniformSizes[type];
if (leftOverUniform.length > 0) {
if (size <= 2) {
const stridedArrayType = `${name}_${this._stridedUniformArrays.length}_strided_arr`;
stridedArrays += `struct ${stridedArrayType} {
@size(16)
el: ${type},
}`;
this._stridedUniformArrays.push(leftOverUniform.name);
ubo += ` @align(16) ${leftOverUniform.name} : array<${stridedArrayType}, ${leftOverUniform.length}>,\n`;
}
else {
ubo += ` ${leftOverUniform.name} : array<${leftOverUniform.type}, ${leftOverUniform.length}>,\n`;
}
}
else {
ubo += ` ${leftOverUniform.name} : ${leftOverUniform.type},\n`;
}
}
ubo += "};\n";
ubo = `${stridedArrays}\n${ubo}`;
ubo += `@group(${uniformBufferDescription.binding.groupIndex}) @binding(${uniformBufferDescription.binding.bindingIndex}) var<uniform> ${leftOverVarName} : ${name};\n`;
return ubo;
}
_processSamplers(code, isVertex) {
const samplerRegexp = /var\s+(\w+Sampler)\s*:\s*(sampler|sampler_comparison)\s*;/gm;
// eslint-disable-next-line no-constant-condition
while (true) {
const match = samplerRegexp.exec(code);
if (match === null) {
break;
}
const name = match[1]; // name of the variable
const samplerType = match[2]; // sampler or sampler_comparison
const suffixLessLength = name.length - Constants.AUTOSAMPLERSUFFIX.length;
const textureName = name.lastIndexOf(Constants.AUTOSAMPLERSUFFIX) === suffixLessLength ? name.substring(0, suffixLessLength) : null;
const samplerBindingType = samplerType === "sampler_comparison" ? "comparison" /* WebGPUConstants.SamplerBindingType.Comparison */ : "filtering" /* WebGPUConstants.SamplerBindingType.Filtering */;
if (textureName) {
const textureInfo = this._webgpuProcessingContext.availableTextures[textureName];
if (textureInfo) {
textureInfo.autoBindSampler = true;
}
}
let samplerInfo = this._webgpuProcessingContext.availableSamplers[name];
if (!samplerInfo) {
samplerInfo = {
binding: this._webgpuProcessingContext.getNextFreeUBOBinding(),
type: samplerBindingType,
};
this._webgpuProcessingContext.availableSamplers[name] = samplerInfo;
}
this._addSamplerBindingDescription(name, samplerInfo, isVertex);
const part1 = code.substring(0, match.index);
const insertPart = `@group(${samplerInfo.binding.groupIndex}) @binding(${samplerInfo.binding.bindingIndex}) `;
const part2 = code.substring(match.index);
code = part1 + insertPart + part2;
samplerRegexp.lastIndex += insertPart.length;
}
return code;
}
_processCustomBuffers(code, isVertex) {
const instantiateBufferRegexp = /var<\s*(uniform|storage)\s*(,\s*(read|read_write)\s*)?>\s+(\S+)\s*:\s*(\S+)\s*;/gm;
// eslint-disable-next-line no-constant-condition
while (true) {
const match = instantiateBufferRegexp.exec(code);
if (match === null) {
break;
}
const type = match[1];
const decoration = match[3];
let name = match[4];
const structName = match[5];
let bufferInfo = this._webgpuProcessingContext.availableBuffers[name];
if (!bufferInfo) {
const knownUBO = type === "uniform" ? WebGPUShaderProcessingContext.KnownUBOs[structName] : null;
let binding;
if (knownUBO) {
name = structName;
binding = knownUBO.binding;
if (binding.groupIndex === -1) {
binding = this._webgpuProcessingContext.availableBuffers[name]?.binding;
if (!binding) {
binding = this._webgpuProcessingContext.getNextFreeUBOBinding();
}
}
}
else {
binding = this._webgpuProcessingContext.getNextFreeUBOBinding();
}
bufferInfo = { binding };
this._webgpuProcessingContext.availableBuffers[name] = bufferInfo;
}
this._addBufferBindingDescription(name, this._webgpuProcessingContext.availableBuffers[name], decoration === "read_write"
? "storage" /* WebGPUConstants.BufferBindingType.Storage */
: type === "storage"
? "read-only-storage" /* WebGPUConstants.BufferBindingType.ReadOnlyStorage */
: "uniform" /* WebGPUConstants.BufferBindingType.Uniform */, isVertex);
const groupIndex = bufferInfo.binding.groupIndex;
const bindingIndex = bufferInfo.binding.bindingIndex;
const part1 = code.substring(0, match.index);
const insertPart = `@group(${groupIndex}) @binding(${bindingIndex}) `;
const part2 = code.substring(match.index);
code = part1 + insertPart + part2;
instantiateBufferRegexp.lastIndex += insertPart.length;
}
return code;
}
_processStridedUniformArrays(code) {
for (const uniformArrayName of this._stridedUniformArrays) {
code = code.replace(new RegExp(`${uniformArrayName}\\s*\\[(.*?)\\]`, "g"), `${uniformArrayName}[$1].el`);
}
return code;
}
}
/** @internal */
class WebGPUHardwareTexture {
get underlyingResource() {
return this._webgpuTexture;
}
// index is the index of the layer for a 2DArrayTexture / 3DTexture, the face index for a cube texture or the layer * 6 + face index for a cube array texture.
// In single render target case, index is 0.
getMSAATexture(sampleCount, index = 0) {
const texture = this._webgpuMSAATexture[index];
if (!texture) {
this._createMSAATexture(sampleCount, index);
}
return this._webgpuMSAATexture[index];
}
releaseMSAATextures() {
for (const texture of this._webgpuMSAATexture) {
if (texture) {
this._engine._textureHelper.releaseTexture(texture);
}
}
this._webgpuMSAATexture.length = 0;
}
constructor(_engine, existingTexture = null) {
this._engine = _engine;
/** @internal */
this._originalFormatIsRGB = false;
this.format = "rgba8unorm" /* WebGPUConstants.TextureFormat.RGBA8Unorm */;
// This is the original format requested. It can be different from "format" in case original format is a depth/stencil format and MSAA is requested.
// In that case, format will be a R16 or R32 format, because the texture will be used as the resolve texture.
this.originalFormat = "rgba8unorm" /* WebGPUConstants.TextureFormat.RGBA8Unorm */;
this.textureUsages = 0;
this.textureAdditionalUsages = 0;
this._webgpuTexture = existingTexture;
this._webgpuMSAATexture = [];
this.view = null;
this.viewForWriting = null;
}
set(hardwareTexture) {
this._webgpuTexture = hardwareTexture;
}
setUsage(_textureSource, generateMipMaps, is2DArray, isCube, is3D, width, height, depth) {
let viewDimension = "2d" /* WebGPUConstants.TextureViewDimension.E2d */;
let arrayLayerCount = 1;
if (isCube) {
viewDimension = is2DArray ? "cube-array" /* WebGPUConstants.TextureViewDimension.CubeArray */ : "cube" /* WebGPUConstants.TextureViewDimension.Cube */;
arrayLayerCount = 6 * (depth || 1);
}
else if (is3D) {
viewDimension = "3d" /* WebGPUConstants.TextureViewDimension.E3d */;
arrayLayerCount = 1;
}
else if (is2DArray) {
viewDimension = "2d-array" /* WebGPUConstants.TextureViewDimension.E2dArray */;
arrayLayerCount = depth;
}
const format = WebGPUTextureHelper.GetDepthFormatOnly(this.format);
const aspect = WebGPUTextureHelper.HasDepthAndStencilAspects(this.format) ? "depth-only" /* WebGPUConstants.TextureAspect.DepthOnly */ : "all" /* WebGPUConstants.TextureAspect.All */;
this.createView({
label: `TextureView${is3D ? "3D" : isCube ? "Cube" : "2D"}${is2DArray ? "_Array" + arrayLayerCount : ""}_${width}x${height}_${generateMipMaps ? "wmips" : "womips"}_${this.format}_${viewDimension}`,
format,
dimension: viewDimension,
mipLevelCount: generateMipMaps ? ILog2(Math.max(width, height)) + 1 : 1,
baseArrayLayer: 0,
baseMipLevel: 0,
arrayLayerCount,
aspect,
});
}
createView(descriptor, createViewForWriting = false) {
this.view = this._webgpuTexture.createView(descriptor);
if (createViewForWriting && descriptor) {
const saveNumMipMaps = descriptor.mipLevelCount;
descriptor.mipLevelCount = 1;
this.viewForWriting = this._webgpuTexture.createView(descriptor);
descriptor.mipLevelCount = saveNumMipMaps;
}
}
reset() {
this._webgpuTexture = null;
this._webgpuMSAATexture.length = 0;
this.view = null;
this.viewForWriting = null;
}
release() {
this._webgpuTexture?.destroy();
this.releaseMSAATextures();
this._copyInvertYTempTexture?.destroy();
this.reset();
}
_createMSAATexture(samples, index) {
if (!this._webgpuTexture) {
throw new Error("Cannot create GPU MSAA texture because underlying GPU texture is not created yet.");
}
if (!this._webgpuMSAATexture) {
this._webgpuMSAATexture = [];
}
this._webgpuMSAATexture[index] = this._engine._textureHelper.createMSAATexture(this._webgpuTexture, this.originalFormat, samples);
}
}
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable babylonjs/available */
// License for the mipmap generation code:
//
// Copyright 2020 Brandon Jones
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// TODO WEBGPU improve mipmap generation by using compute shaders
const mipmapVertexSource = `
const pos = array<vec2<f32>, 4>( vec2f(-1.0f, 1.0f), vec2f(1.0f, 1.0f), vec2f(-1.0f, -1.0f), vec2f(1.0f, -1.0f));
const tex = array<vec2<f32>, 4>( vec2f(0.0f, 0.0f), vec2f(1.0f, 0.0f), vec2f(0.0f, 1.0f), vec2f(1.0f, 1.0f));
varying vTex: vec2f;
@vertex
fn main(input : VertexInputs) -> FragmentInputs {
vertexOutputs.vTex = tex[input.vertexIndex];
vertexOutputs.position = vec4f(pos[input.vertexIndex], 0.0, 1.0);
}
`;
const mipmapFragmentSource = `
var imgSampler: sampler;
var img: texture_2d<f32>;
varying vTex: vec2f;
@fragment
fn main(input: FragmentInputs) -> FragmentOutputs {
fragmentOutputs.color = textureSample(img, imgSampler, input.vTex);
}
`;
const invertYPreMultiplyAlphaVertexSource = `
const pos = array<vec2<f32>, 4>( vec2f(-1.0f, 1.0f), vec2f(1.0f, 1.0f), vec2f(-1.0f, -1.0f), vec2f(1.0f, -1.0f));
const tex = array<vec2<f32>, 4>( vec2f(0.0f, 0.0f), vec2f(1.0f, 0.0f), vec2f(0.0f, 1.0f), vec2f(1.0f, 1.0f));
var img: texture_2d<f32>;
#ifdef INVERTY
varying vTextureSize: vec2f;
#endif
@vertex
fn main(input : VertexInputs) -> FragmentInputs {
#ifdef INVERTY
vertexOutputs.vTextureSize = vec2f(textureDimensions(img, 0));
#endif
vertexOutputs.position = vec4f(pos[input.vertexIndex], 0.0, 1.0);
}
`;
const invertYPreMultiplyAlphaFragmentSource = `
var img: texture_2d<f32>;
#ifdef INVERTY
varying vTextureSize: vec2f;
#endif
@fragment
fn main(input: FragmentInputs) -> FragmentOutputs {
#ifdef INVERTY
var color: vec4f = textureLoad(img, vec2i(i32(input.position.x), i32(input.vTextureSize.y - input.position.y)), 0);
#else
var color: vec4f = textureLoad(img, vec2i(input.position.xy), 0);
#endif
#ifdef PREMULTIPLYALPHA
color = vec4f(color.rgb * color.a, color.a);
#endif
fragmentOutputs.color = color;
}
`;
const invertYPreMultiplyAlphaWithOfstVertexSource = invertYPreMultiplyAlphaVertexSource;
const invertYPreMultiplyAlphaWithOfstFragmentSource = `
var img: texture_2d<f32>;
uniform ofstX: f32;
uniform ofstY: f32;
uniform width: f32;
uniform height: f32;
#ifdef INVERTY
varying vTextureSize: vec2f;
#endif
@fragment
fn main(input: FragmentInputs) -> FragmentOutputs {
if (input.position.x < uniforms.ofstX || input.position.x >= uniforms.ofstX + uniforms.width) {
discard;
}
if (input.position.y < uniforms.ofstY || input.position.y >= uniforms.ofstY + uniforms.height) {
discard;
}
#ifdef INVERTY
var color: vec4f = textureLoad(img, vec2i(i32(input.position.x), i32(uniforms.ofstY + uniforms.height - (input.position.y - uniforms.ofstY))), 0);
#else
var color: vec4f = textureLoad(img, vec2i(input.position.xy), 0);
#endif
#ifdef PREMULTIPLYALPHA
color = vec4f(color.rgb * color.a, color.a);
#endif
fragmentOutputs.color = color;
}
`;
const clearVertexSource = `
const pos = array<vec2<f32>, 4>( vec2f(-1.0f, 1.0f), vec2f(1.0f, 1.0f), vec2f(-1.0f, -1.0f), vec2f(1.0f, -1.0f));
@vertex
fn main(input : VertexInputs) -> FragmentInputs {
vertexOutputs.position = vec4f(pos[input.vertexIndex], 0.0, 1.0);
}
`;
const clearFragmentSource = `
uniform color: vec4f;
@fragment
fn main(input: FragmentInputs) -> FragmentOutputs {
fragmentOutputs.color = uniforms.color;
}
`;
const copyVideoToTextureVertexSource = `
struct VertexOutput {
@builtin(position) Position : vec4<f32>,
@location(0) fragUV : vec2<f32>
}
@vertex
fn main(
@builtin(vertex_index) VertexIndex : u32
) -> VertexOutput {
var pos = array<vec2<f32>, 4>(
vec2(-1.0, 1.0),
vec2( 1.0, 1.0),
vec2(-1.0, -1.0),
vec2( 1.0, -1.0)
);
var tex = array<vec2<f32>, 4>(
vec2(0.0, 0.0),
vec2(1.0, 0.0),
vec2(0.0, 1.0),
vec2(1.0, 1.0)
);
var output: VertexOutput;
output.Position = vec4<f32>(pos[VertexIndex], 0.0, 1.0);
output.fragUV = tex[VertexIndex];
return output;
}
`;
const copyVideoToTextureFragmentSource = `
@group(0) @binding(0) var videoSampler: sampler;
@group(0) @binding(1) var videoTexture: texture_external;
@fragment
fn main(
@location(0) fragUV: vec2<f32>
) -> @location(0) vec4<f32> {
return textureSampleBaseClampToEdge(videoTexture, videoSampler, fragUV);
}
`;
const copyVideoToTextureInvertYFragmentSource = `
@group(0) @binding(0) var videoSampler: sampler;
@group(0) @binding(1) var videoTexture: texture_external;
@fragment
fn main(
@location(0) fragUV: vec2<f32>
) -> @location(0) vec4<f32> {
return textureSampleBaseClampToEdge(videoTexture, videoSampler, vec2<f32>(fragUV.x, 1.0 - fragUV.y));
}
`;
const resolveDepthVertexSource = `
const pos = array<vec2<f32>, 4>( vec2f(-1.0f, 1.0f), vec2f(1.0f, 1.0f), vec2f(-1.0f, -1.0f), vec2f(1.0f, -1.0f));
@vertex
fn main(input : VertexInputs) -> FragmentInputs {
vertexOutputs.position = vec4f(pos[input.vertexIndex], 0.0, 1.0);
}
`;
const resolveDepthFragmentSource = `
var msaaDepthTexture: texture_depth_multisampled_2d;
@fragment
fn main(input: FragmentInputs) -> FragmentOutputs {
#ifdef USE_MIN
let numSamples = textureNumSamples(msaaDepthTexture);
var depth = 1.0;
for (var i = 0u; i < numSamples; i = i + 1u) {
depth = min(depth, textureLoad(msaaDepthTexture, vec2u(input.position.xy), i));
}
fragmentOutputs.color = vec4f(depth);
#else
fragmentOutputs.color = vec4f(textureLoad(msaaDepthTexture, vec2u(input.position.xy), 0)); // do like WebGL, take the first sample
#endif
}
`;
var PipelineType;
(function (PipelineType) {
PipelineType[PipelineType["MipMap"] = 0] = "MipMap";
PipelineType[PipelineType["InvertYPremultiplyAlpha"] = 1] = "InvertYPremultiplyAlpha";
PipelineType[PipelineType["Clear"] = 2] = "Clear";
PipelineType[PipelineType["InvertYPremultiplyAlphaWithOfst"] = 3] = "InvertYPremultiplyAlphaWithOfst";
PipelineType[PipelineType["ResolveDepth"] = 4] = "ResolveDepth";
})(PipelineType || (PipelineType = {}));
var VideoPipelineType;
(function (VideoPipelineType) {
VideoPipelineType[VideoPipelineType["DontInvertY"] = 0] = "DontInvertY";
VideoPipelineType[VideoPipelineType["InvertY"] = 1] = "InvertY";
})(VideoPipelineType || (VideoPipelineType = {}));
const shadersForPipelineType = [
{ vertex: mipmapVertexSource, fragment: mipmapFragmentSource },
{ vertex: invertYPreMultiplyAlphaVertexSource, fragment: invertYPreMultiplyAlphaFragmentSource },
{ vertex: clearVertexSource, fragment: clearFragmentSource },
{ vertex: invertYPreMultiplyAlphaWithOfstVertexSource, fragment: invertYPreMultiplyAlphaWithOfstFragmentSource },
{ vertex: resolveDepthVertexSource, fragment: resolveDepthFragmentSource },
];
/**
* Map a (renderable) texture format (GPUTextureFormat) to an index for fast lookup (in caches for eg)
* The number of entries should not go over 64! Else, the code in WebGPUCacheRenderPipeline.setMRT should be updated
*/
const renderableTextureFormatToIndex = {
"": 0,
r8unorm: 1,
r8uint: 2,
r8sint: 3,
r16uint: 4,
r16sint: 5,
r16float: 6,
rg8unorm: 7,
rg8uint: 8,
rg8sint: 9,
r32uint: 10,
r32sint: 11,
r32float: 12,
rg16uint: 13,
rg16sint: 14,
rg16float: 15,
rgba8unorm: 16,
"rgba8unorm-srgb": 17,
rgba8uint: 18,
rgba8sint: 19,
bgra8unorm: 20,
"bgra8unorm-srgb": 21,
rgb10a2uint: 22,
rgb10a2unorm: 23,
/* rg11b10ufloat: this entry is dynamically added if the "RG11B10UFloatRenderable" extension is supported */
rg32uint: 24,
rg32sint: 25,
rg32float: 26,
rgba16uint: 27,
rgba16sint: 28,
rgba16float: 29,
rgba32uint: 30,
rgba32sint: 31,
rgba32float: 32,
stencil8: 33,
depth16unorm: 34,
depth24plus: 35,
"depth24plus-stencil8": 36,
depth32float: 37,
"depth32float-stencil8": 38,
r16unorm: 39,
rg16unorm: 40,
rgba16unorm: 41,
r16snorm: 42,
rg16snorm: 43,
rgba16snorm: 44,
};
/** @internal */
class WebGPUTextureManager {
//------------------------------------------------------------------------------
// Initialization / Helpers
//------------------------------------------------------------------------------
constructor(engine, device, bufferManager, enabledExtensions) {
this._pipelines = {};
this._compiledShaders = [];
this._videoPipelines = {};
this._videoCompiledShaders = [];
this._deferredReleaseTextures = [];
this._engine = engine;
this._device = device;
this._bufferManager = bufferManager;
if (enabledExtensions.indexOf("rg11b10ufloat-renderable" /* WebGPUConstants.FeatureName.RG11B10UFloatRenderable */) !== -1) {
const keys = Object.keys(renderableTextureFormatToIndex);
renderableTextureFormatToIndex["rg11b10ufloat" /* WebGPUConstants.TextureFormat.RG11B10UFloat */] = renderableTextureFormatToIndex[keys[keys.length - 1]] + 1;
}
this._mipmapSampler = device.createSampler({ minFilter: "linear" /* WebGPUConstants.FilterMode.Linear */ });
this._videoSampler = device.createSampler({ minFilter: "linear" /* WebGPUConstants.FilterMode.Linear */ });
this._ubCopyWithOfst = this._bufferManager.createBuffer(4 * 4, BufferUsage.Uniform | BufferUsage.CopyDst, "UBCopyWithOffset").underlyingResource;
this._getPipeline("rgba8unorm" /* WebGPUConstants.TextureFormat.RGBA8Unorm */);
this._getVideoPipeline("rgba8unorm" /* WebGPUConstants.TextureFormat.RGBA8Unorm */);
}
_getPipeline(format, type = PipelineType.MipMap, params) {
const index = type === PipelineType.MipMap
? 1 << 0
: type === PipelineType.InvertYPremultiplyAlpha
? ((params.invertY ? 1 : 0) << 1) + ((params.premultiplyAlpha ? 1 : 0) << 2)
: type === PipelineType.Clear
? 1 << 3
: type === PipelineType.InvertYPremultiplyAlphaWithOfst
? ((params.invertY ? 1 : 0) << 4) + ((params.premultiplyAlpha ? 1 : 0) << 5)
: type === PipelineType.ResolveDepth
? 1 << 6
: 0;
if (!this._pipelines[format]) {
this._pipelines[format] = [];
}
let pipelineAndBGL = this._pipelines[format][index];
if (!pipelineAndBGL) {
let defines = "";
if (type === PipelineType.InvertYPremultiplyAlpha || type === PipelineType.InvertYPremultiplyAlphaWithOfst) {
if (params.invertY) {
defines += "#define INVERTY\n";
}
if (params.premultiplyAlpha) {
defines += "#define PREMULTIPLYALPHA\n";
}
}
let modules = this._compiledShaders[index];
if (!modules) {
let vertexCode = shadersForPipelineType[type].vertex;
let fragmentCode = shadersForPipelineType[type].fragment;
const processorOptions = {
defines: defines.split("\n"),
indexParameters: null,
isFragment: false,
shouldUseHighPrecisionShader: true,
processor: this._engine._getShaderProcessor(1 /* ShaderLanguage.WGSL */),
supportsUniformBuffers: true,
shadersRepository: "",
includesShadersStore: {},
version: (this._engine.version * 100).toString(),
platformName: this._engine.shaderPlatformName,
processingContext: this._engine._getShaderProcessingContext(1 /* ShaderLanguage.WGSL */, true),
isNDCHalfZRange: this._engine.isNDCHalfZRange,
useReverseDepthBuffer: this._engine.useReverseDepthBuffer,
};
Initialize(processorOptions);
// Disable special additions not needed here
processorOptions.processor.pureMode = true;
Process(vertexCode, processorOptions, (migratedVertexCode) => {
vertexCode = migratedVertexCode;
}, this._engine);
processorOptions.isFragment = true;
Process(fragmentCode, processorOptions, (migratedFragmentCode) => {
fragmentCode = migratedFragmentCode;
}, this._engine);
const final = Finalize(vertexCode, fragmentCode, processorOptions);
// Restore
processorOptions.processor.pureMode = false;
const vertexModule = this._device.createShaderModule({
label: `BabylonWebGPUDevice${this._engine.uniqueId}_InternalVertexShader_${index}`,
code: final.vertexCode,
});
const fragmentModule = this._device.createShaderModule({
label: `BabylonWebGPUDevice${this._engine.uniqueId}_InternalFragmentShader_${index}`,
code: final.fragmentCode,
});
modules = this._compiledShaders[index] = [vertexModule, fragmentModule];
}
const pipeline = this._device.createRenderPipeline({
label: `BabylonWebGPUDevice${this._engine.uniqueId}_InternalPipeline_${format}_${index}`,
layout: "auto" /* WebGPUConstants.AutoLayoutMode.Auto */,
vertex: {
module: modules[0],
entryPoint: "main",
},
fragment: {
module: modules[1],
entryPoint: "main",
targets: [
{
format,
},
],
},
primitive: {
topology: "triangle-strip" /* WebGPUConstants.PrimitiveTopology.TriangleStrip */,
stripIndexFormat: "uint16" /* WebGPUConstants.IndexFormat.Uint16 */,
},
});
pipelineAndBGL = this._pipelines[format][index] = [pipeline, pipeline.getBindGroupLayout(0)];
}
return pipelineAndBGL;
}
_getVideoPipeline(format, type = VideoPipelineType.DontInvertY) {
const index = type === VideoPipelineType.InvertY ? 1 << 0 : 0;
if (!this._videoPipelines[format]) {
this._videoPipelines[format] = [];
}
let pipelineAndBGL = this._videoPipelines[format][index];
if (!pipelineAndBGL) {
let modules = this._videoCompiledShaders[index];
if (!modules) {
const vertexModule = this._device.createShaderModule({
code: copyVideoToTextureVertexSource,
label: `BabylonWebGPUDevice${this._engine.uniqueId}_CopyVideoToTexture_VertexShader`,
});
const fragmentModule = this._device.createShaderModule({
code: index === 0 ? copyVideoToTextureFragmentSource : copyVideoToTextureInvertYFragmentSource,
label: `BabylonWebGPUDevice${this._engine.uniqueId}_CopyVideoToTexture_FragmentShader_${index === 0 ? "DontInvertY" : "InvertY"}`,
});
modules = this._videoCompiledShaders[index] = [vertexModule, fragmentModule];
}
const pipeline = this._device.createRenderPipeline({
label: `BabylonWebGPUDevice${this._engine.uniqueId}_InternalVideoPipeline_${format}_${index === 0 ? "DontInvertY" : "InvertY"}`,
layout: "auto" /* WebGPUConstants.AutoLayoutMode.Auto */,
vertex: {
module: modules[0],
entryPoint: "main",
},
fragment: {
module: modules[1],
entryPoint: "main",
targets: [
{
format,
},
],
},
primitive: {
topology: "triangle-strip" /* WebGPUConstants.PrimitiveTopology.TriangleStrip */,
stripIndexFormat: "uint16" /* WebGPUConstants.IndexFormat.Uint16 */,
},
});
pipelineAndBGL = this._videoPipelines[format][index] = [pipeline, pipeline.getBindGroupLayout(0)];
}
return pipelineAndBGL;
}
setCommandEncoder(encoder) {
this._commandEncoderForCreation = encoder;
}
copyVideoToTexture(video, texture, format, invertY = false, commandEncoder) {
const useOwnCommandEncoder = commandEncoder === undefined;
const [pipeline, bindGroupLayout] = this._getVideoPipeline(format, invertY ? VideoPipelineType.InvertY : VideoPipelineType.DontInvertY);
if (useOwnCommandEncoder) {
commandEncoder = this._device.createCommandEncoder({});
}
commandEncoder.pushDebugGroup(`copy video to texture (invertY=${invertY})`);
const webgpuHardwareTexture = texture._hardwareTexture;
const renderPassDescriptor = {
label: `BabylonWebGPUDevice${this._engine.uniqueId}_copyVideoToTexture_${format}_${invertY ? "InvertY" : "DontInvertY"}${texture.label ? "_" + texture.label : ""}`,
colorAttachments: [
{
view: webgpuHardwareTexture.underlyingResource.createView({
format,
dimension: "2d" /* WebGPUConstants.TextureViewDimension.E2d */,
mipLevelCount: 1,
baseArrayLayer: 0,
baseMipLevel: 0,
arrayLayerCount: 1,
aspect: "all" /* WebGPUConstants.TextureAspect.All */,
}),
loadOp: "load" /* WebGPUConstants.LoadOp.Load */,
storeOp: "store" /* WebGPUConstants.StoreOp.Store */,
},
],
};
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
const descriptor = {
layout: bindGroupLayout,
entries: [
{
binding: 0,
resource: this._videoSampler,
},
{
binding: 1,
resource: this._device.importExternalTexture({
source: video.underlyingResource,
}),
},
],
};
const bindGroup = this._device.createBindGroup(descriptor);
passEncoder.setPipeline(pipeline);
passEncoder.setBindGroup(0, bindGroup);
passEncoder.draw(4, 1, 0, 0);
passEncoder.end();
commandEncoder.popDebugGroup();
if (useOwnCommandEncoder) {
this._device.queue.submit([commandEncoder.finish()]);
// eslint-disable-next-line no-useless-assignment
commandEncoder = null;
}
}
invertYPreMultiplyAlpha(gpuOrHdwTexture, width, height, format, invertY = false, premultiplyAlpha = false, faceIndex = 0, mipLevel = 0, layers = 1, ofstX = 0, ofstY = 0, rectWidth = 0, rectHeight = 0, commandEncoder,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
allowGPUOptimization) {
const useRect = rectWidth !== 0;
const useOwnCommandEncoder = commandEncoder === undefined;
const [pipeline, bindGroupLayout] = this._getPipeline(format, useRect ? PipelineType.InvertYPremultiplyAlphaWithOfst : PipelineType.InvertYPremultiplyAlpha, {
invertY,
premultiplyAlpha,
});
faceIndex = Math.max(faceIndex, 0);
if (useOwnCommandEncoder) {
commandEncoder = this._device.createCommandEncoder({});
}
let gpuTexture;
if (WebGPUTextureHelper.IsHardwareTexture(gpuOrHdwTexture)) {
gpuTexture = gpuOrHdwTexture.underlyingResource;
if (!(invertY && !premultiplyAlpha && layers === 1 && faceIndex === 0)) {
// we optimize only for the most likely case (invertY=true, premultiplyAlpha=false, layers=1, faceIndex=0) to avoid dealing with big caches
gpuOrHdwTexture = undefined;
}
}
else {
gpuTexture = gpuOrHdwTexture;
gpuOrHdwTexture = undefined;
}
if (!gpuTexture) {
return;
}
commandEncoder.pushDebugGroup(`internal process texture "${gpuTexture.label}" (invertY=${invertY} premultiplyAlpha=${premultiplyAlpha})`);
if (useRect) {
this._bufferManager.setRawData(this._ubCopyWithOfst, 0, new Float32Array([ofstX, ofstY, rectWidth, rectHeight]), 0, 4 * 4);
}
const webgpuHardwareTexture = gpuOrHdwTexture;
const outputTexture = webgpuHardwareTexture?._copyInvertYTempTexture ??
this.createTexture({ width, height, layers: 1 }, false, false, false, false, false, format, 1, commandEncoder, 1 /* WebGPUConstants.TextureUsage.CopySrc */ | 16 /* WebGPUConstants.TextureUsage.RenderAttachment */ | 4 /* WebGPUConstants.TextureUsage.TextureBinding */, undefined, "TempTextureForCopyWithInvertY");
const renderPassDescriptor = webgpuHardwareTexture?._copyInvertYRenderPassDescr ?? {
label: `BabylonWebGPUDevice${this._engine.uniqueId}_invertYPreMultiplyAlpha_${format}_${invertY ? "InvertY" : "DontInvertY"}_${premultiplyAlpha ? "PremultiplyAlpha" : "DontPremultiplyAlpha"}`,
colorAttachments: [
{
view: outputTexture.createView({
format,
dimension: "2d" /* WebGPUConstants.TextureViewDimension.E2d */,
baseMipLevel: 0,
mipLevelCount: 1,
arrayLayerCount: 1,
baseArrayLayer: 0,
}),
loadOp: "load" /* WebGPUConstants.LoadOp.Load */,
storeOp: "store" /* WebGPUConstants.StoreOp.Store */,
},
],
};
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
let bindGroup = useRect ? webgpuHardwareTexture?._copyInvertYBindGroupWithOfst : webgpuHardwareTexture?._copyInvertYBindGroup;
if (!bindGroup) {
const descriptor = {
layout: bindGroupLayout,
entries: [
{
binding: 0,
resource: gpuTexture.createView({
format,
dimension: "2d" /* WebGPUConstants.TextureViewDimension.E2d */,
baseMipLevel: mipLevel,
mipLevelCount: 1,
arrayLayerCount: layers,
baseArrayLayer: faceIndex,
}),
},
],
};
if (useRect) {
descriptor.entries.push({
binding: 1,
resource: {
buffer: this._ubCopyWithOfst,
},
});
}
bindGroup = this._device.createBindGroup(descriptor);
}
passEncoder.setPipeline(pipeline);
passEncoder.setBindGroup(0, bindGroup);
passEncoder.draw(4, 1, 0, 0);
passEncoder.end();
commandEncoder.copyTextureToTexture({
texture: outputTexture,
}, {
texture: gpuTexture,
mipLevel,
origin: {
x: 0,
y: 0,
z: faceIndex,
},
}, {
width: rectWidth || width,
height: rectHeight || height,
depthOrArrayLayers: 1,
});
if (webgpuHardwareTexture) {
webgpuHardwareTexture._copyInvertYTempTexture = outputTexture;
webgpuHardwareTexture._copyInvertYRenderPassDescr = renderPassDescriptor;
if (useRect) {
webgpuHardwareTexture._copyInvertYBindGroupWithOfst = bindGroup;
}
else {
webgpuHardwareTexture._copyInvertYBindGroup = bindGroup;
}
}
else {
this._deferredReleaseTextures.push([outputTexture, null]);
}
commandEncoder.popDebugGroup();
if (useOwnCommandEncoder) {
this._device.queue.submit([commandEncoder.finish()]);
// eslint-disable-next-line no-useless-assignment
commandEncoder = null;
}
}
//------------------------------------------------------------------------------
// Creation
//------------------------------------------------------------------------------
createTexture(imageBitmap, hasMipmaps = false, generateMipmaps = false, invertY = false, premultiplyAlpha = false, is3D = false, format = "rgba8unorm" /* WebGPUConstants.TextureFormat.RGBA8Unorm */, sampleCount = 1, commandEncoder, usage = -1, additionalUsages = 0, label, mipLevelCount) {
sampleCount = WebGPUTextureHelper.GetSample(sampleCount);
const layerCount = imageBitmap.layers || 1;
const textureSize = {
width: imageBitmap.width,
height: imageBitmap.height,
depthOrArrayLayers: layerCount,
};
const renderAttachmentFlag = renderableTextureFormatToIndex[format] ? 16 /* WebGPUConstants.TextureUsage.RenderAttachment */ : 0;
const isCompressedFormat = WebGPUTextureHelper.IsCompressedFormat(format);
const maxNumMipLevels = WebGPUTextureHelper.ComputeNumMipmapLevels(imageBitmap.width, imageBitmap.height);
const effectiveMipLevelCount = hasMipmaps ? Math.min(mipLevelCount ?? maxNumMipLevels, maxNumMipLevels) : 1;
const usages = usage >= 0 ? usage : 1 /* WebGPUConstants.TextureUsage.CopySrc */ | 2 /* WebGPUConstants.TextureUsage.CopyDst */ | 4 /* WebGPUConstants.TextureUsage.TextureBinding */;
additionalUsages |= hasMipmaps && !isCompressedFormat ? 1 /* WebGPUConstants.TextureUsage.CopySrc */ | renderAttachmentFlag : 0;
if (!isCompressedFormat && !is3D) {
// we don't know in advance if the texture will be updated with copyExternalImageToTexture (which requires to have those flags), so we need to force the flags all the times
additionalUsages |= renderAttachmentFlag | 2 /* WebGPUConstants.TextureUsage.CopyDst */;
}
const gpuTexture = this._device.createTexture({
label: `BabylonWebGPUDevice${this._engine.uniqueId}_Texture${is3D ? "3D" : "2D"}_${label ? label + "_" : ""}${textureSize.width}x${textureSize.height}x${textureSize.depthOrArrayLayers}_${hasMipmaps ? "wmips" : "womips"}_${format}_samples${sampleCount}`,
size: textureSize,
dimension: is3D ? "3d" /* WebGPUConstants.TextureDimension.E3d */ : "2d" /* WebGPUConstants.TextureDimension.E2d */,
format,
usage: usages | additionalUsages,
sampleCount,
mipLevelCount: effectiveMipLevelCount,
});
if (WebGPUTextureHelper.IsImageBitmap(imageBitmap)) {
this.updateTexture(imageBitmap, gpuTexture, imageBitmap.width, imageBitmap.height, layerCount, format, 0, 0, invertY, premultiplyAlpha, 0, 0);
if (hasMipmaps && generateMipmaps) {
this.generateMipmaps(gpuTexture, effectiveMipLevelCount, 0, commandEncoder);
}
}
return gpuTexture;
}
createCubeTexture(imageBitmaps, hasMipmaps = false, generateMipmaps = false, invertY = false, premultiplyAlpha = false, format = "rgba8unorm" /* WebGPUConstants.TextureFormat.RGBA8Unorm */, sampleCount = 1, commandEncoder, usage = -1, additionalUsages = 0, label) {
sampleCount = WebGPUTextureHelper.GetSample(sampleCount);
const width = WebGPUTextureHelper.IsImageBitmapArray(imageBitmaps) ? imageBitmaps[0].width : imageBitmaps.width;
const height = WebGPUTextureHelper.IsImageBitmapArray(imageBitmaps) ? imageBitmaps[0].height : imageBitmaps.height;
const layerCount = WebGPUTextureHelper.IsImageBitmapArray(imageBitmaps) ? 1 : imageBitmaps.layers;
const renderAttachmentFlag = renderableTextureFormatToIndex[format] ? 16 /* WebGPUConstants.TextureUsage.RenderAttachment */ : 0;
const isCompressedFormat = WebGPUTextureHelper.IsCompressedFormat(format);
const mipLevelCount = hasMipmaps ? WebGPUTextureHelper.ComputeNumMipmapLevels(width, height) : 1;
const usages = usage >= 0 ? usage : 1 /* WebGPUConstants.TextureUsage.CopySrc */ | 2 /* WebGPUConstants.TextureUsage.CopyDst */ | 4 /* WebGPUConstants.TextureUsage.TextureBinding */;
additionalUsages |= hasMipmaps && !isCompressedFormat ? 1 /* WebGPUConstants.TextureUsage.CopySrc */ | renderAttachmentFlag : 0;
if (!isCompressedFormat) {
// we don't know in advance if the texture will be updated with copyExternalImageToTexture (which requires to have those flags), so we need to force the flags all the times
additionalUsages |= renderAttachmentFlag | 2 /* WebGPUConstants.TextureUsage.CopyDst */;
}
const gpuTexture = this._device.createTexture({
label: `BabylonWebGPUDevice${this._engine.uniqueId}_TextureCube_${label ? label + "_" : ""}${width}x${height}x${layerCount}_${hasMipmaps ? "wmips" : "womips"}_${format}_samples${sampleCount}`,
size: {
width,
height,
depthOrArrayLayers: 6 * layerCount,
},
dimension: "2d" /* WebGPUConstants.TextureDimension.E2d */,
format,
usage: usages | additionalUsages,
sampleCount,
mipLevelCount,
});
if (WebGPUTextureHelper.IsImageBitmapArray(imageBitmaps)) {
this.updateCubeTextures(imageBitmaps, gpuTexture, width, height, format, invertY, premultiplyAlpha, 0, 0);
if (hasMipmaps && generateMipmaps) {
this.generateCubeMipmaps(gpuTexture, mipLevelCount, commandEncoder);
}
}
return gpuTexture;
}
generateCubeMipmaps(gpuOrHdwTexture, mipLevelCount, commandEncoder) {
const useOwnCommandEncoder = commandEncoder === undefined;
const gpuTexture = WebGPUTextureHelper.IsHardwareTexture(gpuOrHdwTexture) ? gpuOrHdwTexture.underlyingResource : gpuOrHdwTexture;
if (useOwnCommandEncoder) {
commandEncoder = this._device.createCommandEncoder({});
}
commandEncoder.pushDebugGroup(`create cube mipmaps for "${gpuTexture.label}" (${mipLevelCount} levels)`);
for (let f = 0; f < gpuTexture.depthOrArrayLayers; ++f) {
this.generateMipmaps(gpuOrHdwTexture, mipLevelCount, f, commandEncoder);
}
commandEncoder.popDebugGroup();
if (useOwnCommandEncoder) {
this._device.queue.submit([commandEncoder.finish()]);
// eslint-disable-next-line no-useless-assignment
commandEncoder = null;
}
}
generateMipmaps(gpuOrHdwTexture, mipLevelCount, faceIndex = 0, commandEncoder) {
const useOwnCommandEncoder = commandEncoder === undefined;
faceIndex = Math.max(faceIndex, 0);
if (useOwnCommandEncoder) {
commandEncoder = this._device.createCommandEncoder({});
}
let gpuTexture;
if (WebGPUTextureHelper.IsHardwareTexture(gpuOrHdwTexture)) {
gpuTexture = gpuOrHdwTexture.underlyingResource;
gpuOrHdwTexture._mipmapGenRenderPassDescr = gpuOrHdwTexture._mipmapGenRenderPassDescr || [];
gpuOrHdwTexture._mipmapGenBindGroup = gpuOrHdwTexture._mipmapGenBindGroup || [];
}
else {
gpuTexture = gpuOrHdwTexture;
gpuOrHdwTexture = undefined;
}
if (!gpuTexture) {
return;
}
commandEncoder.pushDebugGroup(`create mipmaps for "${gpuTexture.label}" (face #${faceIndex} - ${mipLevelCount} levels)`);
const format = gpuTexture.format;
const [pipeline, bindGroupLayout] = this._getPipeline(format);
const is3D = gpuTexture.dimension === "3d" /* WebGPUConstants.TextureDimension.E3d */;
const webgpuHardwareTexture = gpuOrHdwTexture;
for (let i = 1; i < mipLevelCount; ++i) {
const renderPassDescriptor = webgpuHardwareTexture?._mipmapGenRenderPassDescr[faceIndex]?.[i - 1] ?? {
label: `BabylonWebGPUDevice${this._engine.uniqueId}_generateMipmaps_${format}_faceIndex${faceIndex}_level${i}`,
colorAttachments: [
{
view: gpuTexture.createView({
format,
dimension: is3D ? "3d" /* WebGPUConstants.TextureViewDimension.E3d */ : "2d" /* WebGPUConstants.TextureViewDimension.E2d */,
baseMipLevel: i,
mipLevelCount: 1,
arrayLayerCount: 1,
baseArrayLayer: faceIndex,
}),
loadOp: "load" /* WebGPUConstants.LoadOp.Load */,
storeOp: "store" /* WebGPUConstants.StoreOp.Store */,
},
],
};
if (webgpuHardwareTexture) {
webgpuHardwareTexture._mipmapGenRenderPassDescr[faceIndex] = webgpuHardwareTexture._mipmapGenRenderPassDescr[faceIndex] || [];
webgpuHardwareTexture._mipmapGenRenderPassDescr[faceIndex][i - 1] = renderPassDescriptor;
}
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
const bindGroup = webgpuHardwareTexture?._mipmapGenBindGroup[faceIndex]?.[i - 1] ??
this._device.createBindGroup({
layout: bindGroupLayout,
entries: [
{
binding: 0,
resource: gpuTexture.createView({
format,
dimension: is3D ? "3d" /* WebGPUConstants.TextureViewDimension.E3d */ : "2d" /* WebGPUConstants.TextureViewDimension.E2d */,
baseMipLevel: i - 1,
mipLevelCount: 1,
arrayLayerCount: 1,
baseArrayLayer: faceIndex,
}),
},
{
binding: 1,
resource: this._mipmapSampler,
},
],
});
if (webgpuHardwareTexture) {
webgpuHardwareTexture._mipmapGenBindGroup[faceIndex] = webgpuHardwareTexture._mipmapGenBindGroup[faceIndex] || [];
webgpuHardwareTexture._mipmapGenBindGroup[faceIndex][i - 1] = bindGroup;
}
passEncoder.setPipeline(pipeline);
passEncoder.setBindGroup(0, bindGroup);
passEncoder.draw(4, 1, 0, 0);
passEncoder.end();
}
commandEncoder.popDebugGroup();
if (useOwnCommandEncoder) {
this._device.queue.submit([commandEncoder.finish()]);
// eslint-disable-next-line no-useless-assignment
commandEncoder = null;
}
}
createGPUTextureForInternalTexture(texture, width, height, depth, creationFlags) {
if (!texture._hardwareTexture) {
texture._hardwareTexture = new WebGPUHardwareTexture(this._engine);
}
if (width === undefined) {
width = texture.width;
}
if (height === undefined) {
height = texture.height;
}
if (depth === undefined) {
depth = texture.depth;
}
texture.width = texture.baseWidth = width;
texture.height = texture.baseHeight = height;
texture.depth = texture.baseDepth = depth;
const gpuTextureWrapper = texture._hardwareTexture;
const isStorageTexture = ((creationFlags ?? 0) & Constants.TEXTURE_CREATIONFLAG_STORAGE) !== 0;
const label = texture.label ? texture.label + "_InternalUniqueId" + texture.uniqueId : "InternalUniqueId" + texture.uniqueId;
gpuTextureWrapper.format = gpuTextureWrapper.originalFormat = WebGPUTextureHelper.GetWebGPUTextureFormat(texture.type, texture.format, texture._useSRGBBuffer);
if (texture.samples > 1) {
// In case of a MSAA texture, the current texture will be the "resolve" texture, which cannot have a depth format
switch (gpuTextureWrapper.format) {
case "depth16unorm" /* WebGPUConstants.TextureFormat.Depth16Unorm */:
gpuTextureWrapper.format = "r16unorm" /* WebGPUConstants.TextureFormat.R16Unorm */;
break;
case "depth24plus" /* WebGPUConstants.TextureFormat.Depth24Plus */:
case "depth24plus-stencil8" /* WebGPUConstants.TextureFormat.Depth24PlusStencil8 */:
case "depth32float" /* WebGPUConstants.TextureFormat.Depth32Float */:
case "depth32float-stencil8" /* WebGPUConstants.TextureFormat.Depth32FloatStencil8 */:
gpuTextureWrapper.format = "r32float" /* WebGPUConstants.TextureFormat.R32Float */;
break;
}
}
gpuTextureWrapper.textureUsages =
texture._source === 5 /* InternalTextureSource.RenderTarget */ || texture.source === 6 /* InternalTextureSource.MultiRenderTarget */
? 4 /* WebGPUConstants.TextureUsage.TextureBinding */ | 1 /* WebGPUConstants.TextureUsage.CopySrc */ | 16 /* WebGPUConstants.TextureUsage.RenderAttachment */
: texture._source === 12 /* InternalTextureSource.DepthStencil */
? 4 /* WebGPUConstants.TextureUsage.TextureBinding */ | 16 /* WebGPUConstants.TextureUsage.RenderAttachment */
: -1;
gpuTextureWrapper.textureAdditionalUsages = isStorageTexture ? 8 /* WebGPUConstants.TextureUsage.StorageBinding */ : 0;
const hasMipMaps = texture.generateMipMaps;
const layerCount = depth || 1;
let mipmapCount;
if (texture._maxLodLevel !== null) {
mipmapCount = texture._maxLodLevel;
}
else {
mipmapCount = hasMipMaps ? WebGPUTextureHelper.ComputeNumMipmapLevels(width, height) : 1;
}
if (texture.isCube) {
const gpuTexture = this.createCubeTexture({ width, height, layers: layerCount }, texture.generateMipMaps, texture.generateMipMaps, texture.invertY, false, gpuTextureWrapper.format, 1, this._commandEncoderForCreation, gpuTextureWrapper.textureUsages, gpuTextureWrapper.textureAdditionalUsages, label);
gpuTextureWrapper.set(gpuTexture);
const format = WebGPUTextureHelper.GetDepthFormatOnly(gpuTextureWrapper.format);
const aspect = WebGPUTextureHelper.HasDepthAndStencilAspects(gpuTextureWrapper.format) ? "depth-only" /* WebGPUConstants.TextureAspect.DepthOnly */ : "all" /* WebGPUConstants.TextureAspect.All */;
const dimension = texture.is2DArray ? "cube-array" /* WebGPUConstants.TextureViewDimension.CubeArray */ : "cube" /* WebGPUConstants.TextureViewDimension.Cube */;
gpuTextureWrapper.createView({
label: `BabylonWebGPUDevice${this._engine.uniqueId}_TextureViewCube${texture.is2DArray ? "_Array" + layerCount : ""}_${width}x${height}_${hasMipMaps ? "wmips" : "womips"}_${format}_${dimension}_${aspect}_${label}`,
format,
dimension,
mipLevelCount: mipmapCount,
baseArrayLayer: 0,
baseMipLevel: 0,
arrayLayerCount: 6 * layerCount,
aspect,
}, isStorageTexture);
}
else {
const gpuTexture = this.createTexture({ width, height, layers: layerCount }, hasMipMaps, texture.generateMipMaps, texture.invertY, false, texture.is3D, gpuTextureWrapper.format, 1, this._commandEncoderForCreation, gpuTextureWrapper.textureUsages, gpuTextureWrapper.textureAdditionalUsages, label);
gpuTextureWrapper.set(gpuTexture);
const arrayLayerCount = texture.is3D ? 1 : layerCount;
const format = WebGPUTextureHelper.GetDepthFormatOnly(gpuTextureWrapper.format);
const aspect = WebGPUTextureHelper.HasDepthAndStencilAspects(gpuTextureWrapper.format) ? "depth-only" /* WebGPUConstants.TextureAspect.DepthOnly */ : "all" /* WebGPUConstants.TextureAspect.All */;
const dimension = texture.is2DArray
? "2d-array" /* WebGPUConstants.TextureViewDimension.E2dArray */
: texture.is3D
? "3d" /* WebGPUConstants.TextureDimension.E3d */
: "2d" /* WebGPUConstants.TextureViewDimension.E2d */;
gpuTextureWrapper.createView({
label: `BabylonWebGPUDevice${this._engine.uniqueId}_TextureView${texture.is3D ? "3D" : "2D"}${texture.is2DArray ? "_Array" + arrayLayerCount : ""}_${width}x${height}${texture.is3D ? "x" + layerCount : ""}_${hasMipMaps ? "wmips" : "womips"}_${format}_${dimension}_${aspect}_${label}`,
format,
dimension,
mipLevelCount: mipmapCount,
baseArrayLayer: 0,
baseMipLevel: 0,
arrayLayerCount,
aspect,
}, isStorageTexture);
}
return gpuTextureWrapper;
}
createMSAATexture(gpuTexture, format, samples) {
return this.createTexture({ width: gpuTexture.width, height: gpuTexture.height, layers: 1 }, false, false, false, false, false, format, samples, this._commandEncoderForCreation, 16 /* WebGPUConstants.TextureUsage.RenderAttachment */ | 4 /* WebGPUConstants.TextureUsage.TextureBinding */, 0, gpuTexture.label ? gpuTexture.label + " (MSAA)" : "MSAA");
}
resolveMSAADepthTexture(msaaTexture, outputTexture, commandEncoder) {
const format = outputTexture.format;
const useOwnCommandEncoder = commandEncoder === undefined;
const [pipeline, bindGroupLayout] = this._getPipeline(format, PipelineType.ResolveDepth);
if (useOwnCommandEncoder) {
commandEncoder = this._device.createCommandEncoder({});
}
commandEncoder.pushDebugGroup(`resolve MSAA Depth texture "${msaaTexture.label}" to "${outputTexture.label}"`);
const renderPassDescriptor = {
label: `BabylonWebGPUDevice${this._engine.uniqueId}_resolveMSAADepthTexture${msaaTexture.label ? "_" + msaaTexture.label : ""}`,
colorAttachments: [
{
// Note: the WebGPU spec allows passing a GPUTexture directly as the view, but Safari rejects it (non-compliant).
// We explicitly create a GPUTextureView to work around this Safari bug.
view: outputTexture.createView({
format,
dimension: "2d" /* WebGPUConstants.TextureViewDimension.E2d */,
baseMipLevel: 0,
mipLevelCount: 1,
arrayLayerCount: 1,
baseArrayLayer: 0,
}),
loadOp: "load" /* WebGPUConstants.LoadOp.Load */,
storeOp: "store" /* WebGPUConstants.StoreOp.Store */,
},
],
};
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
const descriptor = {
layout: bindGroupLayout,
entries: [
{
binding: 0,
resource: msaaTexture.createView({
format: WebGPUTextureHelper.GetDepthFormatOnly(msaaTexture.format),
dimension: "2d" /* WebGPUConstants.TextureViewDimension.E2d */,
mipLevelCount: 1,
baseArrayLayer: 0,
baseMipLevel: 0,
arrayLayerCount: 1,
aspect: "depth-only" /* WebGPUConstants.TextureAspect.DepthOnly */,
}),
},
],
};
const bindGroup = this._device.createBindGroup(descriptor);
passEncoder.setPipeline(pipeline);
passEncoder.setBindGroup(0, bindGroup);
passEncoder.draw(4, 1, 0, 0);
passEncoder.end();
commandEncoder.popDebugGroup();
if (useOwnCommandEncoder) {
this._device.queue.submit([commandEncoder.finish()]);
// eslint-disable-next-line no-useless-assignment
commandEncoder = null;
}
}
//------------------------------------------------------------------------------
// Update
//------------------------------------------------------------------------------
updateCubeTextures(imageBitmaps, texture, width, height, format, invertY = false, premultiplyAlpha = false, offsetX = 0, offsetY = 0) {
const faces = [0, 3, 1, 4, 2, 5];
for (let f = 0; f < faces.length; ++f) {
const imageBitmap = imageBitmaps[faces[f]];
// Prefer InternalTexture when available: updateTexture needs it to run invertY/premultiplyAlpha
// GPU post-processing on raw (Uint8Array) uploads. Passing only a GPUTexture throws in that path.
this.updateTexture(imageBitmap, texture, width, height, 1, format, f, 0, invertY, premultiplyAlpha, offsetX, offsetY);
}
}
// TODO WEBGPU handle data source not being in the same format than the destination texture?
updateTexture(imageBitmap, texture, width, height, layers, format, faceIndex = 0, mipLevel = 0, invertY = false, premultiplyAlpha = false, offsetX = 0, offsetY = 0, allowGPUOptimization) {
const gpuTexture = WebGPUTextureHelper.IsInternalTexture(texture) ? texture._hardwareTexture.underlyingResource : texture;
const blockInformation = WebGPUTextureHelper.GetBlockInformationFromFormat(format);
const gpuOrHdwTexture = WebGPUTextureHelper.IsInternalTexture(texture) ? texture._hardwareTexture : texture;
const textureCopyView = {
texture: gpuTexture,
origin: {
x: offsetX,
y: offsetY,
z: Math.max(faceIndex, 0),
},
mipLevel: mipLevel,
premultipliedAlpha: premultiplyAlpha,
};
const textureExtent = {
width: Math.ceil(width / blockInformation.width) * blockInformation.width,
height: Math.ceil(height / blockInformation.height) * blockInformation.height,
depthOrArrayLayers: layers || 1,
};
if (imageBitmap.byteLength !== undefined) {
imageBitmap = imageBitmap;
const bytesPerRow = Math.ceil(width / blockInformation.width) * blockInformation.length;
const aligned = Math.ceil(bytesPerRow / 256) * 256 === bytesPerRow;
if (aligned) {
const commandEncoder = this._device.createCommandEncoder({});
const buffer = this._bufferManager.createRawBuffer(imageBitmap.byteLength, BufferUsage.MapWrite | BufferUsage.CopySrc, true, "TempBufferForUpdateTexture" + (gpuTexture ? "_" + gpuTexture.label : ""));
const arrayBuffer = buffer.getMappedRange();
new Uint8Array(arrayBuffer).set(imageBitmap);
buffer.unmap();
commandEncoder.copyBufferToTexture({
buffer: buffer,
offset: 0,
bytesPerRow,
rowsPerImage: textureExtent.height / blockInformation.height,
}, textureCopyView, textureExtent);
this._device.queue.submit([commandEncoder.finish()]);
this._bufferManager.releaseBuffer(buffer);
}
else {
this._device.queue.writeTexture(textureCopyView, imageBitmap, {
offset: 0,
bytesPerRow,
rowsPerImage: textureExtent.height / blockInformation.height,
}, textureExtent);
}
if (invertY || premultiplyAlpha) {
if (WebGPUTextureHelper.IsInternalTexture(texture)) {
const dontUseRect = offsetX === 0 && offsetY === 0 && width === texture.width && height === texture.height;
this.invertYPreMultiplyAlpha(gpuOrHdwTexture, texture.width, texture.height, format, invertY, premultiplyAlpha, faceIndex, mipLevel, layers || 1, offsetX, offsetY, dontUseRect ? 0 : width, dontUseRect ? 0 : height, undefined, allowGPUOptimization);
}
else {
// we should never take this code path
// eslint-disable-next-line no-throw-literal
throw "updateTexture: Can't process the texture data because a GPUTexture was provided instead of an InternalTexture!";
}
}
}
else {
imageBitmap = imageBitmap;
this._device.queue.copyExternalImageToTexture({ source: imageBitmap, flipY: invertY }, textureCopyView, textureExtent);
}
}
updateMipLevelCountForInternalTexture(texture, mipLevelCount) {
const maxNumMipLevels = WebGPUTextureHelper.ComputeNumMipmapLevels(texture.width, texture.height);
if (mipLevelCount !== undefined) {
texture.mipLevelCount = Math.min(Math.max(1, mipLevelCount), maxNumMipLevels);
}
else if (texture.generateMipMaps) {
texture.mipLevelCount = maxNumMipLevels;
}
else {
texture.mipLevelCount = 1;
}
}
// eslint-disable-next-line @typescript-eslint/promise-function-async
readPixels(texture, x, y, width, height, format, faceIndex = 0, mipLevel = 0, buffer = null, noDataConversion = false) {
const blockInformation = WebGPUTextureHelper.GetBlockInformationFromFormat(format);
const bytesPerRow = Math.ceil(width / blockInformation.width) * blockInformation.length;
const bytesPerRowAligned = Math.ceil(bytesPerRow / 256) * 256;
const size = bytesPerRowAligned * height;
const gpuBuffer = this._bufferManager.createRawBuffer(size, BufferUsage.MapRead | BufferUsage.CopyDst, undefined, "TempBufferForReadPixels" + (texture.label ? "_" + texture.label : ""));
const commandEncoder = this._device.createCommandEncoder({});
commandEncoder.copyTextureToBuffer({
texture,
mipLevel,
origin: {
x,
y,
z: Math.max(faceIndex, 0),
},
}, {
buffer: gpuBuffer,
offset: 0,
bytesPerRow: bytesPerRowAligned,
}, {
width,
height,
depthOrArrayLayers: 1,
});
this._device.queue.submit([commandEncoder.finish()]);
return this._bufferManager.readDataFromBuffer(gpuBuffer, size, width, height, bytesPerRow, bytesPerRowAligned, WebGPUTextureHelper.GetTextureTypeFromFormat(format), 0, buffer, true, noDataConversion);
}
//------------------------------------------------------------------------------
// Dispose
//------------------------------------------------------------------------------
releaseTexture(texture) {
if (WebGPUTextureHelper.IsInternalTexture(texture)) {
const hardwareTexture = texture._hardwareTexture;
const irradianceTexture = texture._irradianceTexture;
// We can't destroy the objects just now because they could be used in the current frame - we delay the destroying after the end of the frame
this._deferredReleaseTextures.push([hardwareTexture, irradianceTexture]);
}
else {
this._deferredReleaseTextures.push([texture, null]);
}
}
destroyDeferredTextures() {
for (let i = 0; i < this._deferredReleaseTextures.length; ++i) {
const [hardwareTexture, irradianceTexture] = this._deferredReleaseTextures[i];
if (hardwareTexture) {
if (WebGPUTextureHelper.IsHardwareTexture(hardwareTexture)) {
hardwareTexture.release();
}
else {
hardwareTexture.destroy();
}
}
irradianceTexture?.dispose();
}
this._deferredReleaseTextures.length = 0;
}
}
/** @internal */
class WebGPUDataBuffer extends DataBuffer {
/** @internal */
set buffer(buffer) {
this._buffer = buffer;
}
/** @internal */
constructor(resource, capacity = 0) {
super();
// Used to make sure the buffer is not recreated twice after a context loss/restoration
/** @internal */
this.engineId = -1;
this.capacity = capacity;
if (resource) {
this._buffer = resource;
}
}
/** @internal */
get underlyingResource() {
return this._buffer;
}
}
/** @internal */
class WebGPUBufferManager {
static _IsGPUBuffer(buffer) {
return buffer.underlyingResource === undefined;
}
static _FlagsToString(flags, suffix = "") {
let result = suffix;
for (let i = 0; i <= 9; ++i) {
if (flags & (1 << i)) {
if (result) {
result += "_";
}
result += BufferUsage[1 << i];
}
}
return result;
}
constructor(engine, device) {
this._deferredReleaseBuffers = [];
this._engine = engine;
this._device = device;
}
createRawBuffer(viewOrSize, flags, mappedAtCreation = false, label) {
const alignedLength = viewOrSize.byteLength !== undefined ? (viewOrSize.byteLength + 3) & -4 : (viewOrSize + 3) & -4; // 4 bytes alignments (because of the upload which requires this)
const verticesBufferDescriptor = {
label: "BabylonWebGPUDevice" + this._engine.uniqueId + "_" + WebGPUBufferManager._FlagsToString(flags, label ?? "Buffer") + "_size" + alignedLength,
mappedAtCreation,
size: alignedLength,
usage: flags,
};
return this._device.createBuffer(verticesBufferDescriptor);
}
createBuffer(viewOrSize, flags, label) {
const isView = viewOrSize.byteLength !== undefined;
const dataBuffer = new WebGPUDataBuffer();
const labelId = "DataBufferUniqueId=" + dataBuffer.uniqueId;
dataBuffer.buffer = this.createRawBuffer(viewOrSize, flags, undefined, label ? labelId + "-" + label : labelId);
dataBuffer.references = 1;
// Next line should work, because the "size" property of GPUBuffer is required by the spec, but it seems that it fails in the CI / in playwright tests. So, we will recalculate the aligned size ourselves.
//dataBuffer.capacity = dataBuffer.buffer.size;
dataBuffer.capacity = viewOrSize.byteLength !== undefined ? (viewOrSize.byteLength + 3) & -4 : (viewOrSize + 3) & -4; // 4 bytes alignments (because of the upload which requires this)
dataBuffer.engineId = this._engine.uniqueId;
if (isView) {
this.setSubData(dataBuffer, 0, viewOrSize);
}
return dataBuffer;
}
// This calls GPUBuffer.writeBuffer() with no alignment corrections
// dstByteOffset and byteLength must both be aligned to 4 bytes and bytes moved must be within src and dst arrays
setRawData(buffer, dstByteOffset, src, srcByteOffset, byteLength) {
srcByteOffset += src.byteOffset;
this._device.queue.writeBuffer(buffer, dstByteOffset, src.buffer, srcByteOffset, byteLength);
}
// This calls GPUBuffer.writeBuffer() with alignment corrections (dstByteOffset and byteLength will be aligned to 4 byte boundaries)
// If alignment is needed, src must be a full copy of dataBuffer, or at least should be large enough to cope with the additional bytes copied because of alignment!
setSubData(dataBuffer, dstByteOffset, src, srcByteOffset = 0, byteLength = 0) {
const buffer = dataBuffer.underlyingResource;
byteLength = byteLength || src.byteLength - srcByteOffset;
// Make sure the dst offset is aligned to 4 bytes
const startPre = dstByteOffset & 3;
srcByteOffset -= startPre;
dstByteOffset -= startPre;
// Make sure the byte length is aligned to 4 bytes
const originalByteLength = byteLength;
byteLength = (byteLength + startPre + 3) & -4;
// Check if the backing buffer of src is large enough to cope with the additional bytes copied because of alignment
const backingBufferSize = src.buffer.byteLength - src.byteOffset;
if (backingBufferSize < byteLength) {
// Not enough place in the backing buffer for the aligned copy.
// Creates a new buffer and copy the source data to it.
// The buffer will have byteLength - originalByteLength zeros at the end.
const tmpBuffer = new Uint8Array(byteLength);
tmpBuffer.set(new Uint8Array(src.buffer, src.byteOffset + srcByteOffset, originalByteLength));
src = tmpBuffer;
srcByteOffset = 0;
}
this.setRawData(buffer, dstByteOffset, src, srcByteOffset, byteLength);
}
_getHalfFloatAsFloatRGBAArrayBuffer(dataLength, arrayBuffer, destArray) {
if (!destArray) {
destArray = new Float32Array(dataLength);
}
else {
dataLength = Math.min(dataLength, destArray.length);
}
const srcData = new Uint16Array(arrayBuffer);
while (dataLength--) {
destArray[dataLength] = FromHalfFloat(srcData[dataLength]);
}
return destArray;
}
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/promise-function-async
readDataFromBuffer(gpuBuffer, size, width, height, bytesPerRow, bytesPerRowAligned, type = Constants.TEXTURETYPE_UNSIGNED_BYTE, offset = 0, buffer = null, destroyBuffer = true, noDataConversion = false) {
const floatFormat = type === Constants.TEXTURETYPE_FLOAT ? 2 : type === Constants.TEXTURETYPE_HALF_FLOAT ? 1 : 0;
const engineId = this._engine.uniqueId;
return new Promise((resolve, reject) => {
// eslint-disable-next-line github/no-then
gpuBuffer.mapAsync(1 /* WebGPUConstants.MapMode.Read */, offset, size).then(() => {
const copyArrayBuffer = gpuBuffer.getMappedRange(offset, size);
let data = buffer;
if (noDataConversion) {
if (data === null) {
data = allocateAndCopyTypedBuffer(type, size, true, copyArrayBuffer);
}
else {
data = allocateAndCopyTypedBuffer(type, data.buffer, undefined, copyArrayBuffer);
}
}
else {
if (data === null) {
switch (floatFormat) {
case 0: // byte format
data = new Uint8Array(size);
data.set(new Uint8Array(copyArrayBuffer));
break;
case 1: // half float
// TODO WEBGPU use computer shaders (or render pass) to make the conversion?
data = this._getHalfFloatAsFloatRGBAArrayBuffer(size / 2, copyArrayBuffer);
break;
case 2: // float
data = new Float32Array(size / 4);
data.set(new Float32Array(copyArrayBuffer));
break;
}
}
else {
switch (floatFormat) {
case 0: // byte format
data = new Uint8Array(data.buffer);
data.set(new Uint8Array(copyArrayBuffer, 0, Math.min(data.byteLength, size)));
break;
case 1: // half float
// TODO WEBGPU use computer shaders (or render pass) to make the conversion?
data = this._getHalfFloatAsFloatRGBAArrayBuffer(size / 2, copyArrayBuffer, buffer);
break;
case 2: // float
data = new Float32Array(data.buffer);
data.set(new Float32Array(copyArrayBuffer, 0, data.byteLength / 4));
break;
}
}
}
if (bytesPerRow !== bytesPerRowAligned) {
// TODO WEBGPU use computer shaders (or render pass) to build the final buffer data?
if (floatFormat === 1 && !noDataConversion) {
// half float have been converted to float above
bytesPerRow *= 2;
bytesPerRowAligned *= 2;
}
const data2 = new Uint8Array(data.buffer);
let offset = bytesPerRow, offset2;
for (let y = 1; y < height; ++y) {
offset2 = y * bytesPerRowAligned;
for (let x = 0; x < bytesPerRow; ++x) {
data2[offset++] = data2[offset2++];
}
}
if (floatFormat !== 0 && !noDataConversion) {
data = new Float32Array(data2.buffer, 0, offset / 4);
}
else {
data = new Uint8Array(data2.buffer, 0, offset);
}
}
gpuBuffer.unmap();
if (destroyBuffer) {
this.releaseBuffer(gpuBuffer);
}
resolve(data);
}, (reason) => {
if (this._engine.isDisposed || this._engine.uniqueId !== engineId) {
// The engine was disposed while waiting for the promise, or a context loss/restoration has occurred: don't reject
resolve(new Uint8Array());
}
else {
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
reject(reason);
}
});
});
}
releaseBuffer(buffer) {
if (WebGPUBufferManager._IsGPUBuffer(buffer)) {
this._deferredReleaseBuffers.push(buffer);
return true;
}
buffer.references--;
if (buffer.references === 0) {
this._deferredReleaseBuffers.push(buffer.underlyingResource);
return true;
}
return false;
}
destroyDeferredBuffers() {
for (let i = 0; i < this._deferredReleaseBuffers.length; ++i) {
this._deferredReleaseBuffers[i].destroy();
}
this._deferredReleaseBuffers.length = 0;
}
}
/**
* Note: we don't make a difference between mipmaps enabled or not when computing these bits (so, TEXTURE_NEAREST_NEAREST and TEXTURE_NEAREST_NEAREST_MIPNEAREST have the same bits, for example).
* There's another bit in the hash code for that (see FilterNoMipToBits).
*/
const FilterToBits = [
0 | (0 << 1) | (0 << 2), // not used
0 | (0 << 1) | (0 << 2), // TEXTURE_NEAREST_SAMPLINGMODE / TEXTURE_NEAREST_NEAREST
1 | (1 << 1) | (0 << 2), // TEXTURE_BILINEAR_SAMPLINGMODE / TEXTURE_LINEAR_LINEAR
1 | (1 << 1) | (1 << 2), // TEXTURE_TRILINEAR_SAMPLINGMODE / TEXTURE_LINEAR_LINEAR_MIPLINEAR
0 | (0 << 1) | (0 << 2), // TEXTURE_NEAREST_NEAREST_MIPNEAREST
0 | (1 << 1) | (0 << 2), // TEXTURE_NEAREST_LINEAR_MIPNEAREST
0 | (1 << 1) | (1 << 2), // TEXTURE_NEAREST_LINEAR_MIPLINEAR
0 | (1 << 1) | (0 << 2), // TEXTURE_NEAREST_LINEAR
0 | (0 << 1) | (1 << 2), // TEXTURE_NEAREST_NEAREST_MIPLINEAR
1 | (0 << 1) | (0 << 2), // TEXTURE_LINEAR_NEAREST_MIPNEAREST
1 | (0 << 1) | (1 << 2), // TEXTURE_LINEAR_NEAREST_MIPLINEAR
1 | (1 << 1) | (0 << 2), // TEXTURE_LINEAR_LINEAR_MIPNEAREST
1 | (0 << 1) | (0 << 2), // TEXTURE_LINEAR_NEAREST
];
// subtract 0x01FF from the comparison function value before indexing this array!
const ComparisonFunctionToBits = [
(0 << 3) | (0 << 4) | (0 << 5) | (0 << 6), // undefined
(0 << 3) | (0 << 4) | (0 << 5) | (1 << 6), // NEVER
(0 << 3) | (0 << 4) | (1 << 5) | (0 << 6), // LESS
(0 << 3) | (0 << 4) | (1 << 5) | (1 << 6), // EQUAL
(0 << 3) | (1 << 4) | (0 << 5) | (0 << 6), // LEQUAL
(0 << 3) | (1 << 4) | (0 << 5) | (1 << 6), // GREATER
(0 << 3) | (1 << 4) | (1 << 5) | (0 << 6), // NOTEQUAL
(0 << 3) | (1 << 4) | (1 << 5) | (1 << 6), // GEQUAL
(1 << 3) | (0 << 4) | (0 << 5) | (0 << 6), // ALWAYS
];
const FilterNoMipToBits = [
0 << 7, // not used
1 << 7, // TEXTURE_NEAREST_SAMPLINGMODE / TEXTURE_NEAREST_NEAREST
1 << 7, // TEXTURE_BILINEAR_SAMPLINGMODE / TEXTURE_LINEAR_LINEAR
0 << 7, // TEXTURE_TRILINEAR_SAMPLINGMODE / TEXTURE_LINEAR_LINEAR_MIPLINEAR
0 << 7, // TEXTURE_NEAREST_NEAREST_MIPNEAREST
0 << 7, // TEXTURE_NEAREST_LINEAR_MIPNEAREST
0 << 7, // TEXTURE_NEAREST_LINEAR_MIPLINEAR
1 << 7, // TEXTURE_NEAREST_LINEAR
0 << 7, // TEXTURE_NEAREST_NEAREST_MIPLINEAR
0 << 7, // TEXTURE_LINEAR_NEAREST_MIPNEAREST
0 << 7, // TEXTURE_LINEAR_NEAREST_MIPLINEAR
0 << 7, // TEXTURE_LINEAR_LINEAR_MIPNEAREST
1 << 7, // TEXTURE_LINEAR_NEAREST
];
/** @internal */
class WebGPUCacheSampler {
constructor(device) {
this._samplers = {};
this._device = device;
this.disabled = false;
}
static GetSamplerHashCode(sampler) {
// The WebGPU spec currently only allows values 1 and 4 for anisotropy
const anisotropy = sampler._cachedAnisotropicFilteringLevel ? sampler._cachedAnisotropicFilteringLevel : 1;
const code = FilterToBits[sampler.samplingMode] +
ComparisonFunctionToBits[(sampler._comparisonFunction || 0x0202) - 0x0200 + 1] +
FilterNoMipToBits[sampler.samplingMode] + // handle the lodMinClamp = lodMaxClamp = 0 case when no filter used for mip mapping
((sampler._cachedWrapU ?? 1) << 8) +
((sampler._cachedWrapV ?? 1) << 10) +
((sampler._cachedWrapR ?? 1) << 12) +
((sampler.useMipMaps ? 1 : 0) << 14) + // need to factor this in because _getSamplerFilterDescriptor depends on samplingMode AND useMipMaps!
(anisotropy << 15);
return code;
}
static _GetSamplerFilterDescriptor(sampler, anisotropy) {
let magFilter, minFilter, mipmapFilter, lodMinClamp, lodMaxClamp;
const useMipMaps = sampler.useMipMaps;
switch (sampler.samplingMode) {
case Constants.TEXTURE_LINEAR_LINEAR_MIPNEAREST:
magFilter = "linear" /* WebGPUConstants.FilterMode.Linear */;
minFilter = "linear" /* WebGPUConstants.FilterMode.Linear */;
mipmapFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
if (!useMipMaps) {
lodMinClamp = lodMaxClamp = 0;
}
break;
/* case Constants.TEXTURE_LINEAR_LINEAR_MIPLINEAR: */ // same value as below
case Constants.TEXTURE_TRILINEAR_SAMPLINGMODE:
magFilter = "linear" /* WebGPUConstants.FilterMode.Linear */;
minFilter = "linear" /* WebGPUConstants.FilterMode.Linear */;
if (!useMipMaps) {
mipmapFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
lodMinClamp = lodMaxClamp = 0;
}
else {
mipmapFilter = "linear" /* WebGPUConstants.FilterMode.Linear */;
}
break;
case Constants.TEXTURE_NEAREST_NEAREST_MIPLINEAR:
magFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
minFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
if (!useMipMaps) {
mipmapFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
lodMinClamp = lodMaxClamp = 0;
}
else {
mipmapFilter = "linear" /* WebGPUConstants.FilterMode.Linear */;
}
break;
case Constants.TEXTURE_NEAREST_NEAREST_MIPNEAREST:
magFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
minFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
mipmapFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
if (!useMipMaps) {
lodMinClamp = lodMaxClamp = 0;
}
break;
case Constants.TEXTURE_NEAREST_LINEAR_MIPNEAREST:
magFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
minFilter = "linear" /* WebGPUConstants.FilterMode.Linear */;
mipmapFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
if (!useMipMaps) {
lodMinClamp = lodMaxClamp = 0;
}
break;
case Constants.TEXTURE_NEAREST_LINEAR_MIPLINEAR:
magFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
minFilter = "linear" /* WebGPUConstants.FilterMode.Linear */;
if (!useMipMaps) {
mipmapFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
lodMinClamp = lodMaxClamp = 0;
}
else {
mipmapFilter = "linear" /* WebGPUConstants.FilterMode.Linear */;
}
break;
case Constants.TEXTURE_NEAREST_LINEAR:
magFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
minFilter = "linear" /* WebGPUConstants.FilterMode.Linear */;
mipmapFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
lodMinClamp = lodMaxClamp = 0;
break;
/* case Constants.TEXTURE_NEAREST_NEAREST: */ // same value as below
case Constants.TEXTURE_NEAREST_SAMPLINGMODE:
magFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
minFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
mipmapFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
lodMinClamp = lodMaxClamp = 0;
break;
case Constants.TEXTURE_LINEAR_NEAREST_MIPNEAREST:
magFilter = "linear" /* WebGPUConstants.FilterMode.Linear */;
minFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
mipmapFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
if (!useMipMaps) {
lodMinClamp = lodMaxClamp = 0;
}
break;
case Constants.TEXTURE_LINEAR_NEAREST_MIPLINEAR:
magFilter = "linear" /* WebGPUConstants.FilterMode.Linear */;
minFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
if (!useMipMaps) {
mipmapFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
lodMinClamp = lodMaxClamp = 0;
}
else {
mipmapFilter = "linear" /* WebGPUConstants.FilterMode.Linear */;
}
break;
/* case Constants.TEXTURE_LINEAR_LINEAR: */ // same value as below
case Constants.TEXTURE_BILINEAR_SAMPLINGMODE:
magFilter = "linear" /* WebGPUConstants.FilterMode.Linear */;
minFilter = "linear" /* WebGPUConstants.FilterMode.Linear */;
// In WebGL, if sampling mode is TEXTURE_BILINEAR_SAMPLINGMODE and anisotropy is greater than 1, anisotropy is enabled for the sampler
if (anisotropy > 1) {
mipmapFilter = "linear" /* WebGPUConstants.FilterMode.Linear */;
}
else {
mipmapFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
lodMinClamp = lodMaxClamp = 0;
}
break;
case Constants.TEXTURE_LINEAR_NEAREST:
magFilter = "linear" /* WebGPUConstants.FilterMode.Linear */;
minFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
mipmapFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
lodMinClamp = lodMaxClamp = 0;
break;
default:
magFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
minFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
mipmapFilter = "nearest" /* WebGPUConstants.FilterMode.Nearest */;
lodMinClamp = lodMaxClamp = 0;
break;
}
if (anisotropy > 1 && (lodMinClamp !== 0 || lodMaxClamp !== 0)) {
return {
magFilter: "linear" /* WebGPUConstants.FilterMode.Linear */,
minFilter: "linear" /* WebGPUConstants.FilterMode.Linear */,
mipmapFilter: "linear" /* WebGPUConstants.FilterMode.Linear */,
anisotropyEnabled: true,
};
}
return {
magFilter,
minFilter,
mipmapFilter,
lodMinClamp,
lodMaxClamp,
};
}
static _GetWrappingMode(mode) {
switch (mode) {
case Constants.TEXTURE_WRAP_ADDRESSMODE:
return "repeat" /* WebGPUConstants.AddressMode.Repeat */;
case Constants.TEXTURE_CLAMP_ADDRESSMODE:
return "clamp-to-edge" /* WebGPUConstants.AddressMode.ClampToEdge */;
case Constants.TEXTURE_MIRROR_ADDRESSMODE:
return "mirror-repeat" /* WebGPUConstants.AddressMode.MirrorRepeat */;
}
return "repeat" /* WebGPUConstants.AddressMode.Repeat */;
}
static _GetSamplerWrappingDescriptor(sampler) {
return {
addressModeU: this._GetWrappingMode(sampler._cachedWrapU),
addressModeV: this._GetWrappingMode(sampler._cachedWrapV),
addressModeW: this._GetWrappingMode(sampler._cachedWrapR),
};
}
static _GetSamplerDescriptor(sampler, label) {
// The check with Constants.TEXTURE_LINEAR_LINEAR is to be iso with the WebGL implementation
let anisotropy = (sampler.useMipMaps || sampler.samplingMode === Constants.TEXTURE_LINEAR_LINEAR) && sampler._cachedAnisotropicFilteringLevel
? sampler._cachedAnisotropicFilteringLevel
: 1;
// To be iso with the WebGL implementation
if (sampler.samplingMode !== Constants.TEXTURE_LINEAR_LINEAR_MIPNEAREST &&
sampler.samplingMode !== Constants.TEXTURE_LINEAR_LINEAR_MIPLINEAR &&
sampler.samplingMode !== Constants.TEXTURE_LINEAR_LINEAR) {
anisotropy = 1;
}
const filterDescriptor = this._GetSamplerFilterDescriptor(sampler, anisotropy);
return {
label,
...filterDescriptor,
...this._GetSamplerWrappingDescriptor(sampler),
compare: sampler._comparisonFunction ? WebGPUCacheSampler.GetCompareFunction(sampler._comparisonFunction) : undefined,
maxAnisotropy: filterDescriptor.anisotropyEnabled ? anisotropy : 1,
};
}
static GetCompareFunction(compareFunction) {
switch (compareFunction) {
case Constants.ALWAYS:
return "always" /* WebGPUConstants.CompareFunction.Always */;
case Constants.EQUAL:
return "equal" /* WebGPUConstants.CompareFunction.Equal */;
case Constants.GREATER:
return "greater" /* WebGPUConstants.CompareFunction.Greater */;
case Constants.GEQUAL:
return "greater-equal" /* WebGPUConstants.CompareFunction.GreaterEqual */;
case Constants.LESS:
return "less" /* WebGPUConstants.CompareFunction.Less */;
case Constants.LEQUAL:
return "less-equal" /* WebGPUConstants.CompareFunction.LessEqual */;
case Constants.NEVER:
return "never" /* WebGPUConstants.CompareFunction.Never */;
case Constants.NOTEQUAL:
return "not-equal" /* WebGPUConstants.CompareFunction.NotEqual */;
default:
return "less" /* WebGPUConstants.CompareFunction.Less */;
}
}
getSampler(sampler, bypassCache = false, hash = 0, label) {
if (this.disabled) {
return this._device.createSampler(WebGPUCacheSampler._GetSamplerDescriptor(sampler, label));
}
if (bypassCache) {
hash = 0;
}
else if (hash === 0) {
hash = WebGPUCacheSampler.GetSamplerHashCode(sampler);
}
let gpuSampler = bypassCache ? undefined : this._samplers[hash];
if (!gpuSampler) {
gpuSampler = this._device.createSampler(WebGPUCacheSampler._GetSamplerDescriptor(sampler, label));
if (!bypassCache) {
this._samplers[hash] = gpuSampler;
}
}
return gpuSampler;
}
}
const VertexBufferKindForNonFloatProcessing = {
[VertexBuffer.PositionKind]: true,
[VertexBuffer.NormalKind]: true,
[VertexBuffer.TangentKind]: true,
[VertexBuffer.UVKind]: true,
[VertexBuffer.UV2Kind]: true,
[VertexBuffer.UV3Kind]: true,
[VertexBuffer.UV4Kind]: true,
[VertexBuffer.UV5Kind]: true,
[VertexBuffer.UV6Kind]: true,
[VertexBuffer.ColorKind]: true,
[VertexBuffer.ColorInstanceKind]: true,
[VertexBuffer.MatricesIndicesKind]: true,
[VertexBuffer.MatricesWeightsKind]: true,
[VertexBuffer.MatricesIndicesExtraKind]: true,
[VertexBuffer.MatricesWeightsExtraKind]: true,
};
/**
* Indicates if the type is a signed or unsigned type
* @param type Type to check
* @returns True if it is a signed type
*/
function IsSignedType(type) {
switch (type) {
case VertexBuffer.BYTE:
case VertexBuffer.SHORT:
case VertexBuffer.INT:
case VertexBuffer.FLOAT:
case VertexBuffer.HALF_FLOAT:
return true;
case VertexBuffer.UNSIGNED_BYTE:
case VertexBuffer.UNSIGNED_SHORT:
case VertexBuffer.UNSIGNED_INT:
return false;
default:
throw new Error(`Invalid type '${type}'`);
}
}
/**
* Checks whether some vertex buffers that should be of type float are of a different type (int, byte...).
* If so, trigger a shader recompilation to give the shader processor the opportunity to update the code accordingly.
* @param vertexBuffers List of vertex buffers to check
* @param effect The effect (shaders) that should be recompiled if needed
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
function checkNonFloatVertexBuffers(vertexBuffers, effect) {
const engine = effect.getEngine();
const pipelineContext = effect._pipelineContext;
if (!pipelineContext?.vertexBufferKindToType) {
return;
}
let shaderProcessingContext = null;
for (const kind in vertexBuffers) {
const currentVertexBuffer = vertexBuffers[kind];
if (!currentVertexBuffer || !VertexBufferKindForNonFloatProcessing[kind]) {
continue;
}
const currentVertexBufferType = currentVertexBuffer.normalized || currentVertexBuffer.type === VertexBuffer.HALF_FLOAT ? VertexBuffer.FLOAT : currentVertexBuffer.type;
const vertexBufferType = pipelineContext.vertexBufferKindToType[kind];
if ((currentVertexBufferType !== VertexBuffer.FLOAT && vertexBufferType === undefined) ||
(vertexBufferType !== undefined && vertexBufferType !== currentVertexBufferType)) {
if (!shaderProcessingContext) {
shaderProcessingContext = engine._getShaderProcessingContext(effect.shaderLanguage, false);
}
pipelineContext.vertexBufferKindToType[kind] = currentVertexBufferType;
if (currentVertexBufferType !== VertexBuffer.FLOAT) {
shaderProcessingContext.vertexBufferKindToNumberOfComponents[kind] = VertexBufferDeduceStride(kind);
if (IsSignedType(currentVertexBufferType)) {
shaderProcessingContext.vertexBufferKindToNumberOfComponents[kind] *= -1;
}
}
}
}
if (shaderProcessingContext) {
// We temporarily disable parallel compilation of shaders because we want new shaders to be compiled after the _processShaderCode call, so that they are in effect for the rest of the frame.
// There is no additional call to async so the _processShaderCodeAsync will execute synchronously.
const parallelShaderCompile = engine._caps.parallelShaderCompile;
engine._caps.parallelShaderCompile = undefined;
// eslint-disable-next-line @typescript-eslint/no-floating-promises
effect._processShaderCodeAsync(null, engine._features._checkNonFloatVertexBuffersDontRecreatePipelineContext, shaderProcessingContext);
engine._caps.parallelShaderCompile = parallelShaderCompile;
}
}
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable babylonjs/available */
/* eslint-disable jsdoc/require-jsdoc */
var StatePosition;
(function (StatePosition) {
StatePosition[StatePosition["StencilReadMask"] = 0] = "StencilReadMask";
StatePosition[StatePosition["StencilWriteMask"] = 1] = "StencilWriteMask";
//DepthBiasClamp = 1, // not used, so remove it to improve perf
StatePosition[StatePosition["DepthBias"] = 2] = "DepthBias";
StatePosition[StatePosition["DepthBiasSlopeScale"] = 3] = "DepthBiasSlopeScale";
StatePosition[StatePosition["DepthStencilState"] = 4] = "DepthStencilState";
StatePosition[StatePosition["MRTAttachments"] = 5] = "MRTAttachments";
StatePosition[StatePosition["RasterizationState"] = 6] = "RasterizationState";
StatePosition[StatePosition["ColorStates1"] = 7] = "ColorStates1";
StatePosition[StatePosition["ColorStates2"] = 8] = "ColorStates2";
StatePosition[StatePosition["ColorStates3"] = 9] = "ColorStates3";
StatePosition[StatePosition["ColorStates4"] = 10] = "ColorStates4";
StatePosition[StatePosition["ShaderStage"] = 11] = "ShaderStage";
StatePosition[StatePosition["TextureStage"] = 12] = "TextureStage";
StatePosition[StatePosition["VertexState"] = 13] = "VertexState";
StatePosition[StatePosition["NumStates"] = 14] = "NumStates";
})(StatePosition || (StatePosition = {}));
const alphaBlendFactorToIndex = {
0: 1, // Zero
1: 2, // One
0x0300: 3, // SrcColor
0x0301: 4, // OneMinusSrcColor
0x0302: 5, // SrcAlpha
0x0303: 6, // OneMinusSrcAlpha
0x0304: 7, // DstAlpha
0x0305: 8, // OneMinusDstAlpha
0x0306: 9, // DstColor
0x0307: 10, // OneMinusDstColor
0x0308: 11, // SrcAlphaSaturated
0x8001: 12, // BlendColor
0x8002: 13, // OneMinusBlendColor
0x8003: 14, // BlendColor (alpha)
0x8004: 15, // OneMinusBlendColor (alpha)
0x88f9: 16, // Src1Color
0x88fa: 17, // OneMinusSrc1Color
0x8589: 18, // Src1Alpha
0x88fb: 19, // OneMinusSrc1Alpha
};
const alphaBlendEquationToIndex = {
0x8006: 0, // Add
0x8007: 1, // Min
0x8008: 2, // Max
0x800a: 3, // Subtract
0x800b: 4, // ReverseSubtract
};
const stencilOpToIndex = {
0x0000: 0, // ZERO
0x1e00: 1, // KEEP
0x1e01: 2, // REPLACE
0x1e02: 3, // INCR
0x1e03: 4, // DECR
0x150a: 5, // INVERT
0x8507: 6, // INCR_WRAP
0x8508: 7, // DECR_WRAP
};
const colorStates = [0, 0, 0, 0];
// VertexBuffer.hashCode uses at most bit 23 for WebGPU's maximum 2048-byte stride.
const vertexBufferLayoutContinuation = 1 << 26;
/** @internal */
class WebGPUCacheRenderPipeline {
constructor(device, emptyVertexBuffer) {
this._depthBiasClamp = 0;
this.mrtTextureCount = 0;
this._device = device;
this._useTextureStage = true; // we force usage because we must handle depth textures with "float" filtering, which can't be fixed by a caps (like "textureFloatLinearFiltering" can for float textures)
// Each attribute can add a format state and a non-zero descriptor offset state.
this._states = new Array(StatePosition.VertexState + 2 * (device.limits.maxVertexAttributes || 16));
this._statesLength = 0;
this._stateDirtyLowestIndex = 0;
this._emptyVertexBuffer = emptyVertexBuffer;
this._mrtFormats = [];
this._parameter = { token: undefined, pipeline: null };
this.disabled = false;
this.vertexBuffers = [];
this._kMaxVertexBufferStride = device.limits.maxVertexBufferArrayStride || 2048;
this.reset();
}
reset() {
this._isDirty = true;
this.vertexBuffers.length = 0;
this.setAlphaToCoverage(false);
this.resetDepthCullingState();
this.setClampDepth(false);
this.setDepthBias(0);
//this.setDepthBiasClamp(0);
this._webgpuColorFormat = ["bgra8unorm" /* WebGPUConstants.TextureFormat.BGRA8Unorm */];
this.setColorFormat("bgra8unorm" /* WebGPUConstants.TextureFormat.BGRA8Unorm */);
this.setMRT([]);
this.setAlphaBlendEnabled([false], 1);
this.setAlphaBlendFactors([null, null, null, null], [null, null]);
this.setWriteMask(0xf);
this.setDepthStencilFormat("depth24plus-stencil8" /* WebGPUConstants.TextureFormat.Depth24PlusStencil8 */);
this.setStencilEnabled(false);
this.resetStencilState();
this.setBuffers(null, null, null);
this._setTextureState(0);
}
get colorFormats() {
return this._mrtAttachments > 0 ? this._mrtFormats : this._webgpuColorFormat;
}
/**
* Performs the cache state setup and lookup for a render pipeline.
* @param fillMode defines the fill mode used to configure the rasterization state
* @param effect defines the effect containing the shader and vertex input layout
* @param sampleCount defines the number of samples to use for MSAA
* @param textureState defines the encoded texture state used for the pipeline cache key
* @returns the cached pipeline on hit, or null on miss (with cache state ready for creation)
*/
_lookupRenderPipeline(fillMode, effect, sampleCount, textureState) {
this._setShaderStage(effect.uniqueId);
this._setRasterizationState(fillMode, sampleCount);
this._setColorStates();
this._setDepthStencilState();
this._setVertexState(effect);
this._setTextureState(textureState);
this.lastStateDirtyLowestIndex = this._stateDirtyLowestIndex;
if (!this._isDirty && this._parameter.pipeline) {
this._stateDirtyLowestIndex = this._statesLength;
WebGPUCacheRenderPipeline.NumCacheHitWithoutHash++;
return this._parameter.pipeline;
}
this._getRenderPipeline(this._parameter);
this._isDirty = false;
this._stateDirtyLowestIndex = this._statesLength;
if (this._parameter.pipeline) {
WebGPUCacheRenderPipeline.NumCacheHitWithHash++;
return this._parameter.pipeline;
}
return null;
}
getRenderPipeline(fillMode, effect, sampleCount, textureState = 0) {
sampleCount = WebGPUTextureHelper.GetSample(sampleCount);
if (this.disabled) {
const topology = WebGPUCacheRenderPipeline._GetTopology(fillMode);
this._setVertexState(effect); // to fill this.vertexBuffers with correct data
this._setTextureState(textureState);
this._parameter.pipeline = this._createRenderPipeline(effect, topology, sampleCount);
WebGPUCacheRenderPipeline.NumCacheMiss++;
WebGPUCacheRenderPipeline._NumPipelineCreationCurrentFrame++;
return this._parameter.pipeline;
}
const cached = this._lookupRenderPipeline(fillMode, effect, sampleCount, textureState);
if (cached) {
return cached;
}
const topology = WebGPUCacheRenderPipeline._GetTopology(fillMode);
this._parameter.pipeline = this._createRenderPipeline(effect, topology, sampleCount);
this._setRenderPipeline(this._parameter);
WebGPUCacheRenderPipeline.NumCacheMiss++;
WebGPUCacheRenderPipeline._NumPipelineCreationCurrentFrame++;
return this._parameter.pipeline;
}
endFrame() {
WebGPUCacheRenderPipeline.NumPipelineCreationLastFrame = WebGPUCacheRenderPipeline._NumPipelineCreationCurrentFrame;
WebGPUCacheRenderPipeline._NumPipelineCreationCurrentFrame = 0;
}
setAlphaToCoverage(enabled) {
this._alphaToCoverageEnabled = enabled;
}
setFrontFace(frontFace) {
this._frontFace = frontFace;
}
setCullEnabled(enabled) {
this._cullEnabled = enabled;
}
setCullFace(cullFace) {
this._cullFace = cullFace;
}
setClampDepth(clampDepth) {
this._clampDepth = clampDepth;
}
resetDepthCullingState() {
this.setDepthCullingState(false, 2, 1, 0, 0, true, true, Constants.ALWAYS);
}
setDepthCullingState(cullEnabled, frontFace, cullFace, zOffset, zOffsetUnits, depthTestEnabled, depthWriteEnabled, depthCompare) {
this._depthWriteEnabled = depthWriteEnabled;
this._depthTestEnabled = depthTestEnabled;
this._depthCompare = (depthCompare ?? Constants.ALWAYS) - 0x0200;
this._cullFace = cullFace;
this._cullEnabled = cullEnabled;
this._frontFace = frontFace;
this.setDepthBiasSlopeScale(zOffset);
this.setDepthBias(zOffsetUnits);
}
setDepthBias(depthBias) {
if (this._depthBias !== depthBias) {
this._depthBias = depthBias;
this._states[StatePosition.DepthBias] = depthBias;
this._isDirty = true;
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.DepthBias);
}
}
/*public setDepthBiasClamp(depthBiasClamp: number): void {
if (this._depthBiasClamp !== depthBiasClamp) {
this._depthBiasClamp = depthBiasClamp;
this._states[StatePosition.DepthBiasClamp] = depthBiasClamp.toString();
this._isDirty = true;
}
}*/
setDepthBiasSlopeScale(depthBiasSlopeScale) {
if (this._depthBiasSlopeScale !== depthBiasSlopeScale) {
this._depthBiasSlopeScale = depthBiasSlopeScale;
this._states[StatePosition.DepthBiasSlopeScale] = depthBiasSlopeScale;
this._isDirty = true;
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.DepthBiasSlopeScale);
}
}
setColorFormat(format) {
this._webgpuColorFormat[0] = format;
this._colorFormat = renderableTextureFormatToIndex[format ?? ""];
}
// Must be called after setMRT!
setMRTAttachments(attachments) {
this.mrtAttachments = attachments;
let mask = 0;
for (let i = 0; i < attachments.length; ++i) {
if (attachments[i] !== 0) {
mask += 1 << i;
}
}
if (this._mrtEnabledMask !== mask) {
this._mrtEnabledMask = mask;
this._isDirty = true;
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.MRTAttachments);
}
}
setMRT(textureArray, textureCount) {
textureCount = textureCount ?? textureArray.length;
if (textureCount > 8) {
// We only support 8 MRTs in WebGPU, so we throw an error if we try to set more than that.
throw new Error("Can't handle more than 8 attachments for a MRT in cache render pipeline!");
}
this.mrtTextureArray = textureArray;
this.mrtTextureCount = textureCount;
// Since we need approximately 45 different values per texture format (see WebGPUTextureManager.renderableTextureFormatToIndex), we use 6 bits to encode a texture format,
// which means we can encode 8 texture formats in 48 bits (a double can represent integers exactly up until 2^53, so 48 bits is ok).
this._mrtEnabledMask = 0xffff; // all textures are enabled at start (meaning we can write to them). Calls to setMRTAttachments may disable some
let mrtAttachments = 0;
let mask = 0;
for (let i = 0; i < textureCount; ++i) {
const texture = textureArray[i];
const gpuWrapper = texture?._hardwareTexture;
this._mrtFormats[i] = gpuWrapper?.format ?? this._webgpuColorFormat[0];
mrtAttachments += renderableTextureFormatToIndex[this._mrtFormats[i] ?? ""] * 2 ** mask;
mask += 6;
}
this._mrtFormats.length = textureCount;
if (this._mrtAttachments !== mrtAttachments) {
this._mrtAttachments = mrtAttachments;
this._states[StatePosition.MRTAttachments] = mrtAttachments;
this._isDirty = true;
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.MRTAttachments);
}
}
setAlphaBlendEnabled(enabled, numAlphaBlendTargetsEnabled) {
this._alphaBlendEnabled = enabled;
this._numAlphaBlendTargetsEnabled = numAlphaBlendTargetsEnabled;
}
setAlphaBlendFactors(factors, operations) {
this._alphaBlendFuncParams = factors;
this._alphaBlendEqParams = operations;
}
setWriteMask(mask) {
this._writeMask = mask;
}
setDepthStencilFormat(format) {
this._webgpuDepthStencilFormat = format;
this._depthStencilFormat = format === undefined ? 0 : renderableTextureFormatToIndex[format];
}
setDepthTestEnabled(enabled) {
this._depthTestEnabled = enabled;
}
setDepthWriteEnabled(enabled) {
this._depthWriteEnabled = enabled;
}
setDepthCompare(func) {
this._depthCompare = (func ?? Constants.ALWAYS) - 0x0200;
}
setStencilEnabled(enabled) {
this._stencilEnabled = enabled;
}
setStencilCompare(func) {
this._stencilFrontCompare = (func ?? Constants.ALWAYS) - 0x0200;
}
setStencilDepthFailOp(op) {
this._stencilFrontDepthFailOp = op === null ? 1 /* KEEP */ : stencilOpToIndex[op];
}
setStencilPassOp(op) {
this._stencilFrontPassOp = op === null ? 2 /* REPLACE */ : stencilOpToIndex[op];
}
setStencilFailOp(op) {
this._stencilFrontFailOp = op === null ? 1 /* KEEP */ : stencilOpToIndex[op];
}
setStencilBackCompare(func) {
this._stencilBackCompare = (func ?? Constants.ALWAYS) - 0x0200;
}
setStencilBackDepthFailOp(op) {
this._stencilBackDepthFailOp = op === null ? 1 /* KEEP */ : stencilOpToIndex[op];
}
setStencilBackPassOp(op) {
this._stencilBackPassOp = op === null ? 2 /* REPLACE */ : stencilOpToIndex[op];
}
setStencilBackFailOp(op) {
this._stencilBackFailOp = op === null ? 1 /* KEEP */ : stencilOpToIndex[op];
}
setStencilReadMask(mask) {
if (this._stencilReadMask !== mask) {
this._stencilReadMask = mask;
this._states[StatePosition.StencilReadMask] = mask;
this._isDirty = true;
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.StencilReadMask);
}
}
setStencilWriteMask(mask) {
if (this._stencilWriteMask !== mask) {
this._stencilWriteMask = mask;
this._states[StatePosition.StencilWriteMask] = mask;
this._isDirty = true;
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.StencilWriteMask);
}
}
resetStencilState() {
this.setStencilState(false, Constants.ALWAYS, Constants.KEEP, Constants.REPLACE, Constants.KEEP, 0xff, 0xff);
}
setStencilState(stencilEnabled, compare, depthFailOp, passOp, failOp, readMask, writeMask, backCompare = null, backDepthFailOp = null, backPassOp = null, backFailOp = null) {
this._stencilEnabled = stencilEnabled;
this._stencilFrontCompare = (compare ?? Constants.ALWAYS) - 0x0200;
this._stencilFrontDepthFailOp = depthFailOp === null ? 1 /* KEEP */ : stencilOpToIndex[depthFailOp];
this._stencilFrontPassOp = passOp === null ? 2 /* REPLACE */ : stencilOpToIndex[passOp];
this._stencilFrontFailOp = failOp === null ? 1 /* KEEP */ : stencilOpToIndex[failOp];
this._stencilBackCompare = (backCompare ?? Constants.ALWAYS) - 0x0200;
this._stencilBackDepthFailOp = backDepthFailOp === null ? 1 /* KEEP */ : stencilOpToIndex[backDepthFailOp];
this._stencilBackPassOp = backPassOp === null ? 2 /* REPLACE */ : stencilOpToIndex[backPassOp];
this._stencilBackFailOp = backFailOp === null ? 1 /* KEEP */ : stencilOpToIndex[backFailOp];
this.setStencilReadMask(readMask);
this.setStencilWriteMask(writeMask);
}
setBuffers(vertexBuffers, indexBuffer, overrideVertexBuffers) {
this._vertexBuffers = vertexBuffers;
this._overrideVertexBuffers = overrideVertexBuffers;
this.indexBuffer = indexBuffer;
}
static _GetTopology(fillMode) {
switch (fillMode) {
// Triangle views
case Constants.MATERIAL_TriangleFillMode:
return "triangle-list" /* WebGPUConstants.PrimitiveTopology.TriangleList */;
case Constants.MATERIAL_PointFillMode:
return "point-list" /* WebGPUConstants.PrimitiveTopology.PointList */;
case Constants.MATERIAL_WireFrameFillMode:
return "line-list" /* WebGPUConstants.PrimitiveTopology.LineList */;
// Draw modes
case Constants.MATERIAL_PointListDrawMode:
return "point-list" /* WebGPUConstants.PrimitiveTopology.PointList */;
case Constants.MATERIAL_LineListDrawMode:
return "line-list" /* WebGPUConstants.PrimitiveTopology.LineList */;
case Constants.MATERIAL_LineLoopDrawMode:
// return this._gl.LINE_LOOP;
// TODO WEBGPU. Line Loop Mode Fallback at buffer load time.
// eslint-disable-next-line no-throw-literal
throw "LineLoop is an unsupported fillmode in WebGPU";
case Constants.MATERIAL_LineStripDrawMode:
return "line-strip" /* WebGPUConstants.PrimitiveTopology.LineStrip */;
case Constants.MATERIAL_TriangleStripDrawMode:
return "triangle-strip" /* WebGPUConstants.PrimitiveTopology.TriangleStrip */;
case Constants.MATERIAL_TriangleFanDrawMode:
// return this._gl.TRIANGLE_FAN;
// TODO WEBGPU. Triangle Fan Mode Fallback at buffer load time.
// eslint-disable-next-line no-throw-literal
throw "TriangleFan is an unsupported fillmode in WebGPU";
default:
return "triangle-list" /* WebGPUConstants.PrimitiveTopology.TriangleList */;
}
}
static _GetAphaBlendOperation(operation) {
switch (operation) {
case Constants.GL_ALPHA_EQUATION_ADD:
return "add" /* WebGPUConstants.BlendOperation.Add */;
case Constants.GL_ALPHA_EQUATION_SUBTRACT:
return "subtract" /* WebGPUConstants.BlendOperation.Subtract */;
case Constants.GL_ALPHA_EQUATION_REVERSE_SUBTRACT:
return "reverse-subtract" /* WebGPUConstants.BlendOperation.ReverseSubtract */;
case Constants.GL_ALPHA_EQUATION_MIN:
return "min" /* WebGPUConstants.BlendOperation.Min */;
case Constants.GL_ALPHA_EQUATION_MAX:
return "max" /* WebGPUConstants.BlendOperation.Max */;
default:
return "add" /* WebGPUConstants.BlendOperation.Add */;
}
}
static _GetAphaBlendFactor(factor) {
switch (factor) {
case 0:
return "zero" /* WebGPUConstants.BlendFactor.Zero */;
case 1:
return "one" /* WebGPUConstants.BlendFactor.One */;
case Constants.GL_ALPHA_FUNCTION_SRC:
return "src" /* WebGPUConstants.BlendFactor.Src */;
case Constants.GL_ALPHA_FUNCTION_ONE_MINUS_SRC_COLOR:
return "one-minus-src" /* WebGPUConstants.BlendFactor.OneMinusSrc */;
case Constants.GL_ALPHA_FUNCTION_SRC_ALPHA:
return "src-alpha" /* WebGPUConstants.BlendFactor.SrcAlpha */;
case Constants.GL_ALPHA_FUNCTION_ONE_MINUS_SRC_ALPHA:
return "one-minus-src-alpha" /* WebGPUConstants.BlendFactor.OneMinusSrcAlpha */;
case Constants.GL_ALPHA_FUNCTION_DST_ALPHA:
return "dst-alpha" /* WebGPUConstants.BlendFactor.DstAlpha */;
case Constants.GL_ALPHA_FUNCTION_ONE_MINUS_DST_ALPHA:
return "one-minus-dst-alpha" /* WebGPUConstants.BlendFactor.OneMinusDstAlpha */;
case Constants.GL_ALPHA_FUNCTION_DST_COLOR:
return "dst" /* WebGPUConstants.BlendFactor.Dst */;
case Constants.GL_ALPHA_FUNCTION_ONE_MINUS_DST_COLOR:
return "one-minus-dst" /* WebGPUConstants.BlendFactor.OneMinusDst */;
case Constants.GL_ALPHA_FUNCTION_SRC_ALPHA_SATURATED:
return "src-alpha-saturated" /* WebGPUConstants.BlendFactor.SrcAlphaSaturated */;
case Constants.GL_ALPHA_FUNCTION_CONSTANT_COLOR:
case Constants.GL_ALPHA_FUNCTION_CONSTANT_ALPHA:
return "constant" /* WebGPUConstants.BlendFactor.Constant */;
case Constants.GL_ALPHA_FUNCTION_ONE_MINUS_CONSTANT_COLOR:
case Constants.GL_ALPHA_FUNCTION_ONE_MINUS_CONSTANT_ALPHA:
return "one-minus-constant" /* WebGPUConstants.BlendFactor.OneMinusConstant */;
case Constants.GL_ALPHA_FUNCTION_SRC1_COLOR:
return "src1" /* WebGPUConstants.BlendFactor.Src1 */;
case Constants.GL_ALPHA_FUNCTION_ONE_MINUS_SRC1_COLOR:
return "one-minus-src1" /* WebGPUConstants.BlendFactor.OneMinusSrc1 */;
case Constants.GL_ALPHA_FUNCTION_SRC1_ALPHA:
return "src1-alpha" /* WebGPUConstants.BlendFactor.Src1Alpha */;
case Constants.GL_ALPHA_FUNCTION_ONE_MINUS_SRC1_ALPHA:
return "one-minus-src1-alpha" /* WebGPUConstants.BlendFactor.OneMinusSrc1Alpha */;
default:
return "one" /* WebGPUConstants.BlendFactor.One */;
}
}
static _GetCompareFunction(compareFunction) {
switch (compareFunction) {
case 0: // NEVER
return "never" /* WebGPUConstants.CompareFunction.Never */;
case 1: // LESS
return "less" /* WebGPUConstants.CompareFunction.Less */;
case 2: // EQUAL
return "equal" /* WebGPUConstants.CompareFunction.Equal */;
case 3: // LEQUAL
return "less-equal" /* WebGPUConstants.CompareFunction.LessEqual */;
case 4: // GREATER
return "greater" /* WebGPUConstants.CompareFunction.Greater */;
case 5: // NOTEQUAL
return "not-equal" /* WebGPUConstants.CompareFunction.NotEqual */;
case 6: // GEQUAL
return "greater-equal" /* WebGPUConstants.CompareFunction.GreaterEqual */;
case 7: // ALWAYS
return "always" /* WebGPUConstants.CompareFunction.Always */;
}
return "never" /* WebGPUConstants.CompareFunction.Never */;
}
static _GetStencilOpFunction(operation) {
switch (operation) {
case 0:
return "zero" /* WebGPUConstants.StencilOperation.Zero */;
case 1:
return "keep" /* WebGPUConstants.StencilOperation.Keep */;
case 2:
return "replace" /* WebGPUConstants.StencilOperation.Replace */;
case 3:
return "increment-clamp" /* WebGPUConstants.StencilOperation.IncrementClamp */;
case 4:
return "decrement-clamp" /* WebGPUConstants.StencilOperation.DecrementClamp */;
case 5:
return "invert" /* WebGPUConstants.StencilOperation.Invert */;
case 6:
return "increment-wrap" /* WebGPUConstants.StencilOperation.IncrementWrap */;
case 7:
return "decrement-wrap" /* WebGPUConstants.StencilOperation.DecrementWrap */;
}
return "keep" /* WebGPUConstants.StencilOperation.Keep */;
}
static _GetVertexInputDescriptorFormat(vertexBuffer) {
const type = vertexBuffer.type;
const normalized = vertexBuffer.normalized;
const size = vertexBuffer.getSize();
switch (type) {
case VertexBuffer.BYTE:
switch (size) {
case 1:
case 2:
return normalized ? "snorm8x2" /* WebGPUConstants.VertexFormat.Snorm8x2 */ : "sint8x2" /* WebGPUConstants.VertexFormat.Sint8x2 */;
case 3:
case 4:
return normalized ? "snorm8x4" /* WebGPUConstants.VertexFormat.Snorm8x4 */ : "sint8x4" /* WebGPUConstants.VertexFormat.Sint8x4 */;
}
break;
case VertexBuffer.UNSIGNED_BYTE:
switch (size) {
case 1:
case 2:
return normalized ? "unorm8x2" /* WebGPUConstants.VertexFormat.Unorm8x2 */ : "uint8x2" /* WebGPUConstants.VertexFormat.Uint8x2 */;
case 3:
case 4:
return normalized ? "unorm8x4" /* WebGPUConstants.VertexFormat.Unorm8x4 */ : "uint8x4" /* WebGPUConstants.VertexFormat.Uint8x4 */;
}
break;
case VertexBuffer.SHORT:
switch (size) {
case 1:
case 2:
return normalized ? "snorm16x2" /* WebGPUConstants.VertexFormat.Snorm16x2 */ : "sint16x2" /* WebGPUConstants.VertexFormat.Sint16x2 */;
case 3:
case 4:
return normalized ? "snorm16x4" /* WebGPUConstants.VertexFormat.Snorm16x4 */ : "sint16x4" /* WebGPUConstants.VertexFormat.Sint16x4 */;
}
break;
case VertexBuffer.UNSIGNED_SHORT:
switch (size) {
case 1:
case 2:
return normalized ? "unorm16x2" /* WebGPUConstants.VertexFormat.Unorm16x2 */ : "uint16x2" /* WebGPUConstants.VertexFormat.Uint16x2 */;
case 3:
case 4:
return normalized ? "unorm16x4" /* WebGPUConstants.VertexFormat.Unorm16x4 */ : "uint16x4" /* WebGPUConstants.VertexFormat.Uint16x4 */;
}
break;
case VertexBuffer.HALF_FLOAT:
switch (size) {
case 1:
case 2:
return "float16x2" /* WebGPUConstants.VertexFormat.Float16x2 */;
case 3:
case 4:
return "float16x4" /* WebGPUConstants.VertexFormat.Float16x4 */;
}
break;
case VertexBuffer.INT:
switch (size) {
case 1:
return "sint32" /* WebGPUConstants.VertexFormat.Sint32 */;
case 2:
return "sint32x2" /* WebGPUConstants.VertexFormat.Sint32x2 */;
case 3:
return "sint32x3" /* WebGPUConstants.VertexFormat.Sint32x3 */;
case 4:
return "sint32x4" /* WebGPUConstants.VertexFormat.Sint32x4 */;
}
break;
case VertexBuffer.UNSIGNED_INT:
switch (size) {
case 1:
return "uint32" /* WebGPUConstants.VertexFormat.Uint32 */;
case 2:
return "uint32x2" /* WebGPUConstants.VertexFormat.Uint32x2 */;
case 3:
return "uint32x3" /* WebGPUConstants.VertexFormat.Uint32x3 */;
case 4:
return "uint32x4" /* WebGPUConstants.VertexFormat.Uint32x4 */;
}
break;
case VertexBuffer.FLOAT:
switch (size) {
case 1:
return "float32" /* WebGPUConstants.VertexFormat.Float32 */;
case 2:
return "float32x2" /* WebGPUConstants.VertexFormat.Float32x2 */;
case 3:
return "float32x3" /* WebGPUConstants.VertexFormat.Float32x3 */;
case 4:
return "float32x4" /* WebGPUConstants.VertexFormat.Float32x4 */;
}
break;
}
throw new Error(`Invalid Format '${vertexBuffer.getKind()}' - type=${type}, normalized=${normalized}, size=${size}`);
}
_getAphaBlendState(targetIndex) {
if (!this._alphaBlendEnabled[targetIndex]) {
return null;
}
return {
srcFactor: WebGPUCacheRenderPipeline._GetAphaBlendFactor(this._alphaBlendFuncParams[targetIndex * 4 + 2]),
dstFactor: WebGPUCacheRenderPipeline._GetAphaBlendFactor(this._alphaBlendFuncParams[targetIndex * 4 + 3]),
operation: WebGPUCacheRenderPipeline._GetAphaBlendOperation(this._alphaBlendEqParams[targetIndex * 2 + 1]),
};
}
_getColorBlendState(targetIndex) {
if (!this._alphaBlendEnabled) {
return null;
}
return {
srcFactor: WebGPUCacheRenderPipeline._GetAphaBlendFactor(this._alphaBlendFuncParams[targetIndex * 4 + 0]),
dstFactor: WebGPUCacheRenderPipeline._GetAphaBlendFactor(this._alphaBlendFuncParams[targetIndex * 4 + 1]),
operation: WebGPUCacheRenderPipeline._GetAphaBlendOperation(this._alphaBlendEqParams[targetIndex * 2 + 0]),
};
}
_setShaderStage(id) {
if (this._shaderId !== id) {
this._shaderId = id;
this._states[StatePosition.ShaderStage] = id;
this._isDirty = true;
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.ShaderStage);
}
}
_setRasterizationState(topology, sampleCount) {
const frontFace = this._frontFace;
const cullMode = this._cullEnabled ? this._cullFace : 0;
const clampDepth = this._clampDepth ? 1 : 0;
const alphaToCoverage = this._alphaToCoverageEnabled && sampleCount > 1 ? 1 : 0;
const rasterizationState = frontFace - 1 + (cullMode << 1) + (clampDepth << 3) + (alphaToCoverage << 4) + (topology << 5) + (sampleCount << 8);
if (this._rasterizationState !== rasterizationState) {
this._rasterizationState = rasterizationState;
this._states[StatePosition.RasterizationState] = this._rasterizationState;
this._isDirty = true;
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.RasterizationState);
}
}
_setColorStates() {
// Note that _depthWriteEnabled state has been moved from depthStencilState here because alpha and depth are related (generally when alpha is on, depth write is off and the other way around)
// We need 4 color states because we will be grouping 2 blend targets in each state (and WebGPU supports up to 8 targets).
// Integers can only be represented exactly in 53 bits with a double, so we can only use 53 bits for each state.
// We use 25 bits for each blend target (5 bits for the 2 (color/alpha) equations and 4*5 bits for the 4 factors (src/dst color and src/dst alpha)).
// This means that we need 25*2=50 bits to pack 2 blend targets, and we can use the remaining 3 bits for other states (write mask, depth write, color format).
// The color format is encoded on 6 bits, so we dispatch it over 3 bits to the last two color states.
colorStates[0] = (this._writeMask ? 1 : 0) * 2 ** 53;
colorStates[1] = (this._depthWriteEnabled ? 1 : 0) * 2 ** 53;
colorStates[2] = (this._colorFormat & 0x07) * 2 ** 50;
colorStates[3] = (this._colorFormat & 0x38) * 2 ** 47;
let colorStateIndex = 0;
let isDirty = false;
for (let i = 0; i < 8; i++) {
if (this._alphaBlendEnabled[i]) {
const index0 = i * 4 + 0;
const index1 = i * 4 + 1;
const index2 = i * 4 + 2;
const index3 = i * 4 + 3;
const indexEq0 = i * 2 + 0;
const indexEq1 = i * 2 + 1;
const eq0 = this._alphaBlendEqParams[indexEq0] === null ? 0 : alphaBlendEquationToIndex[this._alphaBlendEqParams[indexEq0]];
const eq1 = this._alphaBlendEqParams[indexEq1] === null ? 0 : alphaBlendEquationToIndex[this._alphaBlendEqParams[indexEq1]];
colorStates[colorStateIndex] +=
((this._alphaBlendFuncParams[index0] === null ? 1 : alphaBlendFactorToIndex[this._alphaBlendFuncParams[index0]]) << 0) +
((this._alphaBlendFuncParams[index1] === null ? 1 : alphaBlendFactorToIndex[this._alphaBlendFuncParams[index1]]) << 5) +
((this._alphaBlendFuncParams[index2] === null ? 1 : alphaBlendFactorToIndex[this._alphaBlendFuncParams[index2]]) << 10) +
((this._alphaBlendFuncParams[index3] === null ? 1 : alphaBlendFactorToIndex[this._alphaBlendFuncParams[index3]]) << 15) +
(eq0 + eq1 * 5) * (1 << 20);
}
if (i & 1) {
isDirty = isDirty || this._states[StatePosition.ColorStates1 + colorStateIndex] !== colorStates[colorStateIndex];
this._states[StatePosition.ColorStates1 + colorStateIndex] = colorStates[colorStateIndex];
colorStateIndex++;
}
}
if (isDirty) {
this._isDirty = true;
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.ColorStates1);
}
}
_setDepthStencilState() {
const stencilState = !this._stencilEnabled
? 7 /* ALWAYS */ +
(1 /* KEEP */ << 3) +
(1 /* KEEP */ << 6) +
(1 /* KEEP */ << 9) + // front
(7 /* ALWAYS */ << 12) +
(1 /* KEEP */ << 15) +
(1 /* KEEP */ << 18) +
(1 /* KEEP */ << 21) // back
: this._stencilFrontCompare +
(this._stencilFrontDepthFailOp << 3) +
(this._stencilFrontPassOp << 6) +
(this._stencilFrontFailOp << 9) + // front
(this._stencilBackCompare << 12) +
(this._stencilBackDepthFailOp << 15) +
(this._stencilBackPassOp << 18) +
(this._stencilBackFailOp << 21); // back
const depthStencilState = this._depthStencilFormat + ((this._depthTestEnabled ? this._depthCompare : 7) /* ALWAYS */ << 6) + stencilState * (1 << 10); // stencil front + back
if (this._depthStencilState !== depthStencilState) {
this._depthStencilState = depthStencilState;
this._states[StatePosition.DepthStencilState] = this._depthStencilState;
this._isDirty = true;
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.DepthStencilState);
}
}
_setVertexState(effect) {
const currStateLen = this._statesLength;
let newNumStates = StatePosition.VertexState;
const webgpuPipelineContext = effect._pipelineContext;
const attributes = webgpuPipelineContext.shaderProcessingContext.attributeNamesFromEffect;
const locations = webgpuPipelineContext.shaderProcessingContext.attributeLocationsFromEffect;
let currentGPUBuffer;
let numVertexBuffers = 0;
for (let index = 0; index < attributes.length; index++) {
const location = locations[index];
let vertexBuffer = (this._overrideVertexBuffers && this._overrideVertexBuffers[attributes[index]]) ?? this._vertexBuffers[attributes[index]];
if (!vertexBuffer) {
// In WebGL it's valid to not bind a vertex buffer to an attribute, but it's not valid in WebGPU
// So we must bind a dummy buffer when we are not given one for a specific attribute
vertexBuffer = this._emptyVertexBuffer;
if (WebGPUCacheRenderPipeline.LogErrorIfNoVertexBuffer) {
Logger.Error(`No vertex buffer is provided for the "${attributes[index]}" attribute. A default empty vertex buffer will be used, but this may generate errors in some browsers.`);
}
}
const buffer = vertexBuffer.effectiveBuffer?.underlyingResource;
// We optimize usage of GPUVertexBufferLayout: we will create a single GPUVertexBufferLayout for all the attributes which follow each other and which use the same GPU buffer
// However, there are some constraints in the attribute.offset value range, so we must check for them before being able to reuse the same GPUVertexBufferLayout
// See _getVertexInputDescriptor() below
if (vertexBuffer._validOffsetRange === undefined) {
const offset = vertexBuffer.effectiveByteOffset;
const formatSize = vertexBuffer.getSize(true);
const byteStride = vertexBuffer.effectiveByteStride;
vertexBuffer._validOffsetRange =
(offset + formatSize <= this._kMaxVertexBufferStride && byteStride === 0) || (byteStride !== 0 && offset + formatSize <= byteStride);
}
const canMergeWithCurrentBuffer = WebGPUCacheRenderPipeline._CanMergeVertexBuffer(currentGPUBuffer, buffer, vertexBuffer);
if (!canMergeWithCurrentBuffer) {
// we can't combine the previous vertexBuffer with the current one
this.vertexBuffers[numVertexBuffers++] = vertexBuffer;
currentGPUBuffer = vertexBuffer._validOffsetRange ? buffer : null;
}
const vid = vertexBuffer.hashCode + (location << 7) + (canMergeWithCurrentBuffer ? vertexBufferLayoutContinuation : 0);
const descriptorOffset = vertexBuffer._validOffsetRange ? vertexBuffer.effectiveByteOffset : 0;
this._isDirty = this._isDirty || this._states[newNumStates] !== vid;
this._states[newNumStates++] = vid;
if (descriptorOffset !== 0) {
// Offset states are negative so they cannot collide with vertex format states.
const offsetState = -descriptorOffset;
this._isDirty = this._isDirty || this._states[newNumStates] !== offsetState;
this._states[newNumStates++] = offsetState;
}
}
this.vertexBuffers.length = numVertexBuffers;
this._statesLength = newNumStates;
this._isDirty = this._isDirty || newNumStates !== currStateLen;
if (this._isDirty) {
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.VertexState);
}
}
_setTextureState(textureState) {
if (this._textureState !== textureState) {
this._textureState = textureState;
this._states[StatePosition.TextureStage] = this._textureState;
this._isDirty = true;
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.TextureStage);
}
}
_createPipelineLayout(webgpuPipelineContext) {
if (this._useTextureStage) {
return this._createPipelineLayoutWithTextureStage(webgpuPipelineContext);
}
const bindGroupLayouts = [];
const bindGroupLayoutEntries = webgpuPipelineContext.shaderProcessingContext.bindGroupLayoutEntries;
for (let i = 0; i < bindGroupLayoutEntries.length; i++) {
const setDefinition = bindGroupLayoutEntries[i];
bindGroupLayouts[i] = this._device.createBindGroupLayout({
entries: setDefinition,
});
}
webgpuPipelineContext.bindGroupLayouts[0] = bindGroupLayouts;
return this._device.createPipelineLayout({ bindGroupLayouts });
}
_createPipelineLayoutWithTextureStage(webgpuPipelineContext) {
const shaderProcessingContext = webgpuPipelineContext.shaderProcessingContext;
const bindGroupLayoutEntries = shaderProcessingContext.bindGroupLayoutEntries;
let bitVal = 1;
for (let i = 0; i < bindGroupLayoutEntries.length; i++) {
const setDefinition = bindGroupLayoutEntries[i];
for (let j = 0; j < setDefinition.length; j++) {
const entry = bindGroupLayoutEntries[i][j];
if (entry.texture) {
const name = shaderProcessingContext.bindGroupLayoutEntryInfo[i][entry.binding].name;
const textureInfo = shaderProcessingContext.availableTextures[name];
const samplerInfo = textureInfo.autoBindSampler ? shaderProcessingContext.availableSamplers[name + Constants.AUTOSAMPLERSUFFIX] : null;
let sampleType = textureInfo.sampleType;
let samplerType = samplerInfo?.type ?? "filtering" /* WebGPUConstants.SamplerBindingType.Filtering */;
if (this._textureState & bitVal && sampleType !== "depth" /* WebGPUConstants.TextureSampleType.Depth */) {
// The texture is a 32 bits float texture but the system does not support linear filtering for them OR the texture is a depth texture with "float" filtering:
// we set the sampler to "non-filtering" and the texture sample type to "unfilterable-float"
if (textureInfo.autoBindSampler) {
samplerType = "non-filtering" /* WebGPUConstants.SamplerBindingType.NonFiltering */;
}
sampleType = "unfilterable-float" /* WebGPUConstants.TextureSampleType.UnfilterableFloat */;
}
entry.texture.sampleType = sampleType;
if (samplerInfo) {
const binding = shaderProcessingContext.bindGroupLayoutEntryInfo[samplerInfo.binding.groupIndex][samplerInfo.binding.bindingIndex].index;
bindGroupLayoutEntries[samplerInfo.binding.groupIndex][binding].sampler.type = samplerType;
}
bitVal = bitVal << 1;
}
}
}
const bindGroupLayouts = [];
for (let i = 0; i < bindGroupLayoutEntries.length; ++i) {
bindGroupLayouts[i] = this._device.createBindGroupLayout({
entries: bindGroupLayoutEntries[i],
});
}
webgpuPipelineContext.bindGroupLayouts[this._textureState] = bindGroupLayouts;
return this._device.createPipelineLayout({ bindGroupLayouts });
}
_getVertexInputDescriptor(effect) {
const descriptors = [];
const webgpuPipelineContext = effect._pipelineContext;
const attributes = webgpuPipelineContext.shaderProcessingContext.attributeNamesFromEffect;
const locations = webgpuPipelineContext.shaderProcessingContext.attributeLocationsFromEffect;
let currentGPUBuffer;
let currentGPUAttributes;
for (let index = 0; index < attributes.length; index++) {
const location = locations[index];
let vertexBuffer = (this._overrideVertexBuffers && this._overrideVertexBuffers[attributes[index]]) ?? this._vertexBuffers[attributes[index]];
if (!vertexBuffer) {
// In WebGL it's valid to not bind a vertex buffer to an attribute, but it's not valid in WebGPU
// So we must bind a dummy buffer when we are not given one for a specific attribute
vertexBuffer = this._emptyVertexBuffer;
}
let buffer = vertexBuffer.effectiveBuffer?.underlyingResource;
// We reuse the same GPUVertexBufferLayout for all attributes that use the same underlying GPU buffer (and for attributes that follow each other in the attributes array)
let offset = vertexBuffer.effectiveByteOffset;
const invalidOffsetRange = !vertexBuffer._validOffsetRange;
if (!WebGPUCacheRenderPipeline._CanMergeVertexBuffer(currentGPUBuffer, buffer, vertexBuffer)) {
const vertexBufferDescriptor = {
arrayStride: vertexBuffer.effectiveByteStride,
stepMode: vertexBuffer.getIsInstanced() ? "instance" /* WebGPUConstants.VertexStepMode.Instance */ : "vertex" /* WebGPUConstants.VertexStepMode.Vertex */,
attributes: [],
};
descriptors.push(vertexBufferDescriptor);
currentGPUAttributes = vertexBufferDescriptor.attributes;
if (invalidOffsetRange) {
offset = 0; // the offset will be set directly in the setVertexBuffer call
buffer = null; // buffer can't be reused
}
}
currentGPUAttributes.push({
shaderLocation: location,
offset,
format: WebGPUCacheRenderPipeline._GetVertexInputDescriptorFormat(vertexBuffer),
});
currentGPUBuffer = buffer;
}
return descriptors;
}
static _CanMergeVertexBuffer(currentGPUBuffer, buffer, vertexBuffer) {
return !!currentGPUBuffer && currentGPUBuffer === buffer && vertexBuffer._validOffsetRange;
}
_buildRenderPipelineDescriptor(effect, topology, sampleCount) {
const webgpuPipelineContext = effect._pipelineContext;
const inputStateDescriptor = this._getVertexInputDescriptor(effect);
const pipelineLayout = this._createPipelineLayout(webgpuPipelineContext);
const colorStates = [];
if (this._vertexBuffers) {
checkNonFloatVertexBuffers(this._vertexBuffers, effect);
}
if (this._mrtAttachments > 0) {
for (let i = 0; i < this._mrtFormats.length; ++i) {
const format = this._mrtFormats[i];
if (format) {
const descr = {
format,
writeMask: (this._mrtEnabledMask & (1 << i)) !== 0 ? this._writeMask : 0,
};
const alphaBlend = this._getAphaBlendState(i < this._numAlphaBlendTargetsEnabled ? i : 0);
const colorBlend = this._getColorBlendState(i < this._numAlphaBlendTargetsEnabled ? i : 0);
if (alphaBlend && colorBlend) {
descr.blend = {
alpha: alphaBlend,
color: colorBlend,
};
}
colorStates.push(descr);
}
else {
colorStates.push(null);
}
}
}
else {
if (this._webgpuColorFormat[0]) {
const descr = {
format: this._webgpuColorFormat[0],
writeMask: this._writeMask,
};
const alphaBlend = this._getAphaBlendState(0);
const colorBlend = this._getColorBlendState(0);
if (alphaBlend && colorBlend) {
descr.blend = {
alpha: alphaBlend,
color: colorBlend,
};
}
colorStates.push(descr);
}
else {
colorStates.push(null);
}
}
const stencilFront = {
compare: WebGPUCacheRenderPipeline._GetCompareFunction(this._stencilEnabled ? this._stencilFrontCompare : 7 /* ALWAYS */),
depthFailOp: WebGPUCacheRenderPipeline._GetStencilOpFunction(this._stencilEnabled ? this._stencilFrontDepthFailOp : 1 /* KEEP */),
failOp: WebGPUCacheRenderPipeline._GetStencilOpFunction(this._stencilEnabled ? this._stencilFrontFailOp : 1 /* KEEP */),
passOp: WebGPUCacheRenderPipeline._GetStencilOpFunction(this._stencilEnabled ? this._stencilFrontPassOp : 1 /* KEEP */),
};
const stencilBack = {
compare: WebGPUCacheRenderPipeline._GetCompareFunction(this._stencilEnabled ? this._stencilBackCompare : 7 /* ALWAYS */),
depthFailOp: WebGPUCacheRenderPipeline._GetStencilOpFunction(this._stencilEnabled ? this._stencilBackDepthFailOp : 1 /* KEEP */),
failOp: WebGPUCacheRenderPipeline._GetStencilOpFunction(this._stencilEnabled ? this._stencilBackFailOp : 1 /* KEEP */),
passOp: WebGPUCacheRenderPipeline._GetStencilOpFunction(this._stencilEnabled ? this._stencilBackPassOp : 1 /* KEEP */),
};
const topologyIsTriangle = topology === "triangle-list" /* WebGPUConstants.PrimitiveTopology.TriangleList */ || topology === "triangle-strip" /* WebGPUConstants.PrimitiveTopology.TriangleStrip */;
let stripIndexFormat = undefined;
if (topology === "line-strip" /* WebGPUConstants.PrimitiveTopology.LineStrip */ || topology === "triangle-strip" /* WebGPUConstants.PrimitiveTopology.TriangleStrip */) {
stripIndexFormat = !this.indexBuffer || this.indexBuffer.is32Bits ? "uint32" /* WebGPUConstants.IndexFormat.Uint32 */ : "uint16" /* WebGPUConstants.IndexFormat.Uint16 */;
}
const depthStencilFormatHasStencil = this._webgpuDepthStencilFormat ? WebGPUTextureHelper.HasStencilAspect(this._webgpuDepthStencilFormat) : false;
const primitiveState = {
topology,
frontFace: this._frontFace === 1 ? "ccw" /* WebGPUConstants.FrontFace.CCW */ : "cw" /* WebGPUConstants.FrontFace.CW */,
cullMode: !this._cullEnabled ? "none" /* WebGPUConstants.CullMode.None */ : this._cullFace === 2 ? "front" /* WebGPUConstants.CullMode.Front */ : "back" /* WebGPUConstants.CullMode.Back */,
};
if (stripIndexFormat) {
primitiveState.stripIndexFormat = stripIndexFormat;
}
return {
label: `RenderPipeline_${colorStates[0]?.format ?? "nooutput"}_${this._webgpuDepthStencilFormat ?? "nodepth"}_samples${sampleCount}_textureState${this._textureState}`,
layout: pipelineLayout,
vertex: {
module: webgpuPipelineContext.stages.vertexStage.module,
entryPoint: webgpuPipelineContext.stages.vertexStage.entryPoint,
buffers: inputStateDescriptor,
},
primitive: primitiveState,
fragment: !webgpuPipelineContext.stages.fragmentStage
? undefined
: {
module: webgpuPipelineContext.stages.fragmentStage.module,
entryPoint: webgpuPipelineContext.stages.fragmentStage.entryPoint,
targets: colorStates,
},
multisample: {
count: sampleCount,
},
depthStencil: this._webgpuDepthStencilFormat === undefined
? undefined
: {
depthWriteEnabled: this._depthWriteEnabled,
depthCompare: this._depthTestEnabled ? WebGPUCacheRenderPipeline._GetCompareFunction(this._depthCompare) : "always" /* WebGPUConstants.CompareFunction.Always */,
format: this._webgpuDepthStencilFormat,
stencilFront: this._stencilEnabled && depthStencilFormatHasStencil ? stencilFront : undefined,
stencilBack: this._stencilEnabled && depthStencilFormatHasStencil ? stencilBack : undefined,
stencilReadMask: this._stencilEnabled && depthStencilFormatHasStencil ? this._stencilReadMask : 0xffffffff,
stencilWriteMask: this._stencilEnabled && depthStencilFormatHasStencil ? this._stencilWriteMask : 0xffffffff,
depthBias: this._depthBias,
depthBiasClamp: topologyIsTriangle ? this._depthBiasClamp : 0,
depthBiasSlopeScale: topologyIsTriangle ? this._depthBiasSlopeScale : 0,
},
};
}
_createRenderPipeline(effect, topology, sampleCount) {
return this._device.createRenderPipeline(this._buildRenderPipelineDescriptor(effect, topology, sampleCount));
}
/**
* Pre-warms a render pipeline asynchronously. Sets up the cache state,
* checks for a cache hit, and on miss starts an async pipeline compilation.
* @param fillMode - the fill mode (triangle list, wireframe, etc.)
* @param effect - the effect to create the pipeline for
* @param sampleCount - the MSAA sample count
* @param textureState - the texture state bitmask
* @returns a Promise that resolves when the pipeline is compiled and cached, or null if already cached
*/
preWarmPipeline(fillMode, effect, sampleCount, textureState = 0) {
sampleCount = WebGPUTextureHelper.GetSample(sampleCount);
const cached = this._lookupRenderPipeline(fillMode, effect, sampleCount, textureState);
if (cached) {
return null;
}
const topology = WebGPUCacheRenderPipeline._GetTopology(fillMode);
// Capture the cache token before any subsequent cache operations overwrite it
const capturedParam = { token: this._parameter.token, pipeline: null };
const promise = this._device.createRenderPipelineAsync(this._buildRenderPipelineDescriptor(effect, topology, sampleCount));
// eslint-disable-next-line github/no-then
return promise.then((pipeline) => {
capturedParam.pipeline = pipeline;
this._setRenderPipeline(capturedParam);
WebGPUCacheRenderPipeline.NumCacheMiss++;
return pipeline;
});
}
}
WebGPUCacheRenderPipeline.LogErrorIfNoVertexBuffer = false;
WebGPUCacheRenderPipeline.NumCacheHitWithoutHash = 0;
WebGPUCacheRenderPipeline.NumCacheHitWithHash = 0;
WebGPUCacheRenderPipeline.NumCacheMiss = 0;
WebGPUCacheRenderPipeline.NumPipelineCreationLastFrame = 0;
WebGPUCacheRenderPipeline._NumPipelineCreationCurrentFrame = 0;
/** @internal */
class NodeState {
constructor() {
this.values = {};
}
count() {
let countNode = 0, countPipeline = this.pipeline ? 1 : 0;
for (const value in this.values) {
const node = this.values[value];
const [childCountNodes, childCoundPipeline] = node.count();
countNode += childCountNodes;
countPipeline += childCoundPipeline;
countNode++;
}
return [countNode, countPipeline];
}
}
/** @internal */
class WebGPUCacheRenderPipelineTree extends WebGPUCacheRenderPipeline {
static GetNodeCounts() {
const counts = WebGPUCacheRenderPipelineTree._Cache.count();
return { nodeCount: counts[0], pipelineCount: counts[1] };
}
static _GetPipelines(node, pipelines, curPath, curPathLen) {
if (node.pipeline) {
const path = curPath.slice();
path.length = curPathLen;
pipelines.push(path);
}
for (const value in node.values) {
const nnode = node.values[value];
curPath[curPathLen] = parseInt(value);
WebGPUCacheRenderPipelineTree._GetPipelines(nnode, pipelines, curPath, curPathLen + 1);
}
}
static GetPipelines() {
const pipelines = [];
WebGPUCacheRenderPipelineTree._GetPipelines(WebGPUCacheRenderPipelineTree._Cache, pipelines, [], 0);
return pipelines;
}
static ResetCache() {
WebGPUCacheRenderPipelineTree._Cache = new NodeState();
}
reset() {
this._nodeStack = [];
this._nodeStack[0] = WebGPUCacheRenderPipelineTree._Cache;
super.reset();
}
_getRenderPipeline(param) {
let node = this._nodeStack[this._stateDirtyLowestIndex];
for (let i = this._stateDirtyLowestIndex; i < this._statesLength; ++i) {
let nn = node.values[this._states[i]];
if (!nn) {
nn = new NodeState();
node.values[this._states[i]] = nn;
}
node = nn;
this._nodeStack[i + 1] = node;
}
param.token = node;
param.pipeline = node.pipeline;
}
_setRenderPipeline(param) {
param.token.pipeline = param.pipeline;
}
}
WebGPUCacheRenderPipelineTree._Cache = new NodeState();
/**
* @internal
**/
class WebGPUStencilStateComposer extends StencilStateComposer {
constructor(cache) {
super(false);
this._cache = cache;
this.reset();
}
get func() {
return this._func;
}
set func(value) {
if (this._func === value) {
return;
}
this._func = value;
this._cache.setStencilCompare(value);
}
get backFunc() {
return this._backFunc;
}
set backFunc(value) {
if (this._backFunc === value) {
return;
}
this._backFunc = value;
this._cache.setStencilBackCompare(value);
}
get funcMask() {
return this._funcMask;
}
set funcMask(value) {
if (this._funcMask === value) {
return;
}
this._funcMask = value;
this._cache.setStencilReadMask(value);
}
get opStencilFail() {
return this._opStencilFail;
}
set opStencilFail(value) {
if (this._opStencilFail === value) {
return;
}
this._opStencilFail = value;
this._cache.setStencilFailOp(value);
}
get opDepthFail() {
return this._opDepthFail;
}
set opDepthFail(value) {
if (this._opDepthFail === value) {
return;
}
this._opDepthFail = value;
this._cache.setStencilDepthFailOp(value);
}
get opStencilDepthPass() {
return this._opStencilDepthPass;
}
set opStencilDepthPass(value) {
if (this._opStencilDepthPass === value) {
return;
}
this._opStencilDepthPass = value;
this._cache.setStencilPassOp(value);
}
get backOpStencilFail() {
return this._backOpStencilFail;
}
set backOpStencilFail(value) {
if (this._backOpStencilFail === value) {
return;
}
this._backOpStencilFail = value;
this._cache.setStencilBackFailOp(value);
}
get backOpDepthFail() {
return this._backOpDepthFail;
}
set backOpDepthFail(value) {
if (this._backOpDepthFail === value) {
return;
}
this._backOpDepthFail = value;
this._cache.setStencilBackDepthFailOp(value);
}
get backOpStencilDepthPass() {
return this._backOpStencilDepthPass;
}
set backOpStencilDepthPass(value) {
if (this._backOpStencilDepthPass === value) {
return;
}
this._backOpStencilDepthPass = value;
this._cache.setStencilBackPassOp(value);
}
get mask() {
return this._mask;
}
set mask(value) {
if (this._mask === value) {
return;
}
this._mask = value;
this._cache.setStencilWriteMask(value);
}
get enabled() {
return this._enabled;
}
set enabled(value) {
if (this._enabled === value) {
return;
}
this._enabled = value;
this._cache.setStencilEnabled(value);
}
reset() {
super.reset();
this._cache.resetStencilState();
}
apply() {
const stencilMaterialEnabled = !this.useStencilGlobalOnly && !!this.stencilMaterial?.enabled;
this.enabled = stencilMaterialEnabled ? this.stencilMaterial.enabled : this.stencilGlobal.enabled;
if (!this.enabled) {
return;
}
this.mask = stencilMaterialEnabled ? this.stencilMaterial.mask : this.stencilGlobal.mask;
this.funcRef = stencilMaterialEnabled ? this.stencilMaterial.funcRef : this.stencilGlobal.funcRef;
this.funcMask = stencilMaterialEnabled ? this.stencilMaterial.funcMask : this.stencilGlobal.funcMask;
this.func = stencilMaterialEnabled ? this.stencilMaterial.func : this.stencilGlobal.func;
this.opStencilFail = stencilMaterialEnabled ? this.stencilMaterial.opStencilFail : this.stencilGlobal.opStencilFail;
this.opDepthFail = stencilMaterialEnabled ? this.stencilMaterial.opDepthFail : this.stencilGlobal.opDepthFail;
this.opStencilDepthPass = stencilMaterialEnabled ? this.stencilMaterial.opStencilDepthPass : this.stencilGlobal.opStencilDepthPass;
this.backFunc = stencilMaterialEnabled ? this.stencilMaterial.backFunc : this.stencilGlobal.backFunc;
this.backOpStencilFail = stencilMaterialEnabled ? this.stencilMaterial.backOpStencilFail : this.stencilGlobal.backOpStencilFail;
this.backOpDepthFail = stencilMaterialEnabled ? this.stencilMaterial.backOpDepthFail : this.stencilGlobal.backOpDepthFail;
this.backOpStencilDepthPass = stencilMaterialEnabled ? this.stencilMaterial.backOpStencilDepthPass : this.stencilGlobal.backOpStencilDepthPass;
}
}
/**
* @internal
**/
class WebGPUDepthCullingState extends DepthCullingState {
/**
* Initializes the state.
* @param cache
*/
constructor(cache) {
super(false);
this._cache = cache;
this.reset();
}
get zOffset() {
return this._zOffset;
}
set zOffset(value) {
if (this._zOffset === value) {
return;
}
this._zOffset = value;
this._isZOffsetDirty = true;
this._cache.setDepthBiasSlopeScale(value);
}
get zOffsetUnits() {
return this._zOffsetUnits;
}
set zOffsetUnits(value) {
if (this._zOffsetUnits === value) {
return;
}
this._zOffsetUnits = value;
this._isZOffsetDirty = true;
this._cache.setDepthBias(value);
}
get cullFace() {
return this._cullFace;
}
set cullFace(value) {
if (this._cullFace === value) {
return;
}
this._cullFace = value;
this._isCullFaceDirty = true;
this._cache.setCullFace(value ?? 1);
}
get cull() {
return this._cull;
}
set cull(value) {
if (this._cull === value) {
return;
}
this._cull = value;
this._isCullDirty = true;
this._cache.setCullEnabled(!!value);
}
get depthFunc() {
return this._depthFunc;
}
set depthFunc(value) {
if (this._depthFunc === value) {
return;
}
this._depthFunc = value;
this._isDepthFuncDirty = true;
this._cache.setDepthCompare(value);
}
get depthMask() {
return this._depthMask;
}
set depthMask(value) {
if (this._depthMask === value) {
return;
}
this._depthMask = value;
this._isDepthMaskDirty = true;
this._cache.setDepthWriteEnabled(value);
}
get depthTest() {
return this._depthTest;
}
set depthTest(value) {
if (this._depthTest === value) {
return;
}
this._depthTest = value;
this._isDepthTestDirty = true;
this._cache.setDepthTestEnabled(value);
}
get frontFace() {
return this._frontFace;
}
set frontFace(value) {
if (this._frontFace === value) {
return;
}
this._frontFace = value;
this._isFrontFaceDirty = true;
this._cache.setFrontFace(value ?? 2);
}
reset() {
super.reset();
this._cache.resetDepthCullingState();
}
apply() {
// nothing to do
}
}
/**
* Class used to store an external texture (like GPUExternalTexture in WebGPU)
*/
class ExternalTexture {
/**
* Checks if a texture is an external or internal texture
* @param texture the external or internal texture
* @returns true if the texture is an external texture, else false
*/
static IsExternalTexture(texture) {
return texture.underlyingResource !== undefined;
}
/**
* Get the class name of the texture.
* @returns "ExternalTexture"
*/
getClassName() {
return "ExternalTexture";
}
/**
* Gets the underlying texture object
*/
get underlyingResource() {
return this._video;
}
/**
* Constructs the texture
* @param video The video the texture should be wrapped around
*/
constructor(video) {
/**
* Gets a boolean indicating if the texture uses mipmaps
*/
this.useMipMaps = false;
/**
* The type of the underlying texture is implementation dependent, so return "UNDEFINED" for the type
*/
this.type = Constants.TEXTURETYPE_UNDEFINED;
/**
* The format of the underlying texture is implementation dependent, so return "UNDEFINED" for the format
*/
this.format = Constants.TEXTUREFORMAT_UNDEFINED;
this._video = video;
this.uniqueId = InternalTexture._Counter++;
}
/**
* Get if the texture is ready to be used (downloaded, converted, mip mapped...).
* @returns true if fully ready
*/
isReady() {
return this._video.readyState >= this._video.HAVE_CURRENT_DATA;
}
/**
* Dispose the texture and release its associated resources.
*/
dispose() { }
}
/* eslint-disable babylonjs/available */
/* eslint-disable jsdoc/require-jsdoc */
/** @internal */
class WebGPUMaterialContext {
get forceBindGroupCreation() {
// If there is at least one external texture to bind, we must recreate the bind groups each time
// because we need to retrieve a new texture each frame (by calling device.importExternalTexture)
return this._numExternalTextures > 0;
}
get hasFloatOrDepthTextures() {
return this._numFloatOrDepthTextures > 0;
}
constructor() {
this.useVertexPulling = false;
this.uniqueId = WebGPUMaterialContext._Counter++;
this.updateId = 0;
this.textureState = 0;
this.reset();
}
reset() {
this.samplers = {};
this.textures = {};
this.isDirty = true;
this._numFloatOrDepthTextures = 0;
this._numExternalTextures = 0;
}
setSampler(name, sampler) {
let samplerCache = this.samplers[name];
let currentHashCode = -1;
if (!samplerCache) {
this.samplers[name] = samplerCache = { sampler, hashCode: 0 };
}
else {
currentHashCode = samplerCache.hashCode;
}
samplerCache.sampler = sampler;
samplerCache.hashCode = sampler ? WebGPUCacheSampler.GetSamplerHashCode(sampler) : 0;
const isDirty = currentHashCode !== samplerCache.hashCode;
if (isDirty) {
this.updateId++;
}
this.isDirty ||= isDirty;
}
setTexture(name, texture) {
let textureCache = this.textures[name];
let currentTextureId = -1;
if (!textureCache) {
this.textures[name] = textureCache = { texture, isFloatOrDepthTexture: false, isExternalTexture: false };
}
else {
currentTextureId = textureCache.texture?.uniqueId ?? -1;
}
if (textureCache.isExternalTexture) {
this._numExternalTextures--;
}
if (textureCache.isFloatOrDepthTexture) {
this._numFloatOrDepthTextures--;
}
if (texture) {
textureCache.isFloatOrDepthTexture =
texture.type === Constants.TEXTURETYPE_FLOAT ||
(texture.format >= Constants.TEXTUREFORMAT_DEPTH24_STENCIL8 && texture.format <= Constants.TEXTUREFORMAT_DEPTH32FLOAT_STENCIL8);
textureCache.isExternalTexture = ExternalTexture.IsExternalTexture(texture);
if (textureCache.isFloatOrDepthTexture) {
this._numFloatOrDepthTextures++;
}
if (textureCache.isExternalTexture) {
this._numExternalTextures++;
}
}
else {
textureCache.isFloatOrDepthTexture = false;
textureCache.isExternalTexture = false;
}
textureCache.texture = texture;
const isDirty = currentTextureId !== (texture?.uniqueId ?? -1);
if (isDirty) {
this.updateId++;
}
this.isDirty ||= isDirty;
}
}
WebGPUMaterialContext._Counter = 0;
/**
* WebGPU implementation of the IDrawContext interface.
* This class manages the draw context for WebGPU, including buffers and indirect draw data.
*/
class WebGPUDrawContext {
/**
* Checks if the draw context is dirty.
* @param materialContextUpdateId The update ID of the material context associated with the draw context.
* @returns True if the draw or material context is dirty, false otherwise.
*/
isDirty(materialContextUpdateId) {
return this._isDirty || this._materialContextUpdateId !== materialContextUpdateId;
}
/**
* Resets the dirty state of the draw context.
* @param materialContextUpdateId The update ID of the material context associated with the draw context.
*/
resetIsDirty(materialContextUpdateId) {
this._isDirty = false;
this._materialContextUpdateId = materialContextUpdateId;
}
get enableIndirectDraw() {
return this._enableIndirectDraw;
}
set enableIndirectDraw(enable) {
this._enableIndirectDrawInCompatMode = true;
if (this._enableIndirectDraw === enable) {
return;
}
this._enableIndirectDraw = enable;
if (!enable && !this._useInstancing && this.indirectDrawBuffer) {
this._bufferManager.releaseBuffer(this.indirectDrawBuffer);
this.indirectDrawBuffer = undefined;
this._indirectDrawData = undefined;
}
else if (enable && !this.indirectDrawBuffer) {
this.indirectDrawBuffer = this._bufferManager.createRawBuffer(20, BufferUsage.CopyDst | BufferUsage.Indirect | BufferUsage.Storage, undefined, "IndirectDrawBuffer");
this._indirectDrawData = new Uint32Array(5);
this._indirectDrawData[3] = 0;
this._indirectDrawData[4] = 0;
}
}
get useInstancing() {
return this._useInstancing;
}
set useInstancing(use) {
if (this._useInstancing === use) {
return;
}
this._useInstancing = use;
this._currentInstanceCount = -1;
const enableIndirectDrawInCompatMode = this._enableIndirectDrawInCompatMode;
this.enableIndirectDraw = use;
this._enableIndirectDrawInCompatMode = enableIndirectDrawInCompatMode;
}
/**
* Creates a new WebGPUDrawContext.
* @param bufferManager The buffer manager used to manage WebGPU buffers.
* @param _dummyIndexBuffer A dummy index buffer to be bound as the "indices"
* storage buffer when no index buffer is provided.
*/
constructor(bufferManager, _dummyIndexBuffer) {
this._dummyIndexBuffer = _dummyIndexBuffer;
/**
* @internal
* By default, indirect draws are enabled in NON compatibility mode only
* To enable indirect draws in compatibility mode (done by the end user), enableIndirectDraw must be set to true
*/
this._enableIndirectDrawInCompatMode = false;
this._bufferManager = bufferManager;
this.uniqueId = WebGPUDrawContext._Counter++;
this._useInstancing = false;
this._currentInstanceCount = 0;
this._enableIndirectDraw = false;
this._vertexPullingEnabled = false;
this.reset();
}
reset() {
this.buffers = {};
this._isDirty = true;
this._materialContextUpdateId = 0;
this.fastBundle = undefined;
this.bindGroups = undefined;
this._vertexPullingEnabled = false;
}
/**
* Associates a buffer to the draw context.
* @param name The name of the buffer.
* @param buffer The buffer to set.
*/
setBuffer(name, buffer) {
this._isDirty ||= buffer?.uniqueId !== this.buffers[name]?.uniqueId;
this.buffers[name] = buffer;
}
setIndirectData(indexOrVertexCount, instanceCount, firstIndexOrVertex, forceUpdate = false) {
if ((!forceUpdate && instanceCount === this._currentInstanceCount) || !this.indirectDrawBuffer || !this._indirectDrawData) {
// The current buffer is already up to date so do nothing
// Note that we only check for instanceCount and not indexOrVertexCount nor firstIndexOrVertex because those values
// are supposed to not change during the lifetime of a draw context
return;
}
this._currentInstanceCount = instanceCount;
this._indirectDrawData[0] = indexOrVertexCount;
this._indirectDrawData[1] = instanceCount;
this._indirectDrawData[2] = firstIndexOrVertex;
this._bufferManager.setRawData(this.indirectDrawBuffer, 0, this._indirectDrawData, 0, 20);
}
/**
* Setup or disable vertex pulling as needed.
* @param useVertexPulling Use vertex pulling or not
* @param webgpuPipelineContext The WebGPU pipeline context
* @param vertexBuffers The current vertex buffers
* @param indexBuffer The current index buffer
* @param overrideVertexBuffers The vertex buffers to override
*/
setVertexPulling(useVertexPulling, webgpuPipelineContext, vertexBuffers, indexBuffer, overrideVertexBuffers) {
if (this._vertexPullingEnabled === useVertexPulling) {
return;
}
this._vertexPullingEnabled = useVertexPulling;
this._isDirty = true;
const bufferNames = webgpuPipelineContext.shaderProcessingContext.bufferNames;
if (overrideVertexBuffers) {
for (const attributeName in overrideVertexBuffers) {
const vertexBuffer = overrideVertexBuffers[attributeName];
if (!vertexBuffer || bufferNames.indexOf(attributeName) === -1) {
continue;
}
const buffer = vertexBuffer.effectiveBuffer;
this.setBuffer(attributeName, useVertexPulling ? buffer : null);
}
}
for (const attributeName in vertexBuffers) {
if (overrideVertexBuffers && attributeName in overrideVertexBuffers) {
continue;
}
const vertexBuffer = vertexBuffers[attributeName];
if (!vertexBuffer || bufferNames.indexOf(attributeName) === -1) {
continue;
}
const buffer = vertexBuffer.effectiveBuffer;
this.setBuffer(attributeName, useVertexPulling ? buffer : null);
}
if (bufferNames.indexOf("indices") !== -1) {
this.setBuffer("indices", !useVertexPulling ? null : (indexBuffer ?? this._dummyIndexBuffer));
}
}
dispose() {
if (this.indirectDrawBuffer) {
this._bufferManager.releaseBuffer(this.indirectDrawBuffer);
this.indirectDrawBuffer = undefined;
this._indirectDrawData = undefined;
}
this.fastBundle = undefined;
this.bindGroups = undefined;
this.buffers = undefined;
this._enableIndirectDraw = false;
}
}
WebGPUDrawContext._Counter = 0;
/* eslint-disable babylonjs/available */
/* eslint-disable jsdoc/require-jsdoc */
/**
* Sampler hash codes are 19 bits long, so using a start value of 2^20 for buffer ids will ensure we can't have any collision with the sampler hash codes
*/
const BufferIdStart = 1 << 20;
/**
* textureIdStart is added to texture ids to ensure we can't have any collision with the buffer ids / sampler hash codes.
* 2^35 for textureIdStart means we can have:
* - 2^(35-20) = 2^15 = 32768 possible buffer ids
* - 2^(53-35) = 2^18 = 524288 possible texture ids
*/
const TextureIdStart = 2 ** 35;
class WebGPUBindGroupCacheNode {
constructor() {
this.values = {};
}
}
/** @internal */
class WebGPUCacheBindGroups {
static get Statistics() {
return {
totalCreated: WebGPUCacheBindGroups.NumBindGroupsCreatedTotal,
lastFrameCreated: WebGPUCacheBindGroups.NumBindGroupsCreatedLastFrame,
lookupLastFrame: WebGPUCacheBindGroups.NumBindGroupsLookupLastFrame,
noLookupLastFrame: WebGPUCacheBindGroups.NumBindGroupsNoLookupLastFrame,
};
}
static ResetCache() {
WebGPUCacheBindGroups._Cache = new WebGPUBindGroupCacheNode();
WebGPUCacheBindGroups.NumBindGroupsCreatedTotal = 0;
WebGPUCacheBindGroups.NumBindGroupsCreatedLastFrame = 0;
WebGPUCacheBindGroups.NumBindGroupsLookupLastFrame = 0;
WebGPUCacheBindGroups.NumBindGroupsNoLookupLastFrame = 0;
WebGPUCacheBindGroups._NumBindGroupsCreatedCurrentFrame = 0;
WebGPUCacheBindGroups._NumBindGroupsLookupCurrentFrame = 0;
WebGPUCacheBindGroups._NumBindGroupsNoLookupCurrentFrame = 0;
}
constructor(device, cacheSampler, engine) {
this.disabled = false;
this._device = device;
this._cacheSampler = cacheSampler;
this._engine = engine;
}
endFrame() {
WebGPUCacheBindGroups.NumBindGroupsCreatedLastFrame = WebGPUCacheBindGroups._NumBindGroupsCreatedCurrentFrame;
WebGPUCacheBindGroups.NumBindGroupsLookupLastFrame = WebGPUCacheBindGroups._NumBindGroupsLookupCurrentFrame;
WebGPUCacheBindGroups.NumBindGroupsNoLookupLastFrame = WebGPUCacheBindGroups._NumBindGroupsNoLookupCurrentFrame;
WebGPUCacheBindGroups._NumBindGroupsCreatedCurrentFrame = 0;
WebGPUCacheBindGroups._NumBindGroupsLookupCurrentFrame = 0;
WebGPUCacheBindGroups._NumBindGroupsNoLookupCurrentFrame = 0;
}
/**
* Cache is currently based on the uniform/storage buffers, samplers and textures used by the binding groups.
* Note that all uniform buffers have an offset of 0 in Babylon and we don't have a use case where we would have the same buffer used with different capacity values:
* that means we don't need to factor in the offset/size of the buffer in the cache, only the id
* @param webgpuPipelineContext
* @param drawContext
* @param materialContext
* @returns a bind group array
*/
getBindGroups(webgpuPipelineContext, drawContext, materialContext) {
let bindGroups = undefined;
let node = WebGPUCacheBindGroups._Cache;
const cacheIsDisabled = this.disabled || materialContext.forceBindGroupCreation;
if (!cacheIsDisabled) {
if (!drawContext.isDirty(materialContext.updateId) && !materialContext.isDirty) {
WebGPUCacheBindGroups._NumBindGroupsNoLookupCurrentFrame++;
return drawContext.bindGroups;
}
for (const bufferName of webgpuPipelineContext.shaderProcessingContext.bufferNames) {
const uboId = (drawContext.buffers[bufferName]?.uniqueId ?? 0) + BufferIdStart;
let nextNode = node.values[uboId];
if (!nextNode) {
nextNode = new WebGPUBindGroupCacheNode();
node.values[uboId] = nextNode;
}
node = nextNode;
}
for (const samplerName of webgpuPipelineContext.shaderProcessingContext.samplerNames) {
const samplerHashCode = materialContext.samplers[samplerName]?.hashCode ?? 0;
let nextNode = node.values[samplerHashCode];
if (!nextNode) {
nextNode = new WebGPUBindGroupCacheNode();
node.values[samplerHashCode] = nextNode;
}
node = nextNode;
}
for (const textureName of webgpuPipelineContext.shaderProcessingContext.textureNames) {
const textureId = (materialContext.textures[textureName]?.texture?.uniqueId ?? 0) + TextureIdStart;
let nextNode = node.values[textureId];
if (!nextNode) {
nextNode = new WebGPUBindGroupCacheNode();
node.values[textureId] = nextNode;
}
node = nextNode;
}
bindGroups = node.bindGroups;
}
drawContext.resetIsDirty(materialContext.updateId);
materialContext.isDirty = false;
if (bindGroups) {
drawContext.bindGroups = bindGroups;
WebGPUCacheBindGroups._NumBindGroupsLookupCurrentFrame++;
return bindGroups;
}
bindGroups = [];
drawContext.bindGroups = bindGroups;
if (!cacheIsDisabled) {
node.bindGroups = bindGroups;
}
WebGPUCacheBindGroups.NumBindGroupsCreatedTotal++;
WebGPUCacheBindGroups._NumBindGroupsCreatedCurrentFrame++;
const bindGroupLayouts = webgpuPipelineContext.bindGroupLayouts[materialContext.textureState];
for (let i = 0; i < webgpuPipelineContext.shaderProcessingContext.bindGroupLayoutEntries.length; i++) {
const setDefinition = webgpuPipelineContext.shaderProcessingContext.bindGroupLayoutEntries[i];
const entries = webgpuPipelineContext.shaderProcessingContext.bindGroupEntries[i];
for (let j = 0; j < setDefinition.length; j++) {
const entry = webgpuPipelineContext.shaderProcessingContext.bindGroupLayoutEntries[i][j];
const entryInfo = webgpuPipelineContext.shaderProcessingContext.bindGroupLayoutEntryInfo[i][entry.binding];
const name = entryInfo.nameInArrayOfTexture ?? entryInfo.name;
if (entry.sampler) {
const bindingInfo = materialContext.samplers[name];
if (bindingInfo) {
const sampler = bindingInfo.sampler;
if (!sampler) {
if (this._engine.dbgSanityChecks) {
Logger.Error(`Trying to bind a null sampler! entry=${JSON.stringify(entry)}, name=${name}, bindingInfo=${JSON.stringify(bindingInfo, (key, value) => (key === "texture" ? "<no dump>" : value))}, materialContext.uniqueId=${materialContext.uniqueId}`, 50);
}
continue;
}
entries[j].resource = this._cacheSampler.getSampler(sampler, false, bindingInfo.hashCode, sampler.label);
}
else {
Logger.Error(`Sampler "${name}" not found in the material context. Make sure you bound it. entry=${JSON.stringify(entry)}, materialContext=${JSON.stringify(materialContext, (key, value) => (key === "texture" || key === "sampler" ? "<no dump>" : value))}`, 50);
}
}
else if (entry.texture || entry.storageTexture) {
const bindingInfo = materialContext.textures[name];
if (bindingInfo) {
if (this._engine.dbgSanityChecks && bindingInfo.texture === null) {
Logger.Error(`Trying to bind a null texture! name="${name}", entry=${JSON.stringify(entry)}, bindingInfo=${JSON.stringify(bindingInfo, (key, value) => (key === "texture" ? "<no dump>" : value))}, materialContext.uniqueId=${materialContext.uniqueId}`, 50);
continue;
}
const hardwareTexture = bindingInfo.texture._hardwareTexture;
if (this._engine.dbgSanityChecks &&
(!hardwareTexture || (entry.texture && !hardwareTexture.view) || (entry.storageTexture && !hardwareTexture.viewForWriting))) {
Logger.Error(`Trying to bind a null gpu texture or view! entry=${JSON.stringify(entry)}, name=${name}, bindingInfo=${JSON.stringify(bindingInfo, (key, value) => (key === "texture" ? "<no dump>" : value))}, isReady=${bindingInfo.texture?.isReady}, materialContext.uniqueId=${materialContext.uniqueId}`, 50);
continue;
}
entries[j].resource = entry.storageTexture ? hardwareTexture.viewForWriting : hardwareTexture.view;
}
else {
Logger.Error(`Texture "${name}" not found in the material context. Make sure you bound it (something like effect.setTexture("${name}", texture)). entry=${JSON.stringify(entry)}, materialContext=${JSON.stringify(materialContext, (key, value) => (key === "texture" || key === "sampler" ? "<no dump>" : value))}`, 50);
}
}
else if (entry.externalTexture) {
const bindingInfo = materialContext.textures[name];
if (bindingInfo) {
if (this._engine.dbgSanityChecks && bindingInfo.texture === null) {
Logger.Error(`Trying to bind a null external texture! entry=${JSON.stringify(entry)}, name=${name}, bindingInfo=${JSON.stringify(bindingInfo, (key, value) => (key === "texture" ? "<no dump>" : value))}, materialContext.uniqueId=${materialContext.uniqueId}`, 50);
continue;
}
const externalTexture = bindingInfo.texture.underlyingResource;
if (this._engine.dbgSanityChecks && !externalTexture) {
Logger.Error(`Trying to bind a null gpu external texture! entry=${JSON.stringify(entry)}, name=${name}, bindingInfo=${JSON.stringify(bindingInfo, (key, value) => (key === "texture" ? "<no dump>" : value))}, isReady=${bindingInfo.texture?.isReady}, materialContext.uniqueId=${materialContext.uniqueId}`, 50);
continue;
}
entries[j].resource = this._device.importExternalTexture({ source: externalTexture });
}
else {
Logger.Error(`External texture "${name}" not found in the material context. Make sure you bound it. entry=${JSON.stringify(entry)}, materialContext=${JSON.stringify(materialContext, (key, value) => (key === "texture" || key === "sampler" ? "<no dump>" : value))}`, 50);
}
}
else if (entry.buffer) {
const dataBuffer = drawContext.buffers[name];
if (dataBuffer) {
const webgpuBuffer = dataBuffer.underlyingResource;
entries[j].resource.buffer = webgpuBuffer;
entries[j].resource.size = dataBuffer.capacity;
}
else {
Logger.Error(`Can't find buffer "${name}" in the draw context. Make sure you bound it. entry=${JSON.stringify(entry)}, buffers=${JSON.stringify(drawContext.buffers)}, drawContext.uniqueId=${drawContext.uniqueId}`, 50);
}
}
}
const groupLayout = bindGroupLayouts[i];
bindGroups[i] = this._device.createBindGroup({
layout: groupLayout,
entries,
});
}
return bindGroups;
}
}
WebGPUCacheBindGroups.NumBindGroupsCreatedTotal = 0;
WebGPUCacheBindGroups.NumBindGroupsCreatedLastFrame = 0;
WebGPUCacheBindGroups.NumBindGroupsLookupLastFrame = 0;
WebGPUCacheBindGroups.NumBindGroupsNoLookupLastFrame = 0;
WebGPUCacheBindGroups._Cache = new WebGPUBindGroupCacheNode();
WebGPUCacheBindGroups._NumBindGroupsCreatedCurrentFrame = 0;
WebGPUCacheBindGroups._NumBindGroupsLookupCurrentFrame = 0;
WebGPUCacheBindGroups._NumBindGroupsNoLookupCurrentFrame = 0;
/* eslint-disable babylonjs/available */
/**
* Caches GPUTextureView objects so that render-pass attachments (color, MSAA resolve, depth/stencil)
* don't recreate a fresh view every time a render target is bound/cleared.
* Views are keyed on the live GPUTexture object via a WeakMap: once a texture is recreated (e.g. on resize)
* the old texture becomes unreachable and its cached views are collected along with it, so no manual
* invalidation is required.
* @internal
*/
class WebGPUCacheTextureView {
constructor() {
this._cache = new WeakMap();
}
getView(texture, descriptor) {
let viewsForTexture = this._cache.get(texture);
if (!viewsForTexture) {
viewsForTexture = new Map();
this._cache.set(texture, viewsForTexture);
}
const key = WebGPUCacheTextureView._GetKey(descriptor);
let view = viewsForTexture.get(key);
if (!view) {
view = texture.createView(descriptor);
viewsForTexture.set(key, view);
}
return view;
}
static _GetKey(descriptor) {
return `${descriptor.format}_${descriptor.dimension}_${descriptor.baseMipLevel}_${descriptor.mipLevelCount}_${descriptor.baseArrayLayer}_${descriptor.arrayLayerCount}_${descriptor.aspect}_${descriptor.usage}_${descriptor.swizzle}`;
}
}
/** This file must only contain pure code and pure imports */
/** @internal */
class WebGPUClearQuad {
setDepthStencilFormat(format) {
this._depthTextureFormat = format;
this._cacheRenderPipeline.setDepthStencilFormat(format);
}
setColorFormat(format) {
this._cacheRenderPipeline.setColorFormat(format);
}
setMRTAttachments(attachments, textureArray, textureCount) {
this._cacheRenderPipeline.setMRT(textureArray, textureCount);
this._cacheRenderPipeline.setMRTAttachments(attachments);
}
constructor(device, engine, emptyVertexBuffer) {
this._bindGroups = {};
this._bundleCache = {};
this._keyTemp = [];
this._device = device;
this._engine = engine;
this._cacheRenderPipeline = new WebGPUCacheRenderPipelineTree(this._device, emptyVertexBuffer);
this._cacheRenderPipeline.setDepthTestEnabled(false);
this._cacheRenderPipeline.setStencilReadMask(0xff);
this._effect = engine.createEffect("clearQuad", [], ["color", "depthValue"], undefined, undefined, undefined, undefined, undefined, undefined, 1 /* ShaderLanguage.WGSL */,
// On the pure import path the wrapper modules that eagerly register the clearQuad shaders are not in the
// module graph, so load the generated shader modules here to guarantee they are present in the ShaderStore
// before the effect compiles (otherwise the engine falls back to fetching clearQuad.*.fx and 404s).
async () => {
await Promise.all([import('./clearQuad.vertex-Jpm08Tfg.esm.js'), import('./clearQuad.fragment-C9nJq5fW.esm.js')]);
});
}
clear(renderPass, clearColor, clearDepth, clearStencil, sampleCount = 1) {
let renderPass2;
let bundle = null;
let bundleKey;
const isRTTPass = !!this._engine._currentRenderTarget;
if (renderPass) {
renderPass2 = renderPass;
}
else {
let idx = 0;
this._keyTemp.length = 0;
for (let i = 0; i < this._cacheRenderPipeline.colorFormats.length; ++i) {
this._keyTemp[idx++] = renderableTextureFormatToIndex[this._cacheRenderPipeline.colorFormats[i] ?? ""];
}
const depthStencilFormatIndex = renderableTextureFormatToIndex[this._depthTextureFormat ?? 0];
this._keyTemp[idx] =
(clearColor ? clearColor.r + clearColor.g * 256 + clearColor.b * 256 * 256 + clearColor.a * 256 * 256 * 256 : 0) +
(clearDepth ? 2 ** 32 : 0) +
(clearStencil ? 2 ** 33 : 0) +
(this._engine.useReverseDepthBuffer ? 2 ** 34 : 0) +
(isRTTPass ? 2 ** 35 : 0) +
(sampleCount > 1 ? 2 ** 36 : 0) +
depthStencilFormatIndex * 2 ** 37;
bundleKey = this._keyTemp.join("_");
bundle = this._bundleCache[bundleKey];
if (bundle) {
return bundle;
}
renderPass2 = this._device.createRenderBundleEncoder({
label: "clearQuadRenderBundle",
colorFormats: this._cacheRenderPipeline.colorFormats,
depthStencilFormat: this._depthTextureFormat,
sampleCount: WebGPUTextureHelper.GetSample(sampleCount),
});
}
this._cacheRenderPipeline.setDepthWriteEnabled(!!clearDepth);
this._cacheRenderPipeline.setStencilEnabled(!!clearStencil && !!this._depthTextureFormat && WebGPUTextureHelper.HasStencilAspect(this._depthTextureFormat));
this._cacheRenderPipeline.setStencilWriteMask(clearStencil ? 0xff : 0);
this._cacheRenderPipeline.setStencilCompare(clearStencil ? Constants.ALWAYS : Constants.NEVER);
this._cacheRenderPipeline.setStencilPassOp(clearStencil ? Constants.REPLACE : Constants.KEEP);
this._cacheRenderPipeline.setWriteMask(clearColor ? 0xf : 0);
const pipeline = this._cacheRenderPipeline.getRenderPipeline(Constants.MATERIAL_TriangleStripDrawMode, this._effect, sampleCount);
const webgpuPipelineContext = this._effect._pipelineContext;
if (clearColor) {
this._effect.setDirectColor4("color", clearColor);
}
this._effect.setFloat("depthValue", this._engine.useReverseDepthBuffer ? this._engine._clearReverseDepthValue : this._engine._clearDepthValue);
webgpuPipelineContext.uniformBuffer.update();
const bufferInternals = isRTTPass ? this._engine._ubInvertY : this._engine._ubDontInvertY;
const bufferLeftOver = webgpuPipelineContext.uniformBuffer.getBuffer();
const key = bufferLeftOver.uniqueId + "-" + bufferInternals.uniqueId;
let bindGroups = this._bindGroups[key];
if (!bindGroups) {
const bindGroupLayouts = webgpuPipelineContext.bindGroupLayouts[0];
bindGroups = this._bindGroups[key] = [];
bindGroups.push(this._device.createBindGroup({
label: `clearQuadBindGroup0-${key}`,
layout: bindGroupLayouts[0],
entries: [],
}));
if (!WebGPUShaderProcessingContext._SimplifiedKnownBindings) {
bindGroups.push(this._device.createBindGroup({
label: `clearQuadBindGroup1-${key}`,
layout: bindGroupLayouts[1],
entries: [],
}));
}
bindGroups.push(this._device.createBindGroup({
label: `clearQuadBindGroup${WebGPUShaderProcessingContext._SimplifiedKnownBindings ? 1 : 2}-${key}`,
layout: bindGroupLayouts[WebGPUShaderProcessingContext._SimplifiedKnownBindings ? 1 : 2],
entries: [
{
binding: 0,
resource: {
buffer: bufferInternals.underlyingResource,
size: bufferInternals.capacity,
},
},
{
binding: 1,
resource: {
buffer: bufferLeftOver.underlyingResource,
size: bufferLeftOver.capacity,
},
},
],
}));
}
renderPass2.setPipeline(pipeline);
for (let i = 0; i < bindGroups.length; ++i) {
renderPass2.setBindGroup(i, bindGroups[i]);
}
renderPass2.draw(4, 1, 0, 0);
if (!renderPass) {
bundle = renderPass2.finish();
this._bundleCache[bundleKey] = bundle;
}
return bundle;
}
}
/** @internal */
class WebGPURenderItemViewport {
constructor(x, y, w, h) {
this.x = Math.floor(x);
this.y = Math.floor(y);
this.w = Math.floor(w);
this.h = Math.floor(h);
}
run(renderPass) {
renderPass.setViewport(this.x, this.y, this.w, this.h, 0, 1);
}
clone() {
return new WebGPURenderItemViewport(this.x, this.y, this.w, this.h);
}
}
/** @internal */
class WebGPURenderItemScissor {
constructor(x, y, w, h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
run(renderPass) {
renderPass.setScissorRect(this.x, this.y, this.w, this.h);
}
clone() {
return new WebGPURenderItemScissor(this.x, this.y, this.w, this.h);
}
}
/** @internal */
class WebGPURenderItemStencilRef {
constructor(ref) {
this.ref = ref;
}
run(renderPass) {
renderPass.setStencilReference(this.ref);
}
clone() {
return new WebGPURenderItemStencilRef(this.ref);
}
}
/** @internal */
class WebGPURenderItemBlendColor {
constructor(color) {
this.color = color;
}
run(renderPass) {
renderPass.setBlendConstant(this.color);
}
clone() {
return new WebGPURenderItemBlendColor(this.color);
}
}
/** @internal */
class WebGPURenderItemBeginOcclusionQuery {
constructor(query) {
this.query = query;
}
run(renderPass) {
renderPass.beginOcclusionQuery(this.query);
}
clone() {
return new WebGPURenderItemBeginOcclusionQuery(this.query);
}
}
/** @internal */
class WebGPURenderItemEndOcclusionQuery {
constructor() { }
run(renderPass) {
renderPass.endOcclusionQuery();
}
clone() {
return new WebGPURenderItemEndOcclusionQuery();
}
}
class WebGPURenderItemBundles {
constructor() {
this.bundles = [];
}
run(renderPass) {
renderPass.executeBundles(this.bundles);
}
clone() {
const cloned = new WebGPURenderItemBundles();
cloned.bundles = this.bundles;
return cloned;
}
}
/** @internal */
class WebGPUBundleList {
constructor(device) {
this.numDrawCalls = 0;
this._device = device;
this._list = new Array(10);
this._listLength = 0;
}
addBundle(bundle) {
if (!this._currentItemIsBundle) {
const item = new WebGPURenderItemBundles();
this._list[this._listLength++] = item;
this._currentBundleList = item.bundles;
this._currentItemIsBundle = true;
}
if (bundle) {
this._currentBundleList.push(bundle);
}
}
_finishBundle() {
if (this._currentItemIsBundle && this._bundleEncoder) {
this._currentBundleList.push(this._bundleEncoder.finish());
this._bundleEncoder = undefined;
this._currentItemIsBundle = false;
}
}
addItem(item) {
this._finishBundle();
this._list[this._listLength++] = item;
this._currentItemIsBundle = false;
}
getBundleEncoder(colorFormats, depthStencilFormat, sampleCount) {
if (!this._currentItemIsBundle) {
this.addBundle();
this._bundleEncoder = this._device.createRenderBundleEncoder({
colorFormats,
depthStencilFormat,
sampleCount: WebGPUTextureHelper.GetSample(sampleCount),
});
}
return this._bundleEncoder;
}
close() {
this._finishBundle();
}
run(renderPass) {
this.close();
for (let i = 0; i < this._listLength; ++i) {
this._list[i].run(renderPass);
}
}
reset() {
this._listLength = 0;
this._currentItemIsBundle = false;
this.numDrawCalls = 0;
}
clone() {
this.close();
const cloned = new WebGPUBundleList(this._device);
cloned._list = new Array(this._listLength);
cloned._listLength = this._listLength;
cloned.numDrawCalls = this.numDrawCalls;
for (let i = 0; i < this._listLength; ++i) {
cloned._list[i] = this._list[i].clone();
}
return cloned;
}
}
/** @internal */
class WebGPUQuerySet {
get querySet() {
return this._querySet;
}
constructor(engine, count, type, device, bufferManager, canUseMultipleBuffers = true, label) {
this._dstBuffers = [];
this._engine = engine;
this._device = device;
this._bufferManager = bufferManager;
this._count = count;
this._canUseMultipleBuffers = canUseMultipleBuffers;
this._querySet = device.createQuerySet({
label: label ?? "QuerySet",
type,
count,
});
this._queryBuffer = bufferManager.createRawBuffer(8 * count, BufferUsage.QueryResolve | BufferUsage.CopySrc, undefined, "QueryBuffer");
if (!canUseMultipleBuffers) {
this._dstBuffers.push(this._bufferManager.createRawBuffer(8 * this._count, BufferUsage.MapRead | BufferUsage.CopyDst, undefined, "QueryBufferNoMultipleBuffers"));
}
}
_getBuffer(firstQuery, queryCount) {
if (!this._canUseMultipleBuffers && this._dstBuffers.length === 0) {
return null;
}
const encoderResult = this._device.createCommandEncoder();
let buffer;
if (this._dstBuffers.length === 0) {
buffer = this._bufferManager.createRawBuffer(8 * this._count, BufferUsage.MapRead | BufferUsage.CopyDst, undefined, "QueryBufferAdditionalBuffer");
}
else {
buffer = this._dstBuffers[this._dstBuffers.length - 1];
this._dstBuffers.length--;
}
encoderResult.resolveQuerySet(this._querySet, firstQuery, queryCount, this._queryBuffer, 0);
encoderResult.copyBufferToBuffer(this._queryBuffer, 0, buffer, 0, 8 * queryCount);
this._device.queue.submit([encoderResult.finish()]);
return buffer;
}
async readValues(firstQuery = 0, queryCount = 1) {
const buffer = this._getBuffer(firstQuery, queryCount);
if (buffer === null) {
return null;
}
const engineId = this._engine.uniqueId;
try {
await buffer.mapAsync(1 /* WebGPUConstants.MapMode.Read */);
const arrayBuf = new BigUint64Array(buffer.getMappedRange()).slice();
buffer.unmap();
this._dstBuffers[this._dstBuffers.length] = buffer;
return arrayBuf;
}
catch (err) {
if (this._engine.isDisposed || this._engine.uniqueId !== engineId) {
// Engine disposed or context loss/restoration
return null;
}
throw err;
}
}
async readValue(firstQuery = 0) {
const buffer = this._getBuffer(firstQuery, 1);
if (buffer === null) {
return null;
}
const engineId = this._engine.uniqueId;
try {
await buffer.mapAsync(1 /* WebGPUConstants.MapMode.Read */);
const arrayBuf = new BigUint64Array(buffer.getMappedRange());
const value = Number(arrayBuf[0]);
buffer.unmap();
this._dstBuffers[this._dstBuffers.length] = buffer;
return value;
}
catch (err) {
if (this._engine.isDisposed || this._engine.uniqueId !== engineId) {
// Engine disposed or context loss/restoration
return 0;
}
throw err;
}
}
async readTwoValuesAndSubtract(firstQuery = 0) {
const buffer = this._getBuffer(firstQuery, 2);
if (buffer === null) {
return null;
}
const engineId = this._engine.uniqueId;
try {
await buffer.mapAsync(1 /* WebGPUConstants.MapMode.Read */);
const arrayBuf = new BigUint64Array(buffer.getMappedRange());
const value = Number(arrayBuf[1] - arrayBuf[0]);
buffer.unmap();
this._dstBuffers[this._dstBuffers.length] = buffer;
return value;
}
catch (err) {
if (this._engine.isDisposed || this._engine.uniqueId !== engineId) {
// Engine disposed or context loss/restoration
return 0;
}
throw err;
}
}
dispose() {
this._querySet.destroy();
this._bufferManager.releaseBuffer(this._queryBuffer);
for (let i = 0; i < this._dstBuffers.length; ++i) {
this._bufferManager.releaseBuffer(this._dstBuffers[i]);
}
}
}
/* eslint-disable babylonjs/available */
/* eslint-disable @typescript-eslint/naming-convention */
/** @internal */
class WebGPUTimestampQuery {
get gpuFrameTimeCounter() {
return this._gpuFrameTimeCounter;
}
constructor(engine, device, bufferManager) {
this._enabled = false;
this._gpuFrameTimeCounter = new PerfCounter();
this._measureDurationState = 0;
this._engine = engine;
this._device = device;
this._bufferManager = bufferManager;
}
get enable() {
return this._enabled;
}
set enable(value) {
if (this._enabled === value) {
return;
}
this._enabled = value;
this._measureDurationState = 0;
if (value) {
try {
this._measureDuration = new WebGPUDurationMeasure(this._engine, this._device, this._bufferManager, 2000, "QuerySet_TimestampQuery");
}
catch (e) {
this._enabled = false;
Logger.Error("Could not create a WebGPUDurationMeasure!\nError: " + e.message + "\nMake sure timestamp query is supported and enabled in your browser.");
return;
}
}
else {
this._measureDuration.dispose();
}
}
startFrame(commandEncoder) {
if (this._enabled && this._measureDurationState === 0) {
this._measureDuration.start(commandEncoder);
this._measureDurationState = 1;
}
}
endFrame(commandEncoder) {
if (this._measureDurationState === 1) {
this._measureDurationState = 2;
// eslint-disable-next-line @typescript-eslint/no-floating-promises, github/no-then
this._measureDuration.stop(commandEncoder).then((duration) => {
if (duration !== null && duration >= 0) {
this._gpuFrameTimeCounter.fetchNewFrame();
this._gpuFrameTimeCounter.addCount(duration, true);
}
this._measureDurationState = 0;
});
}
}
startPass(descriptor, index) {
if (this._enabled) {
this._measureDuration.startPass(descriptor, index);
}
else {
descriptor.timestampWrites = undefined;
}
}
endPass(index, gpuPerfCounter) {
if (!this._enabled || !gpuPerfCounter) {
return;
}
const currentFrameId = this._engine.frameId;
// eslint-disable-next-line @typescript-eslint/no-floating-promises, github/no-then
this._measureDuration.stopPass(index).then((duration_) => {
gpuPerfCounter._addDuration(currentFrameId, duration_ !== null && duration_ > 0 ? duration_ : 0);
});
}
dispose() {
this._measureDuration?.dispose();
}
}
/** @internal */
class WebGPUDurationMeasure {
constructor(engine, device, bufferManager, count = 2, querySetLabel) {
this._count = count;
this._querySet = new WebGPUQuerySet(engine, count, "timestamp" /* WebGPUConstants.QueryType.Timestamp */, device, bufferManager, true, querySetLabel);
}
start(encoder) {
encoder.writeTimestamp?.(this._querySet.querySet, 0);
}
async stop(encoder) {
encoder.writeTimestamp?.(this._querySet.querySet, 1);
return encoder.writeTimestamp ? await this._querySet.readTwoValuesAndSubtract(0) : 0;
}
startPass(descriptor, index) {
if (index + 3 > this._count) {
throw new Error("WebGPUDurationMeasure: index out of range (" + index + ")");
}
descriptor.timestampWrites = {
querySet: this._querySet.querySet,
beginningOfPassWriteIndex: index + 2,
endOfPassWriteIndex: index + 3,
};
}
async stopPass(index) {
return await this._querySet.readTwoValuesAndSubtract(index + 2);
}
dispose() {
this._querySet.dispose();
}
}
/** @internal */
class WebGPUOcclusionQuery {
get querySet() {
return this._querySet.querySet;
}
get hasQueries() {
return this._currentTotalIndices !== this._availableIndices.length;
}
canBeginQuery(index) {
if (this._frameQuerySetIsDirty === this._engine.frameId || this._queryFrameId[index] === this._engine.frameId) {
return false;
}
const canBegin = this._engine._getCurrentRenderPassWrapper().renderPassDescriptor.occlusionQuerySet !== undefined;
if (canBegin) {
this._queryFrameId[index] = this._engine.frameId;
}
return canBegin;
}
constructor(engine, device, bufferManager, startCount = 50, incrementCount = 100) {
this._availableIndices = [];
this._frameQuerySetIsDirty = -1;
this._queryFrameId = [];
this._engine = engine;
this._device = device;
this._bufferManager = bufferManager;
this._frameLastBuffer = -1;
this._currentTotalIndices = 0;
this._countIncrement = incrementCount;
this._allocateNewIndices(startCount);
}
createQuery() {
if (this._availableIndices.length === 0) {
this._allocateNewIndices();
}
const index = this._availableIndices[this._availableIndices.length - 1];
this._availableIndices.length--;
return index;
}
deleteQuery(index) {
this._availableIndices[this._availableIndices.length] = index;
}
isQueryResultAvailable(index) {
this._retrieveQueryBuffer();
return !!this._lastBuffer && index < this._lastBuffer.length;
}
getQueryResult(index) {
return Number(this._lastBuffer?.[index] ?? -1);
}
_retrieveQueryBuffer() {
if (this._lastBuffer && this._frameLastBuffer === this._engine.frameId) {
return;
}
if (this._frameLastBuffer !== this._engine.frameId) {
this._frameLastBuffer = this._engine.frameId;
// eslint-disable-next-line @typescript-eslint/no-floating-promises, github/no-then
this._querySet.readValues(0, this._currentTotalIndices).then((arrayBuffer) => {
this._lastBuffer = arrayBuffer;
});
}
}
_allocateNewIndices(numIndices) {
numIndices = numIndices ?? this._countIncrement;
this._delayQuerySetDispose();
for (let i = 0; i < numIndices; ++i) {
this._availableIndices.push(this._currentTotalIndices + i);
}
this._currentTotalIndices += numIndices;
this._querySet = new WebGPUQuerySet(this._engine, this._currentTotalIndices, "occlusion" /* WebGPUConstants.QueryType.Occlusion */, this._device, this._bufferManager, false, "QuerySet_OcclusionQuery_count_" + this._currentTotalIndices);
this._frameQuerySetIsDirty = this._engine.frameId;
}
_delayQuerySetDispose() {
const querySet = this._querySet;
if (querySet) {
// Wait a bit before disposing of the queryset, in case some queries are still running for it
setTimeout(() => querySet.dispose, 1000);
}
}
dispose() {
this._querySet?.dispose();
this._availableIndices.length = 0;
}
}
/**
* Class used to inline functions in shader code
*/
class ShaderCodeInliner {
/** Gets the code after the inlining process */
get code() {
return this._sourceCode;
}
/**
* Initializes the inliner
* @param sourceCode shader code source to inline
* @param numMaxIterations maximum number of iterations (used to detect recursive calls)
*/
constructor(sourceCode, numMaxIterations = 20) {
/** Gets or sets the debug mode */
this.debug = false;
this._sourceCode = sourceCode;
this._numMaxIterations = numMaxIterations;
this._functionDescr = [];
this.inlineToken = "#define inline";
}
/**
* Start the processing of the shader code
*/
processCode() {
if (this.debug) {
Logger.Log(`Start inlining process (code size=${this._sourceCode.length})...`);
}
this._collectFunctions();
this._processInlining(this._numMaxIterations);
if (this.debug) {
Logger.Log("End of inlining process.");
}
}
_collectFunctions() {
let startIndex = 0;
while (startIndex < this._sourceCode.length) {
// locate the function to inline and extract its name
const inlineTokenIndex = this._sourceCode.indexOf(this.inlineToken, startIndex);
if (inlineTokenIndex < 0) {
break;
}
const funcParamsStartIndex = this._sourceCode.indexOf("(", inlineTokenIndex + this.inlineToken.length);
if (funcParamsStartIndex < 0) {
if (this.debug) {
Logger.Warn(`Could not find the opening parenthesis after the token. startIndex=${startIndex}`);
}
startIndex = inlineTokenIndex + this.inlineToken.length;
continue;
}
const funcNameMatch = ShaderCodeInliner._RegexpFindFunctionNameAndType.exec(this._sourceCode.substring(inlineTokenIndex + this.inlineToken.length, funcParamsStartIndex));
if (!funcNameMatch) {
if (this.debug) {
Logger.Warn(`Could not extract the name/type of the function from: ${this._sourceCode.substring(inlineTokenIndex + this.inlineToken.length, funcParamsStartIndex)}`);
}
startIndex = inlineTokenIndex + this.inlineToken.length;
continue;
}
const [funcType, funcName] = [funcNameMatch[3], funcNameMatch[4]];
// extract the parameters of the function as a whole string (without the leading / trailing parenthesis)
const funcParamsEndIndex = ExtractBetweenMarkers("(", ")", this._sourceCode, funcParamsStartIndex);
if (funcParamsEndIndex < 0) {
if (this.debug) {
Logger.Warn(`Could not extract the parameters the function '${funcName}' (type=${funcType}). funcParamsStartIndex=${funcParamsStartIndex}`);
}
startIndex = inlineTokenIndex + this.inlineToken.length;
continue;
}
const funcParams = this._sourceCode.substring(funcParamsStartIndex + 1, funcParamsEndIndex);
// extract the body of the function (with the curly brackets)
const funcBodyStartIndex = SkipWhitespaces(this._sourceCode, funcParamsEndIndex + 1);
if (funcBodyStartIndex === this._sourceCode.length) {
if (this.debug) {
Logger.Warn(`Could not extract the body of the function '${funcName}' (type=${funcType}). funcParamsEndIndex=${funcParamsEndIndex}`);
}
startIndex = inlineTokenIndex + this.inlineToken.length;
continue;
}
const funcBodyEndIndex = ExtractBetweenMarkers("{", "}", this._sourceCode, funcBodyStartIndex);
if (funcBodyEndIndex < 0) {
if (this.debug) {
Logger.Warn(`Could not extract the body of the function '${funcName}' (type=${funcType}). funcBodyStartIndex=${funcBodyStartIndex}`);
}
startIndex = inlineTokenIndex + this.inlineToken.length;
continue;
}
const funcBody = this._sourceCode.substring(funcBodyStartIndex, funcBodyEndIndex + 1);
// process the parameters: extract each names
const params = RemoveComments(funcParams).split(",");
const paramNames = [];
for (let p = 0; p < params.length; ++p) {
const param = params[p].trim();
const idx = param.lastIndexOf(" ");
if (idx >= 0) {
paramNames.push(param.substring(idx + 1));
}
}
if (funcType !== "void") {
// for functions that return a value, we will replace "return" by "tempvarname = ", tempvarname being a unique generated name
paramNames.push("return");
}
// collect the function
this._functionDescr.push({
name: funcName,
type: funcType,
parameters: paramNames,
body: funcBody,
callIndex: 0,
});
startIndex = funcBodyEndIndex + 1;
// remove the function from the source code
const partBefore = inlineTokenIndex > 0 ? this._sourceCode.substring(0, inlineTokenIndex) : "";
const partAfter = funcBodyEndIndex + 1 < this._sourceCode.length - 1 ? this._sourceCode.substring(funcBodyEndIndex + 1) : "";
this._sourceCode = partBefore + partAfter;
startIndex -= funcBodyEndIndex + 1 - inlineTokenIndex;
}
if (this.debug) {
Logger.Log(`Collect functions: ${this._functionDescr.length} functions found. functionDescr=${this._functionDescr}`);
}
}
_processInlining(numMaxIterations = 20) {
while (numMaxIterations-- >= 0) {
if (!this._replaceFunctionCallsByCode()) {
break;
}
}
if (this.debug) {
Logger.Log(`numMaxIterations is ${numMaxIterations} after inlining process`);
}
return numMaxIterations >= 0;
}
_replaceFunctionCallsByCode() {
let doAgain = false;
for (const func of this._functionDescr) {
const { name, type, parameters, body } = func;
let startIndex = 0;
while (startIndex < this._sourceCode.length) {
// Look for the function name in the source code
const functionCallIndex = this._sourceCode.indexOf(name, startIndex);
if (functionCallIndex < 0) {
break;
}
// Make sure "name" is not part of a bigger string
if (functionCallIndex === 0 || IsIdentifierChar(this._sourceCode.charAt(functionCallIndex - 1))) {
startIndex = functionCallIndex + name.length;
continue;
}
// Find the opening parenthesis
const callParamsStartIndex = SkipWhitespaces(this._sourceCode, functionCallIndex + name.length);
if (callParamsStartIndex === this._sourceCode.length || this._sourceCode.charAt(callParamsStartIndex) !== "(") {
startIndex = functionCallIndex + name.length;
continue;
}
// extract the parameters of the function call as a whole string (without the leading / trailing parenthesis)
const callParamsEndIndex = ExtractBetweenMarkers("(", ")", this._sourceCode, callParamsStartIndex);
if (callParamsEndIndex < 0) {
if (this.debug) {
Logger.Warn(`Could not extract the parameters of the function call. Function '${name}' (type=${type}). callParamsStartIndex=${callParamsStartIndex}`);
}
startIndex = functionCallIndex + name.length;
continue;
}
const callParams = this._sourceCode.substring(callParamsStartIndex + 1, callParamsEndIndex);
// process the parameter call: extract each names
// this function split the parameter list used in the function call at ',' boundaries by taking care of potential parenthesis like in:
// myfunc(a, vec2(1., 0.), 4.)
const splitParameterCall = (s) => {
const parameters = [];
let curIdx = 0, startParamIdx = 0;
while (curIdx < s.length) {
if (s.charAt(curIdx) === "(") {
const idx2 = ExtractBetweenMarkers("(", ")", s, curIdx);
if (idx2 < 0) {
return null;
}
curIdx = idx2;
}
else if (s.charAt(curIdx) === ",") {
parameters.push(s.substring(startParamIdx, curIdx));
startParamIdx = curIdx + 1;
}
curIdx++;
}
if (startParamIdx < curIdx) {
parameters.push(s.substring(startParamIdx, curIdx));
}
return parameters;
};
const params = splitParameterCall(RemoveComments(callParams));
if (params === null) {
if (this.debug) {
Logger.Warn(`Invalid function call: can't extract the parameters of the function call. Function '${name}' (type=${type}). callParamsStartIndex=${callParamsStartIndex}, callParams=` +
callParams);
}
startIndex = functionCallIndex + name.length;
continue;
}
const paramNames = [];
for (let p = 0; p < params.length; ++p) {
const param = params[p].trim();
paramNames.push(param);
}
const retParamName = type !== "void" ? name + "_" + func.callIndex++ : null;
if (retParamName) {
paramNames.push(retParamName + " =");
}
if (paramNames.length !== parameters.length) {
if (this.debug) {
Logger.Warn(`Invalid function call: not the same number of parameters for the call than the number expected by the function. Function '${name}' (type=${type}). function parameters=${parameters}, call parameters=${paramNames}`);
}
startIndex = functionCallIndex + name.length;
continue;
}
startIndex = callParamsEndIndex + 1;
// replace the function call by the body function
const funcBody = this._replaceNames(body, parameters, paramNames);
let partBefore = functionCallIndex > 0 ? this._sourceCode.substring(0, functionCallIndex) : "";
const partAfter = callParamsEndIndex + 1 < this._sourceCode.length - 1 ? this._sourceCode.substring(callParamsEndIndex + 1) : "";
if (retParamName) {
// case where the function returns a value. We generate:
// FUNCTYPE retParamName;
// {function body}
// and replace the function call by retParamName
const injectDeclarationIndex = FindBackward(this._sourceCode, functionCallIndex - 1, "\n", "{");
partBefore = this._sourceCode.substring(0, injectDeclarationIndex + 1);
const partBetween = this._sourceCode.substring(injectDeclarationIndex + 1, functionCallIndex);
this._sourceCode = partBefore + type + " " + retParamName + ";\n" + funcBody + "\n" + partBetween + retParamName + partAfter;
if (this.debug) {
Logger.Log(`Replace function call by code. Function '${name}' (type=${type}). injectDeclarationIndex=${injectDeclarationIndex}, call parameters=${paramNames}`);
}
}
else {
// simple case where the return value of the function is "void"
this._sourceCode = partBefore + funcBody + partAfter;
startIndex += funcBody.length - (callParamsEndIndex + 1 - functionCallIndex);
if (this.debug) {
Logger.Log(`Replace function call by code. Function '${name}' (type=${type}). functionCallIndex=${functionCallIndex}, call parameters=${paramNames}`);
}
}
doAgain = true;
}
}
return doAgain;
}
_replaceNames(code, sources, destinations) {
for (let i = 0; i < sources.length; ++i) {
const source = new RegExp(EscapeRegExp(sources[i]), "g"), sourceLen = sources[i].length, destination = destinations[i];
code = code.replace(source, (match, ...args) => {
const offset = args[0];
// Make sure "source" is not part of a bigger identifier (for eg, if source=view and we matched it with viewDirection)
if (IsIdentifierChar(code.charAt(offset - 1)) || IsIdentifierChar(code.charAt(offset + sourceLen))) {
return sources[i];
}
return destination;
});
}
return code;
}
}
ShaderCodeInliner._RegexpFindFunctionNameAndType = /((\s+?)(\w+)\s+(\w+)\s*?)$/;
/** @internal */
class WebGPUTintWASM {
// eslint-disable-next-line @typescript-eslint/naming-convention
async initTwgsl(twgslOptions) {
if (WebGPUTintWASM._Twgsl) {
return;
}
twgslOptions = twgslOptions || {};
twgslOptions = {
...WebGPUTintWASM._TwgslDefaultOptions,
...twgslOptions,
};
if (twgslOptions.twgsl) {
WebGPUTintWASM._Twgsl = twgslOptions.twgsl;
return;
}
if (twgslOptions.jsPath && twgslOptions.wasmPath) {
await Tools.LoadBabylonScriptAsync(twgslOptions.jsPath);
}
if (self.twgsl) {
// eslint-disable-next-line require-atomic-updates
WebGPUTintWASM._Twgsl = await self.twgsl(Tools.GetBabylonScriptURL(twgslOptions.wasmPath));
return;
}
throw new Error("twgsl is not available.");
}
convertSpirV2WGSL(code, disableUniformityAnalysis = false) {
const ccode = WebGPUTintWASM._Twgsl.convertSpirV2WGSL(code, WebGPUTintWASM.DisableUniformityAnalysis || disableUniformityAnalysis);
if (WebGPUTintWASM.ShowWGSLShaderCode) {
Logger.Log(ccode);
Logger.Log("***********************************************");
}
return WebGPUTintWASM.DisableUniformityAnalysis || disableUniformityAnalysis ? "diagnostic(off, derivative_uniformity);\n" + ccode : ccode;
}
}
// Default twgsl options.
WebGPUTintWASM._TwgslDefaultOptions = {
jsPath: `${Tools._DefaultCdnUrl}/twgsl/twgsl.js`,
wasmPath: `${Tools._DefaultCdnUrl}/twgsl/twgsl.wasm`,
};
WebGPUTintWASM.ShowWGSLShaderCode = false;
WebGPUTintWASM.DisableUniformityAnalysis = false;
WebGPUTintWASM._Twgsl = null;
/** @internal */
class WebGPUSnapshotRendering {
constructor(engine, renderingMode, bundleList) {
this._record = false;
this._play = false;
this._playBundleListIndex = 0;
this._allBundleLists = [];
this._enabled = false;
this.showDebugLogs = false;
this._engine = engine;
this._mode = renderingMode;
this._bundleList = bundleList;
}
get enabled() {
return this._enabled;
}
get play() {
return this._play;
}
get record() {
return this._record;
}
set enabled(activate) {
this._log("enabled", `activate=${activate}, mode=${this._mode}`);
this._allBundleLists.length = 0;
if (this._record) {
// A recording pass is in flight: _mode is temporarily SNAPSHOTRENDERING_STANDARD and is normally
// restored to _modeSaved at the next endFrame(). Restore it now so that flipping _record off
// doesn't leave _mode stuck on STANDARD — otherwise the next "enabled = true" would save STANDARD
// as the user-requested mode and the engine would permanently fall back to STANDARD (forum #63365).
this._mode = this._modeSaved;
}
this._record = this._enabled = activate;
this._play = false;
if (activate) {
this._modeSaved = this._mode;
this._mode = Constants.SNAPSHOTRENDERING_STANDARD; // need to reset to standard for the recording pass to avoid some code being bypassed
}
}
get mode() {
return this._mode;
}
set mode(mode) {
if (this._record) {
this._modeSaved = mode;
}
else {
this._mode = mode;
}
}
endRenderPass(currentRenderPass) {
if (!this._record && !this._play) {
// Snapshot rendering mode is not enabled
return false;
}
let bundleList = null;
if (this._record) {
bundleList = this._bundleList.clone();
this._allBundleLists.push(bundleList);
this._bundleList.reset();
this._log("endRenderPass", `bundleList recorded at position #${this._allBundleLists.length - 1}`);
}
else {
// We are playing the snapshot
if (this._playBundleListIndex >= this._allBundleLists.length) {
this._log("endRenderPass", `empty or out-of-sync bundleList (_allBundleLists.length=${this._allBundleLists.length}, playBundleListIndex=${this._playBundleListIndex})`);
}
else {
this._log("endRenderPass", `run bundleList #${this._playBundleListIndex}`);
bundleList = this._allBundleLists[this._playBundleListIndex++];
}
}
if (bundleList) {
bundleList.run(currentRenderPass);
if (this._mode === Constants.SNAPSHOTRENDERING_FAST) {
this._engine._reportDrawCall(bundleList.numDrawCalls);
}
}
return true;
}
endFrame() {
if (this._record) {
// We stop recording and switch to replay mode for the next frames
this._record = false;
this._play = true;
this._mode = this._modeSaved;
this._log("endFrame", "bundles recorded, switching to play mode");
}
this._playBundleListIndex = 0;
}
reset() {
this._log("reset", "called");
this.enabled = false;
this.enabled = true;
}
_log(funcName, message) {
if (this.showDebugLogs) {
Logger.Log(`[Frame: ${this._engine.frameId}] WebGPUSnapshotRendering:${funcName} - ${message}`);
}
}
}
/**
* Nothing specific to WebGPU in this class, but the spec is not final yet so let's remove it later on
* if it is not needed
* @internal
**/
class WebGPUExternalTexture extends ExternalTexture {
constructor(video) {
super(video);
}
}
/** This file must only contain pure code and pure imports */
/* eslint-disable babylonjs/available */
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 = /*#__PURE__*/ new Color4();
/**
* The web GPU engine class provides support for WebGPU version of babylon.js.
* @since 5.0.0
*/
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)
*/
// eslint-disable-next-line no-restricted-syntax
static get IsSupportedAsync() {
return !navigator.gpu
? Promise.resolve(false)
: navigator.gpu
.requestAdapter()
// eslint-disable-next-line github/no-then
.then((adapter) => !!adapter, () => false)
// eslint-disable-next-line github/no-then
.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;
}
/**
* Gets the number of samples used by the current render target
* @returns the current sample count, or 1 when multisampling is disabled
*/
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
*/
// eslint-disable-next-line @typescript-eslint/promise-function-async, no-restricted-syntax
static CreateAsync(canvas, options = {}) {
const engine = new WebGPUEngine(canvas, options);
return new Promise((resolve) => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises, github/no-then
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 = {}) {
RegisterAbstractEngineDom();
RegisterAbstractEngineRenderPass();
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: "",
subgroupMinSize: 0,
subgroupMaxSize: 0,
isFallbackAdapter: false,
};
/** @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,
};
this._currentVertexBuffers = {};
this._currentOverrideVertexBuffers = null;
this._currentIndexBuffer = null;
this._colorWriteLocal = true;
this._forceEnableEffect = false;
this._internalFrameCounter = 0;
/**
* 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;
this._enableGPUDebugMarkers = options.enableGPUDebugMarkers;
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
*/
// eslint-disable-next-line @typescript-eslint/promise-function-async, no-restricted-syntax
prepareGlslangAndTintAsync() {
if (!this._workingGlslangAndTintPromise) {
this._workingGlslangAndTintPromise = new Promise((resolve) => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises, github/no-then
this._initGlslangAsync(this._glslangOptions ?? this._options?.glslangOptions).then((glslang) => {
this._glslang = glslang;
this._tintWASM = new WebGPUTintWASM();
// eslint-disable-next-line @typescript-eslint/no-floating-promises, github/no-then
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.
*/
// eslint-disable-next-line @typescript-eslint/promise-function-async, no-restricted-syntax
initAsync(glslangOptions, twgslOptions) {
this.uniqueId = WebGPUEngine._InstanceId++;
this._glslangOptions = glslangOptions;
this._twgslOptions = twgslOptions;
return (navigator
.gpu.requestAdapter(this._options)
// eslint-disable-next-line github/no-then
.then(async (adapter) => {
if (!adapter) {
// eslint-disable-next-line no-throw-literal
throw "Could not retrieve a WebGPU adapter (adapter is null or undefined).";
}
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 await this._adapter.requestDevice(deviceDescriptor);
}
})
// eslint-disable-next-line github/no-then
.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(`[Frame ${this._frameId}] WebGPU uncaptured error (${numUncapturedErrors + 1}): ${event.error} - ${event.error.message}`);
}
else if (numUncapturedErrors++ === this.numMaxUncapturedErrors) {
Logger.Warn(`[Frame ${this._frameId}] WebGPU uncaptured error: too many warnings (${this.numMaxUncapturedErrors}), no more warnings will be reported to the console for this engine.`);
}
});
if (!this._doNotHandleContextLost) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises, github/no-then
this._device.lost?.then((info) => {
if (this._isDisposed) {
return;
}
this._contextWasLost = true;
Logger.Warn("WebGPU context lost. " + info);
this.onContextLostObservable.notifyObservers(this);
// eslint-disable-next-line @typescript-eslint/no-misused-promises
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;
});
});
}
})
// eslint-disable-next-line github/no-then
.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._cacheTextureViews = new WebGPUCacheTextureView();
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]), BufferUsage.Uniform | BufferUsage.CopyDst, "UBInvertY");
this._ubDontInvertY = this._bufferManager.createBuffer(new Float32Array([1, 0]), BufferUsage.Uniform | BufferUsage.CopyDst, "UBDontInvertY");
const frameCounter = this._internalFrameCounter++;
this._uploadEncoderDescriptor.label = `[${this.frameId}|${frameCounter}] - UploadEncoder`;
this._renderEncoderDescriptor.label = `[${this.frameId}|${frameCounter}] - RenderEncoder`;
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._dummyIndexBuffer = this._bufferManager.createBuffer(new Uint16Array([0, 0, 0, 0]), BufferUsage.Storage | BufferUsage.CopyDst, "DummyIndices");
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 = Constants.LEQUAL;
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();
})
// eslint-disable-next-line github/no-then
.catch((e) => {
Logger.Error("A fatal error occurred during WebGPU creation/initialization.");
throw e;
}));
}
// eslint-disable-next-line @typescript-eslint/promise-function-async, no-restricted-syntax
_initGlslangAsync(glslangOptions) {
glslangOptions = glslangOptions || {};
glslangOptions = {
...WebGPUEngine._GlslangDefaultOptions,
...glslangOptions,
};
if (glslangOptions.glslang) {
return glslangOptions.glslang;
}
if (self.glslang) {
return self.glslang(glslangOptions.wasmPath);
}
if (glslangOptions.jsPath && glslangOptions.wasmPath) {
// eslint-disable-next-line github/no-then
return Tools.LoadBabylonScriptAsync(glslangOptions.jsPath).then(() => {
return self.glslang(Tools.GetBabylonScriptURL(glslangOptions.wasmPath));
});
}
throw new Error("glslang is not available");
}
_initializeLimits() {
// Init caps
const textureFormatsTier1 = this._deviceEnabledExtensions.indexOf("texture-formats-tier1" /* WebGPUConstants.FeatureName.TextureFormatsTier1 */) >= 0;
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),
maxUniformBuffersPerShaderStage: this._deviceLimits.maxUniformBuffersPerShaderStage,
shaderFloatPrecision: 23, // WGSL always uses IEEE-754 binary32 floats (which have 23 bits of significand)
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,
blendFloat: this._deviceEnabledExtensions.indexOf("float32-blendable" /* WebGPUConstants.FeatureName.Float32Blendable */) >= 0,
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: textureFormatsTier1,
blendParametersPerTarget: true,
dualSourceBlending: this._deviceEnabledExtensions.includes("dual-source-blending" /* WebGPUConstants.FeatureName.DualSourceBlending */),
supportReadWriteStorageTextures: typeof navigator !== "undefined" && navigator.gpu?.wgslLanguageFeatures?.has("readonly_and_readwrite_storage_textures") === true,
};
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,
};
this._alphaState = new AlphaState(this._caps.blendParametersPerTarget);
}
_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 */,
};
const frameCounter = this._internalFrameCounter++;
this._mainRenderPassWrapper.renderPassDescriptor = {
label: `[${this.frameId}|${frameCounter}] - MainRenderPass`,
colorAttachments: mainColorAttachments,
depthStencilAttachment: mainDepthAttachment,
};
this.beginFrame();
this._startMainRenderPass(true, null, true, false);
this._endCurrentRenderPass();
this._frameId--; // We don't want to count the frame as a real frame, because it was only used to initialize the depth texture
this.endFrame();
}
/**
* 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
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
async _createImageBitmapFromSource(imageSource, options) {
return await 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;
}
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, this._alphaState._numTargetEnabled);
}
/**
* 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 = Constants.LEQUAL;
this._alphaState.reset();
this._resetAlphaMode();
this._cacheRenderPipeline.setAlphaBlendFactors(this._alphaState._blendFunctionParameters, this._alphaState._blendEquationParameters);
this._cacheRenderPipeline.setAlphaBlendEnabled(this._alphaState._alphaBlend, this._alphaState._numTargetEnabled);
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);
}
}
/**
* @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);
}
}
_scissorIsActive() {
return this._scissorCached.x !== 0 || this._scissorCached.y !== 0 || this._scissorCached.z !== 0 || this._scissorCached.w !== 0;
}
/**
* Enable scissor test on a specific rectangle
* @param x defines left coordinate of the rectangle
* @param y defines top coordinate of the rectangle
* @param width defines width of the rectangle
* @param height defines height of the rectangle
*/
enableScissor(x, y, width, height) {
this._scissorCached.x = x;
this._scissorCached.y = y;
this._scissorCached.z = width;
this._scissorCached.w = height;
}
/**
* Disable previously set scissor test rectangle
*/
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;
if (update) {
this._stencilRefsCurrent = this._stencilStateComposer.funcRef;
}
return update;
}
_applyStencilRef(bundleList) {
if (bundleList) {
bundleList.addItem(new WebGPURenderItemStencilRef(this._stencilStateComposer.funcRef ?? 0));
}
else {
this._getCurrentRenderPass().setStencilReference(this._stencilStateComposer.funcRef ?? 0);
}
}
_mustUpdateBlendColor() {
const colorBlend = this._alphaState._blendConstants;
const update = colorBlend[0] !== this._blendColorsCurrent[0] ||
colorBlend[1] !== this._blendColorsCurrent[1] ||
colorBlend[2] !== this._blendColorsCurrent[2] ||
colorBlend[3] !== this._blendColorsCurrent[3];
if (update) {
this._blendColorsCurrent[0] = colorBlend[0];
this._blendColorsCurrent[1] = colorBlend[1];
this._blendColorsCurrent[2] = colorBlend[2];
this._blendColorsCurrent[3] = colorBlend[3];
}
return update;
}
_applyBlendColor(bundleList) {
if (bundleList) {
bundleList.addItem(new WebGPURenderItemBlendColor(this._alphaState._blendConstants.slice()));
}
else {
this._getCurrentRenderPass().setBlendConstant(this._alphaState._blendConstants);
}
}
_resetRenderPassStates() {
this._viewportsCurrent.x = this._viewportsCurrent.y = this._viewportsCurrent.w = this._viewportsCurrent.h = 0;
this._scissorsCurrent.x = this._scissorsCurrent.y = this._scissorsCurrent.w = this._scissorsCurrent.h = 0;
this._stencilRefsCurrent = -1;
this._blendColorsCurrent[0] = this._blendColorsCurrent[1] = this._blendColorsCurrent[2] = this._blendColorsCurrent[3] = null;
}
/**
* Clear the current render buffer or the current render target (if any is set up)
* @param color defines the color to use
* @param backBuffer defines if the back buffer must be cleared
* @param depth defines if the depth buffer must be cleared
* @param stencil defines if the stencil buffer must be cleared
* @param stencilClearValue defines the value to use to clear the stencil buffer (default is 0)
*/
clear(color, backBuffer, depth, stencil = false, stencilClearValue = 0) {
// Some PGs are using color3...
if (color && color.a === undefined) {
color.a = 1;
}
if (stencil) {
this._clearStencilValue = stencilClearValue;
}
const hasScissor = this._scissorIsActive();
// We need to recreate the render pass so that the new parameters for clear color / depth / stencil are taken into account
if (this._currentRenderTarget) {
if (hasScissor) {
if (!this._currentRenderPass) {
this._startRenderTargetRenderPass(this._currentRenderTarget, false, backBuffer ? color : null, depth, stencil);
}
this._applyScissor(!this.compatibilityMode ? this._bundleList : null);
this._clearFullQuad(backBuffer ? color : null, depth, stencil);
}
else {
if (this._currentRenderPass) {
this._endCurrentRenderPass();
}
this._startRenderTargetRenderPass(this._currentRenderTarget, true, backBuffer ? color : null, depth, stencil);
}
}
else {
if (!this._currentRenderPass || !hasScissor) {
this._startMainRenderPass(!hasScissor, backBuffer ? color : null, depth, stencil);
}
if (hasScissor) {
this._applyScissor(!this.compatibilityMode ? this._bundleList : null);
this._clearFullQuad(backBuffer ? color : null, depth, stencil);
}
}
}
_clearFullQuad(clearColor, clearDepth, clearStencil) {
const renderPass = !this.compatibilityMode ? null : this._getCurrentRenderPass();
this._clearQuad.setColorFormat(this._colorFormat);
this._clearQuad.setDepthStencilFormat(this._depthTextureFormat);
this._clearQuad.setMRTAttachments(this._cacheRenderPipeline.mrtAttachments ?? [], this._cacheRenderPipeline.mrtTextureArray ?? [], this._cacheRenderPipeline.mrtTextureCount);
if (!this.compatibilityMode) {
this._bundleList.addItem(new WebGPURenderItemStencilRef(this._clearStencilValue));
}
else {
renderPass.setStencilReference(this._clearStencilValue);
}
const bundle = this._clearQuad.clear(renderPass, clearColor, clearDepth, clearStencil, this.currentSampleCount);
if (!this.compatibilityMode) {
this._bundleList.addBundle(bundle);
this._applyStencilRef(this._bundleList);
this._reportDrawCall();
}
else {
this._applyStencilRef(null);
}
}
//------------------------------------------------------------------------------
// Vertex/Index/Storage Buffers
//------------------------------------------------------------------------------
/**
* Creates a vertex buffer
* @param data the data or the size for the vertex buffer
* @param _updatable whether the buffer should be created as updatable
* @param label defines the label of the buffer (for debug purpose)
* @returns the new buffer
*/
createVertexBuffer(data, _updatable, label) {
let view;
if (data instanceof Array) {
view = new Float32Array(data);
}
else if (ArrayBuffer.isView(data)) {
view = data;
}
else {
view = new Uint8Array(data);
}
const dataBuffer = this._bufferManager.createBuffer(view, BufferUsage.Vertex | BufferUsage.CopyDst | BufferUsage.Storage, label);
return dataBuffer;
}
/**
* Creates a vertex buffer
* @param data the data for the dynamic vertex buffer
* @param label defines the label of the buffer (for debug purpose)
* @returns the new buffer
*/
createDynamicVertexBuffer(data, label) {
return this.createVertexBuffer(data, undefined, label);
}
/**
* Creates a new index buffer
* @param indices defines the content of the index buffer
* @param _updatable defines if the index buffer must be updatable
* @param label defines the label of the buffer (for debug purpose)
* @returns a new buffer
*/
createIndexBuffer(indices, _updatable, label) {
let is32Bits = true;
let view;
if (indices instanceof Uint32Array || indices instanceof Int32Array) {
view = indices;
}
else if (indices instanceof Uint16Array) {
view = indices;
is32Bits = false;
}
else {
for (let index = 0; index < indices.length; index++) {
if (indices[index] > 65535) {
view = new Uint32Array(indices);
break;
}
}
if (!view) {
view = new Uint16Array(indices);
is32Bits = false;
}
}
const dataBuffer = this._bufferManager.createBuffer(view, BufferUsage.Index | BufferUsage.CopyDst | BufferUsage.Storage, label);
dataBuffer.is32Bits = is32Bits;
return dataBuffer;
}
/**
* Update a dynamic index buffer
* @param indexBuffer defines the target index buffer
* @param indices defines the data to update
* @param offset defines the offset in the target index buffer where update should start
*/
updateDynamicIndexBuffer(indexBuffer, indices, offset = 0) {
const gpuBuffer = indexBuffer;
let view;
if (indexBuffer.is32Bits) {
view = indices instanceof Uint32Array ? indices : new Uint32Array(indices);
}
else {
view = indices instanceof Uint16Array ? indices : new Uint16Array(indices);
}
this._bufferManager.setSubData(gpuBuffer, offset, view);
}
/**
* Updates a dynamic vertex buffer.
* @param vertexBuffer the vertex buffer to update
* @param data the data used to update the vertex buffer
* @param byteOffset the byte offset of the data
* @param byteLength the byte length of the data
*/
updateDynamicVertexBuffer(vertexBuffer, data, byteOffset, byteLength) {
const dataBuffer = vertexBuffer;
if (byteOffset === undefined) {
byteOffset = 0;
}
let view;
if (byteLength === undefined) {
if (data instanceof Array) {
view = new Float32Array(data);
}
else if (ArrayBuffer.isView(data)) {
view = data;
}
else {
view = new Uint8Array(data);
}
byteLength = view.byteLength;
}
else {
if (data instanceof Array) {
view = new Float32Array(data);
}
else if (ArrayBuffer.isView(data)) {
view = data;
}
else {
view = new Uint8Array(data);
}
}
this._bufferManager.setSubData(dataBuffer, byteOffset, view, 0, byteLength);
}
/**
* @internal
*/
_createBuffer(data, creationFlags, label) {
let view;
if (data instanceof Array) {
view = new Float32Array(data);
}
else if (ArrayBuffer.isView(data)) {
view = data;
}
else {
view = new Uint8Array(data);
}
let flags = 0;
if (creationFlags & Constants.BUFFER_CREATIONFLAG_READ) {
flags |= BufferUsage.CopySrc;
}
if (creationFlags & Constants.BUFFER_CREATIONFLAG_WRITE) {
flags |= BufferUsage.CopyDst;
}
if (creationFlags & Constants.BUFFER_CREATIONFLAG_UNIFORM) {
flags |= BufferUsage.Uniform;
}
if (creationFlags & Constants.BUFFER_CREATIONFLAG_VERTEX) {
flags |= BufferUsage.Vertex;
}
if (creationFlags & Constants.BUFFER_CREATIONFLAG_INDEX) {
flags |= BufferUsage.Index;
}
if (creationFlags & Constants.BUFFER_CREATIONFLAG_STORAGE) {
flags |= BufferUsage.Storage;
}
if (creationFlags & Constants.BUFFER_CREATIONFLAG_INDIRECT) {
flags |= BufferUsage.Indirect;
}
return this._bufferManager.createBuffer(view, flags, label);
}
/**
* @internal
*/
bindBuffersDirectly() {
// eslint-disable-next-line no-throw-literal
throw "Not implemented on WebGPU";
}
/**
* @internal
*/
updateAndBindInstancesBuffer() {
// eslint-disable-next-line no-throw-literal
throw "Not implemented on WebGPU";
}
/**
* Unbind all instance attributes
*/
unbindInstanceAttributes() {
// Does nothing
}
/**
* Bind a list of vertex buffers with the engine
* @param vertexBuffers defines the list of vertex buffers to bind
* @param indexBuffer defines the index buffer to bind
* @param _effect defines the effect associated with the vertex buffers
* @param overrideVertexBuffers defines optional list of avertex buffers that overrides the entries in vertexBuffers
*/
bindBuffers(vertexBuffers, indexBuffer, _effect, overrideVertexBuffers) {
this._currentVertexBuffers = vertexBuffers;
this._currentIndexBuffer = indexBuffer;
this._currentOverrideVertexBuffers = overrideVertexBuffers ?? null;
this._cacheRenderPipeline.setBuffers(this._currentVertexBuffers, this._currentIndexBuffer, this._currentOverrideVertexBuffers);
}
/**
* @internal
*/
_releaseBuffer(buffer) {
return this._bufferManager.releaseBuffer(buffer);
}
//------------------------------------------------------------------------------
// Uniform Buffers
//------------------------------------------------------------------------------
/**
* Create an uniform buffer
* @see https://doc.babylonjs.com/setup/support/webGL2#uniform-buffer-objets
* @param elements defines the content of the uniform buffer
* @param label defines a name for the buffer (for debugging purpose)
* @returns the webGL uniform buffer
*/
createUniformBuffer(elements, label) {
let view;
if (elements instanceof Array) {
view = new Float32Array(elements);
}
else {
view = elements;
}
const dataBuffer = this._bufferManager.createBuffer(view, BufferUsage.Uniform | BufferUsage.CopyDst, label);
return dataBuffer;
}
/**
* Create a dynamic uniform buffer (no different from a non dynamic uniform buffer in WebGPU)
* @see https://doc.babylonjs.com/setup/support/webGL2#uniform-buffer-objets
* @param elements defines the content of the uniform buffer
* @param label defines a name for the buffer (for debugging purpose)
* @returns the webGL uniform buffer
*/
createDynamicUniformBuffer(elements, label) {
return this.createUniformBuffer(elements, label);
}
/**
* Update an existing uniform buffer
* @see https://doc.babylonjs.com/setup/support/webGL2#uniform-buffer-objets
* @param uniformBuffer defines the target uniform buffer
* @param elements defines the content to update
* @param offset defines the offset in the uniform buffer where update should start
* @param count defines the size of the data to update
*/
updateUniformBuffer(uniformBuffer, elements, offset, count) {
if (offset === undefined) {
offset = 0;
}
const dataBuffer = uniformBuffer;
let view;
if (count === undefined) {
if (elements instanceof Float32Array) {
view = elements;
}
else {
view = new Float32Array(elements);
}
count = view.byteLength;
}
else {
if (elements instanceof Float32Array) {
view = elements;
}
else {
view = new Float32Array(elements);
}
}
this._bufferManager.setSubData(dataBuffer, offset, view, 0, count);
}
/**
* Bind a buffer to the current draw context
* @param buffer defines the buffer to bind
* @param _location not used in WebGPU
* @param name Name of the uniform variable to bind
*/
bindUniformBufferBase(buffer, _location, name) {
this._currentDrawContext.setBuffer(name, buffer);
}
/**
* Unused in WebGPU
*/
bindUniformBlock() { }
//------------------------------------------------------------------------------
// Effects
//------------------------------------------------------------------------------
/**
* Create a new effect (used to store vertex/fragment shaders)
* @param baseName defines the base name of the effect (The name of file without .fragment.fx or .vertex.fx)
* @param attributesNamesOrOptions defines either a list of attribute names or an IEffectCreationOptions object
* @param uniformsNamesOrEngine defines either a list of uniform names or the engine to use
* @param samplers defines an array of string used to represent textures
* @param defines defines the string containing the defines to use to compile the shaders
* @param fallbacks defines the list of potential fallbacks to use if shader compilation fails
* @param onCompiled defines a function to call when the effect creation is successful
* @param onError defines a function to call when the effect creation has failed
* @param indexParameters defines an object containing the index values to use to compile shaders (like the maximum number of simultaneous lights)
* @param shaderLanguage the language the shader is written in (default: GLSL)
* @param extraInitializationsAsync additional async code to run before preparing the effect
* @returns the new Effect
*/
createEffect(baseName, attributesNamesOrOptions, uniformsNamesOrEngine, samplers, defines, fallbacks, onCompiled, onError, indexParameters, shaderLanguage = 0 /* ShaderLanguage.GLSL */, extraInitializationsAsync) {
const vertex = typeof baseName === "string" ? baseName : baseName.vertexToken || baseName.vertexSource || baseName.vertexElement || baseName.vertex;
const fragment = typeof baseName === "string" ? baseName : baseName.fragmentToken || baseName.fragmentSource || baseName.fragmentElement || baseName.fragment;
const globalDefines = this._getGlobalDefines();
const isOptions = attributesNamesOrOptions.attributes !== undefined;
let fullDefines = defines ?? attributesNamesOrOptions.defines ?? "";
if (globalDefines) {
fullDefines += "\n" + globalDefines;
}
const name = vertex + "+" + fragment + "@" + fullDefines;
if (this._compiledEffects[name]) {
const compiledEffect = this._compiledEffects[name];
if (onCompiled && compiledEffect.isReady()) {
onCompiled(compiledEffect);
}
compiledEffect._refCount++;
return compiledEffect;
}
const effect = new Effect(baseName, attributesNamesOrOptions, isOptions ? this : uniformsNamesOrEngine, samplers, this, defines, fallbacks, onCompiled, onError, indexParameters, name, attributesNamesOrOptions.shaderLanguage ?? shaderLanguage, attributesNamesOrOptions.extraInitializationsAsync ?? extraInitializationsAsync);
this._compiledEffects[name] = effect;
return effect;
}
_compileRawShaderToSpirV(source, type) {
return this._glslang.compileGLSL(source, type);
}
_compileShaderToSpirV(source, type, defines, shaderVersion) {
return this._compileRawShaderToSpirV(shaderVersion + (defines ? defines + "\n" : "") + source, type);
}
_getWGSLShader(source, type, defines) {
if (defines) {
defines = "//" + defines.split("\n").join("\n//") + "\n";
}
else {
defines = "";
}
return defines + source;
}
_createPipelineStageDescriptor(vertexShader, fragmentShader, shaderLanguage, disableUniformityAnalysisInVertex, disableUniformityAnalysisInFragment) {
if (this._tintWASM && shaderLanguage === 0 /* ShaderLanguage.GLSL */) {
vertexShader = this._tintWASM.convertSpirV2WGSL(vertexShader, disableUniformityAnalysisInVertex);
fragmentShader = this._tintWASM.convertSpirV2WGSL(fragmentShader, disableUniformityAnalysisInFragment);
}
return {
vertexStage: {
module: this._device.createShaderModule({
label: "vertex",
code: vertexShader,
}),
entryPoint: "main",
},
fragmentStage: {
module: this._device.createShaderModule({
label: "fragment",
code: fragmentShader,
}),
entryPoint: "main",
},
};
}
_compileRawPipelineStageDescriptor(vertexCode, fragmentCode, shaderLanguage) {
const disableUniformityAnalysisInVertex = vertexCode.indexOf(Constants.DISABLEUA) >= 0;
const disableUniformityAnalysisInFragment = fragmentCode.indexOf(Constants.DISABLEUA) >= 0;
const vertexShader = shaderLanguage === 0 /* ShaderLanguage.GLSL */ ? this._compileRawShaderToSpirV(vertexCode, "vertex") : vertexCode;
const fragmentShader = shaderLanguage === 0 /* ShaderLanguage.GLSL */ ? this._compileRawShaderToSpirV(fragmentCode, "fragment") : fragmentCode;
return this._createPipelineStageDescriptor(vertexShader, fragmentShader, shaderLanguage, disableUniformityAnalysisInVertex, disableUniformityAnalysisInFragment);
}
_compilePipelineStageDescriptor(vertexCode, fragmentCode, defines, shaderLanguage) {
this.onBeforeShaderCompilationObservable.notifyObservers(this);
const disableUniformityAnalysisInVertex = vertexCode.indexOf(Constants.DISABLEUA) >= 0;
const disableUniformityAnalysisInFragment = fragmentCode.indexOf(Constants.DISABLEUA) >= 0;
const shaderVersion = "#version 450\n";
const vertexShader = shaderLanguage === 0 /* ShaderLanguage.GLSL */ ? this._compileShaderToSpirV(vertexCode, "vertex", defines, shaderVersion) : this._getWGSLShader(vertexCode, "vertex", defines);
const fragmentShader = shaderLanguage === 0 /* ShaderLanguage.GLSL */
? this._compileShaderToSpirV(fragmentCode, "fragment", defines, shaderVersion)
: this._getWGSLShader(fragmentCode, "fragment", defines);
const program = this._createPipelineStageDescriptor(vertexShader, fragmentShader, shaderLanguage, disableUniformityAnalysisInVertex, disableUniformityAnalysisInFragment);
this.onAfterShaderCompilationObservable.notifyObservers(this);
return program;
}
/**
* @internal
*/
createRawShaderProgram() {
// eslint-disable-next-line no-throw-literal
throw "Not available on WebGPU";
}
/**
* @internal
*/
createShaderProgram() {
// eslint-disable-next-line no-throw-literal
throw "Not available on WebGPU";
}
/**
* Inline functions in shader code that are marked to be inlined
* @param code code to inline
* @returns inlined code
*/
inlineShaderCode(code) {
const sci = new ShaderCodeInliner(code);
sci.debug = false;
sci.processCode();
return sci.code;
}
/**
* Creates a new pipeline context
* @param shaderProcessingContext defines the shader processing context used during the processing if available
* @returns the new pipeline
*/
createPipelineContext(shaderProcessingContext) {
return new WebGPUPipelineContext(shaderProcessingContext, this);
}
/**
* Creates a new material context
* @returns the new context
*/
createMaterialContext() {
return new WebGPUMaterialContext();
}
/**
* Creates a new draw context
* @returns the new context
*/
createDrawContext() {
return new WebGPUDrawContext(this._bufferManager, this._dummyIndexBuffer);
}
/**
* @internal
*/
// eslint-disable-next-line @typescript-eslint/no-misused-promises
async _preparePipelineContextAsync(pipelineContext, vertexSourceCode, fragmentSourceCode, createAsRaw, rawVertexSourceCode, rawFragmentSourceCode, _rebuildRebind, defines, _transformFeedbackVaryings, _key, onReady) {
const webGpuContext = pipelineContext;
const shaderLanguage = webGpuContext.shaderProcessingContext.shaderLanguage;
if (shaderLanguage === 0 /* ShaderLanguage.GLSL */ && !this._glslangAndTintAreFullyLoaded) {
await this.prepareGlslangAndTintAsync();
}
if (this.dbgShowShaderCode) {
Logger.Log(["defines", defines]);
Logger.Log(vertexSourceCode);
Logger.Log(fragmentSourceCode);
Logger.Log("***********************************************");
}
webGpuContext.sources = {
fragment: fragmentSourceCode,
vertex: vertexSourceCode,
rawVertex: rawVertexSourceCode,
rawFragment: rawFragmentSourceCode,
};
if (createAsRaw) {
webGpuContext.stages = this._compileRawPipelineStageDescriptor(vertexSourceCode, fragmentSourceCode, shaderLanguage);
}
else {
webGpuContext.stages = this._compilePipelineStageDescriptor(vertexSourceCode, fragmentSourceCode, defines, shaderLanguage);
}
onReady();
}
/**
* Gets the list of active attributes for a given WebGPU program
* @param pipelineContext defines the pipeline context to use
* @param attributesNames defines the list of attribute names to get
* @returns an array of indices indicating the offset of each attribute
*/
getAttributes(pipelineContext, attributesNames) {
const results = new Array(attributesNames.length);
const gpuPipelineContext = pipelineContext;
for (let i = 0; i < attributesNames.length; i++) {
const attributeName = attributesNames[i];
const attributeLocation = gpuPipelineContext.shaderProcessingContext.availableAttributes[attributeName];
if (attributeLocation === undefined) {
continue;
}
results[i] = attributeLocation;
}
return results;
}
/**
* Activates an effect, making it the current one (ie. the one used for rendering)
* @param effect defines the effect to activate
*/
enableEffect(effect) {
if (!effect) {
return;
}
if (!IsWrapper(effect)) {
this._currentEffect = effect;
this._currentMaterialContext = this._defaultMaterialContext;
this._currentDrawContext = this._defaultDrawContext;
this._counters.numEnableEffects++;
if (this.dbgLogIfNotDrawWrapper) {
Logger.Warn(`enableEffect has been called with an Effect and not a Wrapper! effect.uniqueId=${effect.uniqueId}, effect.name=${effect.name}, effect.name.vertex=${typeof effect.name === "string" ? "" : effect.name.vertex}, effect.name.fragment=${typeof effect.name === "string" ? "" : effect.name.fragment}`, 10);
}
}
else if (!effect.effect ||
(effect.effect === this._currentEffect &&
effect.materialContext === this._currentMaterialContext &&
effect.drawContext === this._currentDrawContext &&
!this._forceEnableEffect)) {
if (!effect.effect && this.dbgShowEmptyEnableEffectCalls) {
Logger.Log(["drawWrapper=", effect]);
// eslint-disable-next-line no-throw-literal
throw "Invalid call to enableEffect: the effect property is empty!";
}
return;
}
else {
this._currentEffect = effect.effect;
this._currentMaterialContext = effect.materialContext;
this._currentDrawContext = effect.drawContext;
this._counters.numEnableDrawWrapper++;
if (!this._currentMaterialContext) {
Logger.Log(["drawWrapper=", effect]);
// eslint-disable-next-line no-throw-literal
throw `Invalid call to enableEffect: the materialContext property is empty!`;
}
}
this._stencilStateComposer.stencilMaterial = undefined;
this._forceEnableEffect = false;
if (this._currentEffect.onBind) {
this._currentEffect.onBind(this._currentEffect);
}
if (this._currentEffect._onBindObservable) {
this._currentEffect._onBindObservable.notifyObservers(this._currentEffect);
}
}
/**
* @internal
*/
_releaseEffect(effect) {
if (this._compiledEffects[effect._key]) {
delete this._compiledEffects[effect._key];
this._deletePipelineContext(effect.getPipelineContext());
}
}
/**
* Force the engine to release all cached effects. This means that next effect compilation will have to be done completely even if a similar effect was already compiled
*/
releaseEffects() {
for (const name in this._compiledEffects) {
const webGPUPipelineContext = this._compiledEffects[name].getPipelineContext();
this._deletePipelineContext(webGPUPipelineContext);
}
this._compiledEffects = {};
this.onReleaseEffectsObservable.notifyObservers(this);
}
_deletePipelineContext(pipelineContext) {
const webgpuPipelineContext = pipelineContext;
if (webgpuPipelineContext) {
resetCachedPipeline(webgpuPipelineContext);
}
}
//------------------------------------------------------------------------------
// Textures
//------------------------------------------------------------------------------
/**
* Gets a boolean indicating that only power of 2 textures are supported
* Please note that you can still use non power of 2 textures but in this case the engine will forcefully convert them
*/
get needPOTTextures() {
return false;
}
/** @internal */
_createHardwareTexture() {
return new WebGPUHardwareTexture(this);
}
/**
* @internal
*/
_releaseTexture(texture) {
const index = this._internalTexturesCache.indexOf(texture);
if (index !== -1) {
this._internalTexturesCache.splice(index, 1);
}
this._textureHelper.releaseTexture(texture);
}
/**
* @internal
*/
_getRGBABufferInternalSizedFormat() {
return Constants.TEXTUREFORMAT_RGBA;
}
/**
* Update the comparison function of a texture
* @param texture defines the texture to update
* @param comparisonFunction defines the comparison function to set
*/
updateTextureComparisonFunction(texture, comparisonFunction) {
texture._comparisonFunction = comparisonFunction;
}
/**
* Creates an internal texture without binding it to a framebuffer
* @internal
* @param size defines the size of the texture
* @param options defines the options used to create the texture
* @param delayGPUTextureCreation true to delay the texture creation the first time it is really needed. false to create it right away
* @param source source type of the texture
* @returns a new internal texture
*/
_createInternalTexture(size, options, delayGPUTextureCreation = true, source = 0 /* InternalTextureSource.Unknown */) {
const fullOptions = {};
if (options !== undefined && typeof options === "object") {
fullOptions.generateMipMaps = options.generateMipMaps;
fullOptions.createMipMaps = options.createMipMaps;
fullOptions.type = options.type === undefined ? Constants.TEXTURETYPE_UNSIGNED_BYTE : options.type;
fullOptions.samplingMode = options.samplingMode === undefined ? Constants.TEXTURE_TRILINEAR_SAMPLINGMODE : options.samplingMode;
fullOptions.format = options.format === undefined ? Constants.TEXTUREFORMAT_RGBA : options.format;
fullOptions.samples = options.samples ?? 1;
fullOptions.creationFlags = options.creationFlags ?? 0;
fullOptions.useSRGBBuffer = options.useSRGBBuffer ?? false;
fullOptions.label = options.label;
fullOptions.isCube = !!options.isCube;
}
else {
fullOptions.generateMipMaps = options;
fullOptions.type = Constants.TEXTURETYPE_UNSIGNED_BYTE;
fullOptions.samplingMode = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE;
fullOptions.format = Constants.TEXTUREFORMAT_RGBA;
fullOptions.samples = 1;
fullOptions.creationFlags = 0;
fullOptions.useSRGBBuffer = false;
fullOptions.isCube = false;
}
if (fullOptions.type === Constants.TEXTURETYPE_FLOAT && !this._caps.textureFloatLinearFiltering) {
fullOptions.samplingMode = Constants.TEXTURE_NEAREST_SAMPLINGMODE;
}
else if (fullOptions.type === Constants.TEXTURETYPE_HALF_FLOAT && !this._caps.textureHalfFloatLinearFiltering) {
fullOptions.samplingMode = Constants.TEXTURE_NEAREST_SAMPLINGMODE;
}
if (fullOptions.type === Constants.TEXTURETYPE_FLOAT && !this._caps.textureFloat) {
fullOptions.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 = size.width ?? size;
const height = size.height ?? size;
const depth = size.depth ?? 0;
const layers = size.layers ?? 0;
texture.baseWidth = width;
texture.baseHeight = height;
texture.width = width;
texture.height = height;
texture.depth = depth || layers;
texture.isReady = true;
texture.samples = fullOptions.samples;
texture.generateMipMaps = !!fullOptions.generateMipMaps;
texture.samplingMode = fullOptions.samplingMode;
texture.type = fullOptions.type;
texture.format = fullOptions.format;
texture.is2DArray = layers > 0;
texture.is3D = depth > 0;
texture.isCube = fullOptions.isCube;
texture._cachedWrapU = Constants.TEXTURE_CLAMP_ADDRESSMODE;
texture._cachedWrapV = Constants.TEXTURE_CLAMP_ADDRESSMODE;
texture._useSRGBBuffer = fullOptions.useSRGBBuffer;
texture.label = fullOptions.label;
this._internalTexturesCache.push(texture);
if (!delayGPUTextureCreation) {
const createMipMapsOnly = !fullOptions.generateMipMaps && fullOptions.createMipMaps;
if (createMipMapsOnly) {
// So that the call to createGPUTextureForInternalTexture creates the mipmaps
texture.generateMipMaps = true;
}
this._textureHelper.createGPUTextureForInternalTexture(texture, width, height, texture.depth || 1, fullOptions.creationFlags);
if (createMipMapsOnly) {
// So that we don't automatically generate mipmaps when the render target is unbound
texture.generateMipMaps = false;
}
}
return texture;
}
/**
* Usually called from Texture.ts.
* Passed information to create a hardware texture
* @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
*/
createTexture(url, noMipmap, invertY, scene, samplingMode = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE, onLoad = null, onError = null, buffer = null, fallback = null, format = null, forcedExtension = null, mimeType, loaderOptions, creationFlags, useSRGBBuffer) {
return this._createTextureBase(url, noMipmap, invertY, scene, samplingMode, onLoad, onError, (texture, extension, scene, img, invertY, noMipmap, isCompressed, processFunction) => {
const imageBitmap = img; // we will never get an HTMLImageElement in WebGPU
texture.baseWidth = imageBitmap.width;
texture.baseHeight = imageBitmap.height;
texture.width = imageBitmap.width;
texture.height = imageBitmap.height;
texture.format = texture.format !== -1 ? texture.format : (format ?? Constants.TEXTUREFORMAT_RGBA);
texture.type = texture.type !== -1 ? texture.type : Constants.TEXTURETYPE_UNSIGNED_BYTE;
texture._creationFlags = creationFlags ?? 0;
processFunction(texture.width, texture.height, imageBitmap, extension, texture, () => { });
if (!texture._hardwareTexture?.underlyingResource) {
// the texture could have been created before reaching this point so don't recreate it if already existing
const gpuTextureWrapper = this._textureHelper.createGPUTextureForInternalTexture(texture, imageBitmap.width, imageBitmap.height, undefined, creationFlags);
if (WebGPUTextureHelper.IsImageBitmap(imageBitmap)) {
this._textureHelper.updateTexture(imageBitmap, texture, imageBitmap.width, imageBitmap.height, texture.depth, gpuTextureWrapper.format, 0, 0, invertY, false, 0, 0);
if (!noMipmap && !isCompressed) {
this._generateMipmaps(texture, this._uploadEncoder);
}
}
}
else if (!noMipmap && !isCompressed) {
this._generateMipmaps(texture, this._uploadEncoder);
}
if (scene) {
scene.removePendingData(texture);
}
texture.isReady = true;
texture.onLoadedObservable.notifyObservers(texture);
texture.onLoadedObservable.clear();
}, () => false, buffer, fallback, format, forcedExtension, mimeType, loaderOptions, useSRGBBuffer);
}
/**
* Wraps an external web gpu texture in a Babylon texture.
* @param texture defines the external texture
* @returns the babylon internal texture
*/
wrapWebGPUTexture(texture) {
const hardwareTexture = new WebGPUHardwareTexture(this, texture);
// Report the real format of the wrapped texture. The hardware texture otherwise keeps its
// default (RGBA8Unorm); the render-pass color attachment view + pipeline color target are
// built from hardwareTexture.format (see _setColorFormat / bindFramebuffer), so an external
// texture created with any other format (e.g. bgra8unorm or an *-srgb variant, as returned by
// XRGPUBinding.getPreferredColorFormat()) would otherwise get a mismatched view and fail to render.
hardwareTexture.format = texture.format;
hardwareTexture.originalFormat = texture.format;
const internalTexture = new InternalTexture(this, 15 /* InternalTextureSource.External */, true);
internalTexture._hardwareTexture = hardwareTexture;
internalTexture.baseWidth = texture.width;
internalTexture.baseHeight = texture.height;
internalTexture.width = texture.width;
internalTexture.height = texture.height;
internalTexture.isReady = true;
return internalTexture;
}
/**
* Replaces the underlying GPUTexture of a texture previously created via {@link wrapWebGPUTexture}, preserving the
* InternalTexture identity.
*
* Intended for the device-loss / device-restored flow: when the host recreates its external resource on the new
* GPU device, it calls this method to repoint Babylon's wrapper at the new handle without losing references held
* by materials, render-target wrappers, particle systems, etc.
*
* The new GPUTexture must have the same dimensions as the original. To change dimensions, dispose the wrapped
* texture and call {@link wrapWebGPUTexture} again.
*
* Throws if the target was not produced by {@link wrapWebGPUTexture}, or if the new GPUTexture's dimensions don't
* match.
* @param internalTexture defines the wrapped InternalTexture to repoint
* @param texture defines the new GPUTexture to wrap
*/
updateWrappedWebGPUTexture(internalTexture, texture) {
if (internalTexture.source !== 15 /* InternalTextureSource.External */) {
throw new Error("updateWrappedWebGPUTexture: target InternalTexture was not produced by wrapWebGPUTexture.");
}
if (texture.width !== internalTexture.baseWidth || texture.height !== internalTexture.baseHeight) {
throw new Error(`updateWrappedWebGPUTexture: new GPUTexture dimensions (${texture.width}x${texture.height}) must match the wrapped texture's dimensions (${internalTexture.baseWidth}x${internalTexture.baseHeight}).`);
}
// Reuse the existing WebGPUHardwareTexture rather than replacing it. The wrapper carries `textureUsages`,
// `format`, and the `view` / `viewForWriting` set up the first time the texture was bound; the views point
// at the old (now-destroyed) GPUTexture, so we swap the underlying resource and re-issue views via setUsage,
// which uses the InternalTexture's flags + dimensions to pick the right view dimension and recreate the views.
const hardwareTexture = internalTexture._hardwareTexture;
hardwareTexture.set(texture);
hardwareTexture.setUsage(15 /* InternalTextureSource.External */, internalTexture.generateMipMaps, internalTexture.is2DArray, internalTexture.isCube, internalTexture.is3D, texture.width, texture.height, internalTexture.depth);
internalTexture.isReady = true;
// WebGPUMaterialContext and WebGPUCacheBindGroups key on InternalTexture.uniqueId; bump it so caches detect
// the new resource and re-resolve bind groups against the new GPUTexture / GPUTextureView.
internalTexture._setUniqueId(InternalTexture._Counter++);
}
// eslint-disable-next-line jsdoc/require-returns-check
/**
* Wraps an external web gl texture in a Babylon texture.
* @returns the babylon internal texture
*/
wrapWebGLTexture() {
throw new Error("wrapWebGLTexture is not supported, use wrapWebGPUTexture instead.");
}
/**
* Throws because the engine is WebGPU. Use {@link updateWrappedWebGPUTexture} instead.
*/
updateWrappedWebGLTexture() {
throw new Error("updateWrappedWebGLTexture is not supported, use updateWrappedWebGPUTexture instead.");
}
/**
* @internal
*/
_getUseSRGBBuffer(useSRGBBuffer, _noMipmap) {
return useSRGBBuffer && this._caps.supportSRGBBuffers;
}
/**
* @internal
*/
_unpackFlipY(_value) { }
/**
* Update the sampling mode of a given texture
* @param samplingMode defines the required sampling mode
* @param texture defines the texture to update
* @param generateMipMaps defines whether to generate mipmaps for the texture
*/
updateTextureSamplingMode(samplingMode, texture, generateMipMaps = false) {
if (generateMipMaps) {
texture.generateMipMaps = true;
this._generateMipmaps(texture);
}
texture.samplingMode = samplingMode;
}
/**
* Update the sampling mode of a given texture
* @param texture defines the texture to update
* @param wrapU defines the texture wrap mode of the u coordinates
* @param wrapV defines the texture wrap mode of the v coordinates
* @param wrapR defines the texture wrap mode of the r coordinates
*/
updateTextureWrappingMode(texture, wrapU, wrapV = null, wrapR = null) {
if (wrapU !== null) {
texture._cachedWrapU = wrapU;
}
if (wrapV !== null) {
texture._cachedWrapV = wrapV;
}
if ((texture.is2DArray || texture.is3D) && wrapR !== null) {
texture._cachedWrapR = wrapR;
}
}
/**
* Update the dimensions of a texture
* @param texture texture to update
* @param width new width of the texture
* @param height new height of the texture
* @param depth new depth of the texture
*/
updateTextureDimensions(texture, width, height, depth = 1) {
if (!texture._hardwareTexture) {
// the gpu texture is not created yet, so when it is it will be created with the right dimensions
return;
}
if (texture.width === width && texture.height === height && texture.depth === depth) {
return;
}
const additionalUsages = texture._hardwareTexture.textureAdditionalUsages;
texture._hardwareTexture.release(); // don't defer the releasing! Else we will release at the end of this frame the gpu texture we are about to create in the next line...
this._textureHelper.createGPUTextureForInternalTexture(texture, width, height, depth, additionalUsages);
}
/**
* @internal
*/
_setInternalTexture(name, texture, baseName) {
baseName = baseName ?? name;
if (this._currentEffect) {
const webgpuPipelineContext = this._currentEffect._pipelineContext;
const availableTexture = webgpuPipelineContext.shaderProcessingContext.availableTextures[baseName];
this._currentMaterialContext.setTexture(name, texture);
if (availableTexture && availableTexture.autoBindSampler) {
const samplerName = baseName + Constants.AUTOSAMPLERSUFFIX;
this._currentMaterialContext.setSampler(samplerName, texture); // we can safely cast to InternalTexture because ExternalTexture always has autoBindSampler = false
}
}
}
/**
* Create a cube texture from prefiltered data (ie. the mipmaps contain ready to use data for PBR reflection)
* @param rootUrl defines the url where the file to load is located
* @param scene defines the current scene
* @param lodScale defines scale to apply to the mip map selection
* @param lodOffset defines offset to apply to the mip map selection
* @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 defines wheter or not to create polynomails harmonics for the texture
* @returns the cube texture as an InternalTexture
*/
createPrefilteredCubeTexture(rootUrl, scene, lodScale, lodOffset, onLoad = null, onError = null, format, forcedExtension = null, createPolynomials = true) {
const callback = (loadData) => {
if (!loadData) {
if (onLoad) {
onLoad(null);
}
return;
}
const texture = loadData.texture;
if (!createPolynomials) {
texture._sphericalPolynomial = texture._sphericalPolynomial ?? new SphericalPolynomial();
}
else if (loadData.info.sphericalPolynomial) {
texture._sphericalPolynomial = loadData.info.sphericalPolynomial;
}
texture._source = 9 /* InternalTextureSource.CubePrefiltered */;
if (onLoad) {
onLoad(texture);
}
};
return this.createCubeTexture(rootUrl, scene, null, false, callback, onError, format, forcedExtension, createPolynomials, lodScale, lodOffset);
}
/**
* Sets a texture to the according uniform.
* @param channel The texture channel
* @param unused unused parameter
* @param texture The texture to apply
* @param name The name of the uniform in the effect
*/
setTexture(channel, unused, texture, name) {
this._setTexture(channel, texture, false, false, name, name);
}
/**
* Sets an array of texture to the WebGPU context
* @param channel defines the channel where the texture array must be set
* @param unused unused parameter
* @param textures defines the array of textures to bind
* @param name name of the channel
*/
setTextureArray(channel, unused, textures, name) {
for (let index = 0; index < textures.length; index++) {
this._setTexture(-1, textures[index], true, false, name + index.toString(), name);
}
}
/**
* @internal
*/
_setTexture(channel, texture,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
isPartOfTextureArray = false, depthStencilTexture = false, name = "", baseName) {
// name == baseName for a texture that is not part of a texture array
// Else, name is something like 'myTexture0' / 'myTexture1' / ... and baseName is 'myTexture'
// baseName is used to look up the texture in the shaderProcessingContext.availableTextures map
// name is used to look up the texture in the _currentMaterialContext.textures map
baseName = baseName ?? name;
if (this._currentEffect) {
if (!texture) {
this._currentMaterialContext.setTexture(name, null);
return false;
}
// Video
if (texture.video) {
texture.update();
}
else if (texture.delayLoadState === Constants.DELAYLOADSTATE_NOTLOADED) {
// Delay loading
texture.delayLoad();
return false;
}
let 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;
}
if (internalTexture && !internalTexture.isMultiview) {
// CUBIC_MODE and SKYBOX_MODE both require CLAMP_TO_EDGE. All other modes use REPEAT.
if (internalTexture.isCube && internalTexture._cachedCoordinatesMode !== texture.coordinatesMode) {
internalTexture._cachedCoordinatesMode = texture.coordinatesMode;
const textureWrapMode = texture.coordinatesMode !== Constants.TEXTURE_CUBIC_MODE && texture.coordinatesMode !== Constants.TEXTURE_SKYBOX_MODE
? Constants.TEXTURE_WRAP_ADDRESSMODE
: Constants.TEXTURE_CLAMP_ADDRESSMODE;
texture.wrapU = textureWrapMode;
texture.wrapV = textureWrapMode;
}
internalTexture._cachedWrapU = texture.wrapU;
internalTexture._cachedWrapV = texture.wrapV;
if (internalTexture.is3D) {
internalTexture._cachedWrapR = texture.wrapR;
}
this._setAnisotropicLevel(0, internalTexture, texture.anisotropicFilteringLevel);
}
this._setInternalTexture(name, internalTexture, baseName);
}
return true;
}
/**
* @internal
*/
_setAnisotropicLevel(target, internalTexture, anisotropicFilteringLevel) {
if (internalTexture._cachedAnisotropicFilteringLevel !== anisotropicFilteringLevel) {
internalTexture._cachedAnisotropicFilteringLevel = Math.min(anisotropicFilteringLevel, this._caps.maxAnisotropy);
}
}
/**
* @internal
*/
_bindTexture(channel, texture, name) {
if (channel === undefined) {
return;
}
this._setInternalTexture(name, texture);
}
/**
* Generates the mipmaps for a texture
* @param texture texture to generate the mipmaps for
*/
generateMipmaps(texture) {
this._generateMipmaps(texture);
}
/**
* 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
*/
updateTextureData(texture, imageData, xOffset, yOffset, width, height, faceIndex = 0, lod = 0, generateMipMaps = false) {
let gpuTextureWrapper = texture._hardwareTexture;
if (!texture._hardwareTexture?.underlyingResource) {
gpuTextureWrapper = this._textureHelper.createGPUTextureForInternalTexture(texture);
}
const data = new Uint8Array(imageData.buffer, imageData.byteOffset, imageData.byteLength);
this._textureHelper.updateTexture(data, texture, width, height, texture.depth, gpuTextureWrapper.format, faceIndex, lod, texture.invertY, false, xOffset, yOffset);
if (generateMipMaps) {
this._generateMipmaps(texture);
}
}
/**
* @internal
*/
_uploadCompressedDataToTextureDirectly(texture, internalFormat, width, height, imageData, faceIndex = 0, lod = 0) {
let gpuTextureWrapper = texture._hardwareTexture;
if (!texture._hardwareTexture?.underlyingResource) {
texture.format = internalFormat;
gpuTextureWrapper = this._textureHelper.createGPUTextureForInternalTexture(texture, width, height);
}
const data = new Uint8Array(imageData.buffer, imageData.byteOffset, imageData.byteLength);
this._textureHelper.updateTexture(data, texture, width, height, texture.depth, gpuTextureWrapper.format, faceIndex, lod, false, false, 0, 0);
}
/**
* @internal
*/
_uploadDataToTextureDirectly(texture, imageData, faceIndex = 0, lod = 0, babylonInternalFormat, useTextureWidthAndHeight = false) {
const lodMaxWidth = Math.round(Math.log(texture.width) * Math.LOG2E);
const lodMaxHeight = Math.round(Math.log(texture.height) * Math.LOG2E);
const width = useTextureWidthAndHeight ? texture.width : Math.pow(2, Math.max(lodMaxWidth - lod, 0));
const height = useTextureWidthAndHeight ? texture.height : Math.pow(2, Math.max(lodMaxHeight - lod, 0));
let gpuTextureWrapper = texture._hardwareTexture;
if (!texture._hardwareTexture?.underlyingResource) {
gpuTextureWrapper = this._textureHelper.createGPUTextureForInternalTexture(texture, width, height);
}
const data = new Uint8Array(imageData.buffer, imageData.byteOffset, imageData.byteLength);
this._textureHelper.updateTexture(data, texture, width, height, texture.depth, gpuTextureWrapper.format, faceIndex, lod, texture.invertY, false, 0, 0);
}
/**
* @internal
*/
_uploadArrayBufferViewToTexture(texture, imageData, faceIndex = 0, lod = 0) {
this._uploadDataToTextureDirectly(texture, imageData, faceIndex, lod);
}
/**
* @internal
*/
_uploadImageToTexture(texture, image, faceIndex = 0, lod = 0) {
let gpuTextureWrapper = texture._hardwareTexture;
if (!texture._hardwareTexture?.underlyingResource) {
gpuTextureWrapper = this._textureHelper.createGPUTextureForInternalTexture(texture);
}
if (image instanceof HTMLImageElement) {
// eslint-disable-next-line no-throw-literal
throw "WebGPU engine: HTMLImageElement not supported in _uploadImageToTexture!";
}
const bitmap = image; // in WebGPU we will always get an ImageBitmap, not an HTMLImageElement
const width = Math.ceil(texture.width / (1 << lod));
const height = Math.ceil(texture.height / (1 << lod));
this._textureHelper.updateTexture(bitmap, texture, width, height, texture.depth, gpuTextureWrapper.format, faceIndex, lod, texture.invertY, false, 0, 0);
}
/**
* Reads pixels from the current frame buffer. Please note that this function can be slow
* @param x defines the x coordinate of the rectangle where pixels must be read
* @param y defines the y coordinate of the rectangle where pixels must be read
* @param width defines the width of the rectangle where pixels must be read
* @param height defines the height of the rectangle where pixels must be read
* @param _hasAlpha defines whether the output should have alpha or not (defaults to true)
* @param flushRenderer true to flush the renderer from the pending commands before reading the pixels
* @param data defines the data to fill with the read pixels (if not provided, a new one will be created)
* @returns a ArrayBufferView promise (Uint8Array) containing RGBA colors
*/
// eslint-disable-next-line @typescript-eslint/promise-function-async
readPixels(x, y, width, height, _hasAlpha = true, flushRenderer = true, data = null) {
const renderPassWrapper = this._getCurrentRenderPassWrapper();
const hardwareTexture = renderPassWrapper.colorAttachmentGPUTextures[0];
if (!hardwareTexture) {
// we are calling readPixels for a render pass with no color texture bound
return Promise.resolve(new Uint8Array(0));
}
const gpuTexture = hardwareTexture.underlyingResource;
const gpuTextureFormat = hardwareTexture.format;
if (!gpuTexture) {
// we are calling readPixels before startMainRenderPass has been called and no RTT is bound, so swapChainTexture is not setup yet!
return Promise.resolve(new Uint8Array(0));
}
if (flushRenderer) {
this.flushFramebuffer();
}
return this._textureHelper.readPixels(gpuTexture, x, y, width, height, gpuTextureFormat, undefined, undefined, data);
}
//------------------------------------------------------------------------------
// Frame management
//------------------------------------------------------------------------------
_measureFps() {
this._performanceMonitor.sampleFrame();
this._fps = this._performanceMonitor.averageFPS;
this._deltaTime = this._performanceMonitor.instantaneousFrameTime || 0;
}
/**
* Gets the performance monitor attached to this engine
* @see https://doc.babylonjs.com/features/featuresDeepDive/scene/optimize_your_scene#engineinstrumentation
*/
get performanceMonitor() {
return this._performanceMonitor;
}
/**
* Begin a new frame
*/
beginFrame() {
this._measureFps();
super.beginFrame();
}
/**
* End the current frame
*/
endFrame() {
this._endCurrentRenderPass();
this._snapshotRendering.endFrame();
this._timestampQuery.endFrame(this._renderEncoder);
this._timestampIndex = 0;
this._internalFrameCounter = 0;
this.flushFramebuffer(true);
this._textureHelper.destroyDeferredTextures();
this._bufferManager.destroyDeferredBuffers();
this.countersLastFrame.numEnableEffects = this._counters.numEnableEffects;
this.countersLastFrame.numEnableDrawWrapper = this._counters.numEnableDrawWrapper;
this.countersLastFrame.numBundleCreationNonCompatMode = this._counters.numBundleCreationNonCompatMode;
this.countersLastFrame.numBundleReuseNonCompatMode = this._counters.numBundleReuseNonCompatMode;
this._counters.numEnableEffects = 0;
this._counters.numEnableDrawWrapper = 0;
this._counters.numBundleCreationNonCompatMode = 0;
this._counters.numBundleReuseNonCompatMode = 0;
this._cacheRenderPipeline.endFrame();
this._cacheBindGroups.endFrame();
super.endFrame();
}
/**Gets driver info if available */
extractDriverInfo() {
return "";
}
/**
* Force a WebGPU flush (ie. a flush of all waiting commands)
* @internal @param _fromEndFrame defines whether the flush is triggered from endFrame or not (default: false)
*/
flushFramebuffer(_fromEndFrame = false) {
// we need to end the current render pass (main or rtt) if any as we are not allowed to submit the command buffers when being in a pass
this._endCurrentRenderPass();
this._debugPopBeforeEndOfEncoder();
this._commandBuffers[0] = this._uploadEncoder.finish();
this._commandBuffers[1] = this._renderEncoder.finish();
this._device.queue.submit(this._commandBuffers);
const frameCounter = this._internalFrameCounter++;
// If flushFramebuffer is called from endFrame, it means the encoders are created for the next frame, but because frameId is not yet incremented at the time of the call, we use frameId + 1
this._uploadEncoderDescriptor.label = `[${_fromEndFrame ? this.frameId + 1 : this.frameId}|${frameCounter}] - UploadEncoder`;
this._renderEncoderDescriptor.label = `[${_fromEndFrame ? this.frameId + 1 : this.frameId}|${frameCounter}] - RenderEncoder`;
this._uploadEncoder = this._device.createCommandEncoder(this._uploadEncoderDescriptor);
this._renderEncoder = this._device.createCommandEncoder(this._renderEncoderDescriptor);
if (_fromEndFrame) {
this._debugMarkersEncoderGroups.length = 0;
this._debugMarkersPassGroups.length = 0;
}
else {
this._debugPushAfterStartOfEncoder();
}
this._timestampQuery.startFrame(this._uploadEncoder);
this._textureHelper.setCommandEncoder(this._uploadEncoder);
this._bundleList.reset();
}
/** @internal */
_currentFrameBufferIsDefaultFrameBuffer() {
return this._currentPassIsMainPass();
}
//------------------------------------------------------------------------------
// Render Pass
//------------------------------------------------------------------------------
/** @internal */
_startRenderTargetRenderPass(renderTargetWrapper, setClearStates, clearColor, clearDepth, clearStencil) {
this._endCurrentRenderPass();
const rtWrapper = renderTargetWrapper;
const sampleCount = renderTargetWrapper.samples;
const useMSAA = sampleCount > 1;
const depthStencilTexture = rtWrapper._depthStencilTexture;
const gpuDepthStencilWrapper = depthStencilTexture?._hardwareTexture;
const gpuDepthStencilTexture = gpuDepthStencilWrapper?.underlyingResource;
// We use the MSAA texture format first (if available) to determine if it has a stencil aspect or not because, for MSAA depth textures,
// the format of the "resolve" texture (gpuDepthStencilWrapper.format) is a single red channel format, not a depth-stencil format.
let depthStencilView;
let format;
if (useMSAA) {
const gpuMSAATexture = gpuDepthStencilWrapper?.getMSAATexture(sampleCount);
depthStencilView = gpuMSAATexture ? this._cacheTextureViews.getView(gpuMSAATexture, this._rttRenderPassWrapper.depthAttachmentViewDescriptor) : undefined;
format = gpuMSAATexture?.format;
}
if (!depthStencilView && gpuDepthStencilTexture) {
depthStencilView = this._cacheTextureViews.getView(gpuDepthStencilTexture, this._rttRenderPassWrapper.depthAttachmentViewDescriptor);
}
if (!format && gpuDepthStencilWrapper) {
format = gpuDepthStencilWrapper.format;
}
const depthTextureHasStencil = format ? WebGPUTextureHelper.HasStencilAspect(format) : false;
const depthTextureHasDepth = format ? WebGPUTextureHelper.HasDepthAspect(format) : false;
const colorAttachments = [];
if (this.useReverseDepthBuffer) {
this.setDepthFunctionToGreaterOrEqual();
}
const clearColorForIntegerRt = TempColor4;
if (clearColor) {
clearColorForIntegerRt.r = clearColor.r * 255;
clearColorForIntegerRt.g = clearColor.g * 255;
clearColorForIntegerRt.b = clearColor.b * 255;
clearColorForIntegerRt.a = clearColor.a * 255;
}
const mustClearColor = setClearStates && clearColor;
const mustClearDepth = setClearStates && clearDepth;
const mustClearStencil = setClearStates && clearStencil;
if (rtWrapper._attachments && rtWrapper.isMulti) {
// multi render targets
if (!this._mrtAttachments || this._mrtAttachments.length === 0) {
this._mrtAttachments = rtWrapper._defaultAttachments;
}
for (let i = 0; i < this._mrtAttachments.length; ++i) {
const index = this._mrtAttachments[i]; // if index == 0 it means the texture should not be written to => at render pass creation time, it means we should not clear it
const mrtTexture = rtWrapper.textures[i];
const gpuMRTWrapper = mrtTexture?._hardwareTexture;
const gpuMRTTexture = gpuMRTWrapper?.underlyingResource;
if (gpuMRTWrapper && gpuMRTTexture) {
const depthSlice = mrtTexture.is3D ? (rtWrapper.layerIndices?.[i] ?? 0) : undefined;
const baseArrayLayer = rtWrapper.getBaseArrayLayer(i);
const gpuMSAATexture = useMSAA ? gpuMRTWrapper.getMSAATexture(sampleCount, mrtTexture.is3D ? depthSlice : baseArrayLayer) : undefined;
const viewDescriptor = {
...this._rttRenderPassWrapper.colorAttachmentViewDescriptor,
dimension: mrtTexture.is3D ? "3d" /* WebGPUConstants.TextureViewDimension.E3d */ : "2d" /* WebGPUConstants.TextureViewDimension.E2d */,
format: gpuMRTWrapper.format,
baseArrayLayer,
};
const msaaViewDescriptor = {
...this._rttRenderPassWrapper.colorAttachmentViewDescriptor,
dimension: mrtTexture.is3D ? "3d" /* WebGPUConstants.TextureViewDimension.E3d */ : "2d" /* WebGPUConstants.TextureViewDimension.E2d */,
format: gpuMRTWrapper.format,
baseArrayLayer: 0,
};
const isRtInteger = mrtTexture.type === Constants.TEXTURETYPE_UNSIGNED_INTEGER || mrtTexture.type === Constants.TEXTURETYPE_UNSIGNED_SHORT;
const colorTextureView = this._cacheTextureViews.getView(gpuMRTTexture, viewDescriptor);
const colorMSAATextureView = gpuMSAATexture ? this._cacheTextureViews.getView(gpuMSAATexture, msaaViewDescriptor) : undefined;
colorAttachments.push({
view: colorMSAATextureView ? colorMSAATextureView : colorTextureView,
resolveTarget: gpuMSAATexture && !rtWrapper.disableAutomaticMSAAResolve && rtWrapper.resolveMSAAColors ? colorTextureView : undefined,
depthSlice,
clearValue: index !== 0 && mustClearColor ? (isRtInteger ? clearColorForIntegerRt : clearColor) : undefined,
loadOp: index !== 0 && mustClearColor ? "clear" /* WebGPUConstants.LoadOp.Clear */ : "load" /* WebGPUConstants.LoadOp.Load */,
storeOp: "store" /* WebGPUConstants.StoreOp.Store */,
});
}
}
this._cacheRenderPipeline.setMRT(rtWrapper.textures, this._mrtAttachments.length);
this._cacheRenderPipeline.setMRTAttachments(this._mrtAttachments);
}
else {
// single render target
const internalTexture = rtWrapper.texture;
if (internalTexture) {
const gpuWrapper = internalTexture._hardwareTexture;
const gpuTexture = gpuWrapper.underlyingResource;
const layerIndex = this._rttRenderPassWrapper.colorAttachmentViewDescriptor.baseArrayLayer;
let depthSlice = undefined;
if (rtWrapper.is3D) {
depthSlice = this._rttRenderPassWrapper.colorAttachmentViewDescriptor.baseArrayLayer;
this._rttRenderPassWrapper.colorAttachmentViewDescriptor.baseArrayLayer = 0;
}
const gpuMSAATexture = useMSAA ? gpuWrapper.getMSAATexture(sampleCount, layerIndex) : undefined;
const colorTextureView = this._cacheTextureViews.getView(gpuTexture, this._rttRenderPassWrapper.colorAttachmentViewDescriptor);
const colorMSAATextureView = gpuMSAATexture
? this._cacheTextureViews.getView(gpuMSAATexture, this._rttRenderPassWrapper.colorAttachmentViewDescriptor)
: undefined;
const isRtInteger = internalTexture.type === Constants.TEXTURETYPE_UNSIGNED_INTEGER || internalTexture.type === Constants.TEXTURETYPE_UNSIGNED_SHORT;
colorAttachments.push({
view: colorMSAATextureView ? colorMSAATextureView : colorTextureView,
resolveTarget: gpuMSAATexture && !rtWrapper.disableAutomaticMSAAResolve && rtWrapper.resolveMSAAColors ? colorTextureView : undefined,
depthSlice,
clearValue: mustClearColor ? (isRtInteger ? clearColorForIntegerRt : clearColor) : undefined,
loadOp: mustClearColor ? "clear" /* WebGPUConstants.LoadOp.Clear */ : "load" /* WebGPUConstants.LoadOp.Load */,
storeOp: "store" /* WebGPUConstants.StoreOp.Store */,
});
}
else {
colorAttachments.push(null);
}
}
const frameCounter = this._internalFrameCounter++;
this._rttRenderPassWrapper.renderPassDescriptor = {
label: `[${this.frameId}|${frameCounter}] - ` + (renderTargetWrapper.label ?? "RTT") + " - RenderPass",
colorAttachments,
depthStencilAttachment: depthStencilTexture && gpuDepthStencilTexture
? {
view: depthStencilView,
depthClearValue: mustClearDepth ? (this.useReverseDepthBuffer ? this._clearReverseDepthValue : this._clearDepthValue) : undefined,
depthLoadOp: rtWrapper.depthReadOnly || !depthTextureHasDepth ? undefined : mustClearDepth ? "clear" /* WebGPUConstants.LoadOp.Clear */ : "load" /* WebGPUConstants.LoadOp.Load */,
depthStoreOp: rtWrapper.depthReadOnly || !depthTextureHasDepth ? undefined : "store" /* WebGPUConstants.StoreOp.Store */,
depthReadOnly: rtWrapper.depthReadOnly,
stencilClearValue: rtWrapper._depthStencilTextureWithStencil && mustClearStencil ? this._clearStencilValue : undefined,
stencilLoadOp: rtWrapper.stencilReadOnly || !depthTextureHasStencil
? undefined
: rtWrapper._depthStencilTextureWithStencil && mustClearStencil
? "clear" /* WebGPUConstants.LoadOp.Clear */
: "load" /* WebGPUConstants.LoadOp.Load */,
stencilStoreOp: rtWrapper.stencilReadOnly || !depthTextureHasStencil ? undefined : "store" /* WebGPUConstants.StoreOp.Store */,
stencilReadOnly: rtWrapper.stencilReadOnly,
}
: undefined,
occlusionQuerySet: this._occlusionQuery?.hasQueries ? this._occlusionQuery.querySet : undefined,
};
this._timestampQuery.startPass(this._rttRenderPassWrapper.renderPassDescriptor, this._timestampIndex);
this._currentRenderPass = this._renderEncoder.beginRenderPass(this._rttRenderPassWrapper.renderPassDescriptor);
this._debugPushAfterStartOfEncoder();
this._resetRenderPassStates();
if (!gpuDepthStencilWrapper || !WebGPUTextureHelper.HasStencilAspect(gpuDepthStencilWrapper.format)) {
this._stencilStateComposer.enabled = false;
}
}
_startMainRenderPass(setClearStates, clearColor, clearDepth, clearStencil) {
this._endCurrentRenderPass();
if (this.useReverseDepthBuffer) {
this.setDepthFunctionToGreaterOrEqual();
}
const mustClearColor = setClearStates && clearColor;
const mustClearDepth = setClearStates && clearDepth;
const mustClearStencil = setClearStates && clearStencil;
const frameCounter = this._internalFrameCounter++;
this._mainRenderPassWrapper.renderPassDescriptor.label = `[${this.frameId}|${frameCounter}] - MainRenderPass`;
this._mainRenderPassWrapper.renderPassDescriptor.colorAttachments[0].clearValue = mustClearColor ? clearColor : undefined;
this._mainRenderPassWrapper.renderPassDescriptor.colorAttachments[0].loadOp = mustClearColor ? "clear" /* WebGPUConstants.LoadOp.Clear */ : "load" /* WebGPUConstants.LoadOp.Load */;
this._mainRenderPassWrapper.renderPassDescriptor.depthStencilAttachment.depthClearValue = mustClearDepth
? this.useReverseDepthBuffer
? this._clearReverseDepthValue
: this._clearDepthValue
: undefined;
this._mainRenderPassWrapper.renderPassDescriptor.depthStencilAttachment.depthLoadOp = mustClearDepth ? "clear" /* WebGPUConstants.LoadOp.Clear */ : "load" /* WebGPUConstants.LoadOp.Load */;
this._mainRenderPassWrapper.renderPassDescriptor.depthStencilAttachment.stencilClearValue = mustClearStencil ? this._clearStencilValue : undefined;
this._mainRenderPassWrapper.renderPassDescriptor.depthStencilAttachment.stencilLoadOp = !this.isStencilEnable
? undefined
: mustClearStencil
? "clear" /* WebGPUConstants.LoadOp.Clear */
: "load" /* WebGPUConstants.LoadOp.Load */;
this._mainRenderPassWrapper.renderPassDescriptor.occlusionQuerySet = this._occlusionQuery?.hasQueries ? this._occlusionQuery.querySet : undefined;
const swapChainTexture = this._context.getCurrentTexture();
this._mainRenderPassWrapper.colorAttachmentGPUTextures[0].set(swapChainTexture);
// Resolve in case of MSAA
if (this._options.antialias) {
ViewDescriptorSwapChainAntialiasing.format = swapChainTexture.format;
this._mainRenderPassWrapper.renderPassDescriptor.colorAttachments[0].resolveTarget = swapChainTexture.createView(ViewDescriptorSwapChainAntialiasing);
}
else {
ViewDescriptorSwapChain.format = swapChainTexture.format;
this._mainRenderPassWrapper.renderPassDescriptor.colorAttachments[0].view = swapChainTexture.createView(ViewDescriptorSwapChain);
}
this._timestampQuery.startPass(this._mainRenderPassWrapper.renderPassDescriptor, this._timestampIndex);
this._currentRenderPass = this._renderEncoder.beginRenderPass(this._mainRenderPassWrapper.renderPassDescriptor);
this._debugPushAfterStartOfEncoder();
this._setDepthTextureFormat(this._mainRenderPassWrapper);
this._setColorFormat(this._mainRenderPassWrapper);
this._resetRenderPassStates();
if (!this._isStencilEnable) {
this._stencilStateComposer.enabled = false;
}
}
/**
* Binds the frame buffer to the specified texture.
* @param texture The render target wrapper to render to
* @param faceIndex The face of the texture to render to in case of cube texture
* @param requiredWidth The width of the target to render to
* @param requiredHeight The height of the target to render to
* @param forceFullscreenViewport Forces the viewport to be the entire texture/screen if true
* @param lodLevel defines the lod level to bind to the frame buffer
* @param layer defines the 2d array index to bind to frame buffer to
*/
bindFramebuffer(texture, faceIndex = 0, requiredWidth, requiredHeight, forceFullscreenViewport, lodLevel = 0, layer = 0) {
const hardwareTexture = texture.texture?._hardwareTexture;
if (this._currentRenderTarget) {
this.unBindFramebuffer(this._currentRenderTarget);
}
else {
this._endCurrentRenderPass();
}
this._currentRenderTarget = texture;
const depthStencilTexture = this._currentRenderTarget._depthStencilTexture;
this._rttRenderPassWrapper.colorAttachmentGPUTextures[0] = hardwareTexture;
this._rttRenderPassWrapper.depthTextureFormat = depthStencilTexture ? WebGPUTextureHelper.GetWebGPUTextureFormat(-1, depthStencilTexture.format) : undefined;
this._setDepthTextureFormat(this._rttRenderPassWrapper);
this._setColorFormat(this._rttRenderPassWrapper);
this._rttRenderPassWrapper.colorAttachmentViewDescriptor = {
label: texture.label ? texture.label + " - Color Attachment View" : "RTT - Color Attachment View",
format: this._colorFormat,
dimension: texture.is3D ? "3d" /* WebGPUConstants.TextureViewDimension.E3d */ : "2d" /* WebGPUConstants.TextureViewDimension.E2d */,
mipLevelCount: 1,
baseArrayLayer: texture.isCube ? layer * 6 + faceIndex : layer,
baseMipLevel: lodLevel,
arrayLayerCount: 1,
aspect: "all" /* WebGPUConstants.TextureAspect.All */,
};
this._rttRenderPassWrapper.depthAttachmentViewDescriptor = {
label: texture.label ? texture.label + " - Depth Attachment View" : "RTT - Depth Attachment View",
format: this._depthTextureFormat,
dimension: depthStencilTexture && depthStencilTexture.is3D ? "3d" /* WebGPUConstants.TextureViewDimension.E3d */ : "2d" /* WebGPUConstants.TextureViewDimension.E2d */,
mipLevelCount: 1,
baseArrayLayer: depthStencilTexture ? (depthStencilTexture.isCube ? layer * 6 + faceIndex : layer) : 0,
baseMipLevel: 0,
arrayLayerCount: 1,
aspect: "all" /* WebGPUConstants.TextureAspect.All */,
};
// We don't create the render pass just now, we do a lazy creation of the render pass, hoping the render pass will be created by a call to clear()...
// However, if snapshot rendering is enabled, we need to create the render pass immediately, to be sure currentRenderPass is not null when _endCurrentRenderPass() is called.
// (as in snapshot rendering mode, we may not have a call to clear() before _endCurrentRenderPass(), so lazy creation would not work)
if (this._snapshotRendering.play || this._snapshotRendering.record) {
this._startRenderTargetRenderPass(this._currentRenderTarget, false, null, false, false);
}
if (this._cachedViewport && !forceFullscreenViewport) {
this.setViewport(this._cachedViewport, requiredWidth, requiredHeight);
}
else {
if (!requiredWidth) {
requiredWidth = texture.width;
if (lodLevel) {
requiredWidth = requiredWidth / Math.pow(2, lodLevel);
}
}
if (!requiredHeight) {
requiredHeight = texture.height;
if (lodLevel) {
requiredHeight = requiredHeight / Math.pow(2, lodLevel);
}
}
this._viewport(0, 0, requiredWidth, requiredHeight);
}
this.wipeCaches();
}
/**
* Unbind the current render target texture from the WebGPU context
* @param texture defines the render target wrapper to unbind
* @param disableGenerateMipMaps defines a boolean indicating that mipmaps must not be generated
* @param onBeforeUnbind defines a function which will be called before the effective unbind
*/
unBindFramebuffer(texture, disableGenerateMipMaps = false, onBeforeUnbind) {
const saveCrt = this._currentRenderTarget;
this._currentRenderTarget = null; // to be iso with abstractEngine, this._currentRenderTarget must be null when onBeforeUnbind is called
if (onBeforeUnbind) {
onBeforeUnbind();
}
this._currentRenderTarget = saveCrt;
this._endCurrentRenderPass();
this._resolveAndGenerateMipMapsFramebuffer(texture, disableGenerateMipMaps);
this._currentRenderTarget = null;
this._mrtAttachments = [];
this._cacheRenderPipeline.setMRT([]);
this._cacheRenderPipeline.setMRTAttachments(this._mrtAttachments);
}
_resolveAndGenerateMipMapsFramebuffer(texture, disableGenerateMipMaps = false) {
const webglRtWrapper = texture;
if (!webglRtWrapper.disableAutomaticMSAAResolve) {
// we pass false as the second parameter because the color resolve has already been done automatically when the render pass ended
this.resolveFramebuffer(texture, false);
}
if (!disableGenerateMipMaps) {
if (texture.isMulti) {
this.generateMipMapsMultiFramebuffer(texture);
}
else {
this.generateMipMapsFramebuffer(texture);
}
}
}
/**
* Generates mipmaps for the texture of the (single) render target
* @param texture The render target containing the texture to generate the mipmaps for
*/
generateMipMapsFramebuffer(texture) {
if (!texture.isMulti && texture.texture?.generateMipMaps && !texture.isCube) {
this._generateMipmaps(texture.texture);
}
}
/**
* Resolves the MSAA texture of the render target into its non-MSAA version.
* Note that if "texture" is not a MSAA render target, no resolve is performed.
* @param texture The render target texture containing the MSAA texture to resolve
* @param resolveColors If true, resolve the color textures (default: true) - still subject to texture.resolveMSAAColors
*/
resolveFramebuffer(texture, resolveColors = true) {
if (texture.samples <= 1) {
return;
}
if (texture.resolveMSAAColors && resolveColors) {
const disableAutomaticMSAAResolve = texture.disableAutomaticMSAAResolve;
texture.disableAutomaticMSAAResolve = false;
// Simply bind and unbind the framebuffer to trigger the resolve
this.bindFramebuffer(texture);
this._startRenderTargetRenderPass(this._currentRenderTarget, false, null, false, false);
this.unBindFramebuffer(texture);
texture.disableAutomaticMSAAResolve = disableAutomaticMSAAResolve;
}
if (texture.resolveMSAADepth && texture._depthStencilTexture) {
const gpuTextureWrapper = texture._depthStencilTexture._hardwareTexture;
this._textureHelper.resolveMSAADepthTexture(gpuTextureWrapper.getMSAATexture(texture.samples), gpuTextureWrapper.underlyingResource, this._renderEncoder);
}
}
restoreDefaultFramebuffer(unbindOnly) {
if (this._currentRenderTarget) {
this.unBindFramebuffer(this._currentRenderTarget);
}
else {
if (!this._currentRenderPass && !unbindOnly) {
this._startMainRenderPass(false);
}
else {
this._endCurrentRenderPass();
}
}
if (this._cachedViewport) {
this.setViewport(this._cachedViewport);
}
this.wipeCaches();
}
//------------------------------------------------------------------------------
// Render
//------------------------------------------------------------------------------
/**
* @internal
*/
_setColorFormat(wrapper) {
const format = wrapper.colorAttachmentGPUTextures[0]?.format ?? null;
this._cacheRenderPipeline.setColorFormat(format);
if (this._colorFormat === format) {
return;
}
this._colorFormat = format;
}
/**
* @internal
*/
_setDepthTextureFormat(wrapper) {
this._cacheRenderPipeline.setDepthStencilFormat(wrapper.depthTextureFormat);
if (this._depthTextureFormat === wrapper.depthTextureFormat) {
return;
}
this._depthTextureFormat = wrapper.depthTextureFormat;
}
/**
* Set the dithering state (not applicable in WebGPU)
*/
setDitheringState() {
// Does not exist in WebGPU
}
/**
* Set the rasterizer state (not applicable in WebGPU)
*/
setRasterizerState() {
// Does not exist in WebGPU
}
/**
* @internal
*/
_executeWhenRenderingStateIsCompiled(pipelineContext, action) {
// No parallel shader compilation.
// No Async, so direct launch
action();
}
/**
* @internal
*/
bindSamplers() { }
/** @internal */
_getUnpackAlignement() {
return 1;
}
/**
* @internal
*/
_bindTextureDirectly() {
return false;
}
setStateCullFaceType(cullBackFaces, force = false) {
const cullFace = (this.cullBackFaces ?? cullBackFaces ?? true) ? 1 : 2;
if (this._depthCullingState.cullFace !== cullFace || force) {
this._depthCullingState.cullFace = cullFace;
}
}
/**
* Set various states to the webGL context
* @param culling defines culling state: true to enable culling, false to disable it
* @param zOffset defines the value to apply to zOffset (0 by default)
* @param force defines if states must be applied even if cache is up to date
* @param reverseSide defines if culling must be reversed (CCW if false, CW if true)
* @param cullBackFaces true to cull back faces, false to cull front faces (if culling is enabled)
* @param stencil stencil states to set
* @param zOffsetUnits defines the value to apply to zOffsetUnits (0 by default)
*/
setState(culling, zOffset = 0, force, reverseSide = false, cullBackFaces, stencil, zOffsetUnits = 0) {
// Culling
if (this._depthCullingState.cull !== culling || force) {
this._depthCullingState.cull = culling;
}
// Cull face
this.setStateCullFaceType(cullBackFaces, force);
// Z offset
this.setZOffset(zOffset);
this.setZOffsetUnits(zOffsetUnits);
// Front face
// Render targets are drawn Y-flipped (see the InternalsUBO yFactor in _draw), so their triangle
// winding is reversed here to compensate and keep the same faces visible. XR projection-layer targets
// opt out of that flip (_disableEngineYFlip) because the compositor presents them directly, so they
// keep the main-framebuffer winding.
const invertYRenderTarget = !!this._currentRenderTarget && !this._currentRenderTarget._disableEngineYFlip;
const frontFace = reverseSide ? (invertYRenderTarget ? 1 : 2) : invertYRenderTarget ? 2 : 1;
if (this._depthCullingState.frontFace !== frontFace || force) {
this._depthCullingState.frontFace = frontFace;
}
this._stencilStateComposer.stencilMaterial = stencil;
}
_applyRenderPassChanges(bundleList) {
const mustUpdateStencilRef = !this._stencilStateComposer.enabled ? false : this._mustUpdateStencilRef();
const mustUpdateBlendColor = !this._alphaState.alphaBlend ? false : this._mustUpdateBlendColor();
if (this._mustUpdateViewport()) {
this._applyViewport(bundleList);
}
if (this._mustUpdateScissor()) {
this._applyScissor(bundleList);
}
if (mustUpdateStencilRef) {
this._applyStencilRef(bundleList);
}
if (mustUpdateBlendColor) {
this._applyBlendColor(bundleList);
}
}
_draw(drawType, fillMode, start, count, instancesCount) {
const renderPass = this._getCurrentRenderPass();
const bundleList = this._bundleList;
this.applyStates();
const webgpuPipelineContext = this._currentEffect._pipelineContext;
// Render targets are rendered Y-flipped (yFactor = -1) so a later-sampled RTT matches the WebGL
// texture-space convention. XR projection-layer targets (_disableEngineYFlip) are presented directly by
// the compositor and must be rendered upright, so they use the non-inverting UBO like the canvas.
const invertYRenderTarget = !!this._currentRenderTarget && !this._currentRenderTarget._disableEngineYFlip;
this.bindUniformBufferBase(invertYRenderTarget ? this._ubInvertY : this._ubDontInvertY, 0, WebGPUShaderProcessor.InternalsUBOName);
this._currentDrawContext.setVertexPulling(this._currentMaterialContext.useVertexPulling, webgpuPipelineContext, this._currentVertexBuffers, this._cacheRenderPipeline.indexBuffer, // don't use this._currentIndexBuffer, it will have been set to null by _drawArraysType!
this._currentOverrideVertexBuffers);
if (webgpuPipelineContext.uniformBuffer) {
webgpuPipelineContext.uniformBuffer.update();
this.bindUniformBufferBase(webgpuPipelineContext.uniformBuffer.getBuffer(), 0, WebGPUShaderProcessor.LeftOvertUBOName);
}
if (this._snapshotRendering.play) {
this._reportDrawCall();
return;
}
if (!this.compatibilityMode &&
(this._currentDrawContext.isDirty(this._currentMaterialContext.updateId) || this._currentMaterialContext.isDirty || this._currentMaterialContext.forceBindGroupCreation)) {
this._currentDrawContext.fastBundle = undefined;
}
const useFastPath = !this.compatibilityMode && this._currentDrawContext.fastBundle;
let renderPass2 = renderPass;
if (useFastPath || this._snapshotRendering.record) {
this._applyRenderPassChanges(bundleList);
if (!this._snapshotRendering.record) {
this._counters.numBundleReuseNonCompatMode++;
if (this._currentDrawContext.indirectDrawBuffer) {
this._currentDrawContext.setIndirectData(count, instancesCount || 1, start);
}
bundleList.addBundle(this._currentDrawContext.fastBundle);
this._reportDrawCall();
return;
}
renderPass2 = bundleList.getBundleEncoder(this._cacheRenderPipeline.colorFormats, this._depthTextureFormat, this.currentSampleCount); // for snapshot recording mode
bundleList.numDrawCalls++;
}
let textureState = 0;
if (this._currentMaterialContext.hasFloatOrDepthTextures) {
let bitVal = 1;
for (let i = 0; i < webgpuPipelineContext.shaderProcessingContext.textureNames.length; ++i) {
const textureName = webgpuPipelineContext.shaderProcessingContext.textureNames[i];
const texture = this._currentMaterialContext.textures[textureName]?.texture;
const textureIsDepth = texture && texture.format >= Constants.TEXTUREFORMAT_DEPTH24_STENCIL8 && texture.format <= Constants.TEXTUREFORMAT_DEPTH32FLOAT_STENCIL8;
if ((texture?.type === Constants.TEXTURETYPE_FLOAT && !this._caps.textureFloatLinearFiltering) || textureIsDepth) {
textureState |= bitVal;
}
bitVal = bitVal << 1;
}
}
this._currentMaterialContext.textureState = textureState;
const pipeline = this._cacheRenderPipeline.getRenderPipeline(fillMode, this._currentEffect, this.currentSampleCount, textureState);
const bindGroups = this._cacheBindGroups.getBindGroups(webgpuPipelineContext, this._currentDrawContext, this._currentMaterialContext);
if (!this._snapshotRendering.record) {
this._applyRenderPassChanges(!this.compatibilityMode ? bundleList : null);
if (!this.compatibilityMode) {
this._counters.numBundleCreationNonCompatMode++;
renderPass2 = this._device.createRenderBundleEncoder({
colorFormats: this._cacheRenderPipeline.colorFormats,
depthStencilFormat: this._depthTextureFormat,
sampleCount: WebGPUTextureHelper.GetSample(this.currentSampleCount),
});
}
}
// bind pipeline
renderPass2.setPipeline(pipeline);
// bind index/vertex buffers
if (this._currentIndexBuffer) {
renderPass2.setIndexBuffer(this._currentIndexBuffer.underlyingResource, this._currentIndexBuffer.is32Bits ? "uint32" /* WebGPUConstants.IndexFormat.Uint32 */ : "uint16" /* WebGPUConstants.IndexFormat.Uint16 */, 0);
}
const vertexBuffers = this._cacheRenderPipeline.vertexBuffers;
for (let index = 0; index < vertexBuffers.length; index++) {
const vertexBuffer = vertexBuffers[index];
const buffer = vertexBuffer.effectiveBuffer;
if (buffer) {
renderPass2.setVertexBuffer(index, buffer.underlyingResource, vertexBuffer._validOffsetRange ? 0 : vertexBuffer.byteOffset);
}
}
// bind bind groups
for (let i = 0; i < bindGroups.length; i++) {
renderPass2.setBindGroup(i, bindGroups[i]);
}
// draw
const nonCompatMode = !this.compatibilityMode && !this._snapshotRendering.record;
if ((nonCompatMode || this._currentDrawContext._enableIndirectDrawInCompatMode) && this._currentDrawContext.indirectDrawBuffer) {
this._currentDrawContext.setIndirectData(count, instancesCount || 1, start);
if (drawType === 0) {
renderPass2.drawIndexedIndirect(this._currentDrawContext.indirectDrawBuffer, 0);
}
else {
renderPass2.drawIndirect(this._currentDrawContext.indirectDrawBuffer, 0);
}
}
else if (drawType === 0) {
renderPass2.drawIndexed(count, instancesCount || 1, start, 0, 0);
}
else {
renderPass2.draw(count, instancesCount || 1, start, 0);
}
if (nonCompatMode) {
this._currentDrawContext.fastBundle = renderPass2.finish();
bundleList.addBundle(this._currentDrawContext.fastBundle);
}
this._reportDrawCall();
}
/**
* 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)
*/
drawElementsType(fillMode, indexStart, indexCount, instancesCount = 1) {
this._draw(0, fillMode, indexStart, indexCount, instancesCount);
}
/**
* 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)
*/
drawArraysType(fillMode, verticesStart, verticesCount, instancesCount = 1) {
this._currentIndexBuffer = null;
this._draw(1, fillMode, verticesStart, verticesCount, instancesCount);
}
//------------------------------------------------------------------------------
// Async Pipeline Pre-Warming
//------------------------------------------------------------------------------
/**
* Asynchronously pre-creates one or more render pipelines so they are cached and ready
* to use without any compilation hitch when first rendered.
*
* Call this for effects and meshes that are not yet rendering but will be soon
* (e.g. streaming content, predicted material changes, pre-loading the next level).
*
* When passing an array, cache state is set once per entry and restored only at the end,
* making batch pre-warming efficient.
*
* @param options - a single options object, an array of options objects,
* or a raw GPURenderPipelineDescriptor for complete control
* @returns an array of Promises for the pipelines that had cache misses (empty if all were cached).
* When a raw GPURenderPipelineDescriptor is provided, always returns a single-element array.
*/
// eslint-disable-next-line no-restricted-syntax
createRenderPipelineAsync(options) {
// Raw GPURenderPipelineDescriptor: bypass the cache and create directly
if ("vertex" in options) {
return [this._device.createRenderPipelineAsync(options)];
}
const entries = Array.isArray(options) ? options : [options];
const promises = [];
const cache = this._cacheRenderPipeline;
for (const entry of entries) {
// Render target formats
cache.setColorFormat(entry.colorFormat ?? this._colorFormat);
cache.setDepthStencilFormat(entry.depthStencilFormat ?? this._depthTextureFormat);
// Vertex / index buffers from mesh
const geometry = entry.mesh.geometry;
if (!geometry) {
throw new Error("WebGPUEngine.createRenderPipelineAsync: mesh has no geometry to derive vertex/index buffers from.");
}
cache.setBuffers(geometry.getVertexBuffers(), geometry.getIndexBuffer(), null);
// Alpha / blend state
const alphaMode = entry.alphaMode ?? Constants.ALPHA_DISABLE;
if (alphaMode === Constants.ALPHA_DISABLE) {
cache.setAlphaBlendEnabled([false], 0);
}
else {
const prevAlphaMode = this._alphaMode[0];
this.setAlphaMode(alphaMode);
cache.setAlphaBlendEnabled(this._alphaState._alphaBlend, this._alphaState._numTargetEnabled);
cache.setAlphaBlendFactors(this._alphaState._blendFunctionParameters, this._alphaState._blendEquationParameters);
this.setAlphaMode(prevAlphaMode);
}
// Depth / stencil state
cache.setDepthWriteEnabled(entry.depthWrite ?? true);
cache.setDepthTestEnabled(entry.depthTest ?? true);
cache.setDepthCompare(entry.depthCompare ?? Constants.LEQUAL);
// Rasterization state
cache.setCullEnabled(entry.cullEnabled ?? true);
cache.setCullFace(entry.cullFace ?? 1);
cache.setFrontFace(entry.frontFace ?? 2);
// Write mask
cache.setWriteMask(entry.writeMask ?? 0xf);
// Stencil
cache.setStencilEnabled(entry.stencilEnabled ?? false);
const fillMode = entry.fillMode ?? Constants.MATERIAL_TriangleFillMode;
const sampleCount = entry.sampleCount ?? this.currentSampleCount;
const promise = cache.preWarmPipeline(fillMode, entry.effect, sampleCount, 0);
if (promise) {
promises.push(promise);
}
}
// Restore the cache state to the engine's current tracked state once after the entire batch
cache.setColorFormat(this._colorFormat);
cache.setDepthStencilFormat(this._depthTextureFormat);
cache.setBuffers(this._currentVertexBuffers ?? null, this._currentIndexBuffer, this._currentOverrideVertexBuffers ?? null);
cache.setDepthCullingState(this._depthCullingState.cull ?? false, this._depthCullingState.frontFace ?? 2, this._depthCullingState.cullFace ?? 1, this._depthCullingState.zOffset, this._depthCullingState.zOffsetUnits, this._depthCullingState.depthTest ?? true, this._depthCullingState.depthMask ?? true, this._depthCullingState.depthFunc);
cache.setAlphaBlendEnabled(this._alphaState._alphaBlend, this._alphaState._numTargetEnabled);
cache.setAlphaBlendFactors(this._alphaState._blendFunctionParameters, this._alphaState._blendEquationParameters);
cache.setStencilEnabled(this._stencilStateComposer.enabled ?? false);
cache.setWriteMask(this._colorWrite ? 0xf : 0);
return promises;
}
//------------------------------------------------------------------------------
// Dispose
//------------------------------------------------------------------------------
/**
* Dispose and release all associated resources
*/
dispose() {
this._isDisposed = true;
this.hideLoadingUI?.();
this._timestampQuery.dispose();
this._mainTexture?.destroy();
this._depthTexture?.destroy();
this._textureHelper.destroyDeferredTextures();
this._bufferManager.destroyDeferredBuffers();
this._device.destroy();
_CommonDispose(this, this._renderingCanvas);
super.dispose();
}
//------------------------------------------------------------------------------
// Misc
//------------------------------------------------------------------------------
/**
* Gets the current render width
* @param useScreen defines if screen size must be used (or the current render target if any)
* @returns a number defining the current render width
*/
getRenderWidth(useScreen = false) {
if (!useScreen && this._currentRenderTarget) {
return this._currentRenderTarget.width;
}
return this._renderingCanvas?.width ?? 0;
}
/**
* Gets the current render height
* @param useScreen defines if screen size must be used (or the current render target if any)
* @returns a number defining the current render height
*/
getRenderHeight(useScreen = false) {
if (!useScreen && this._currentRenderTarget) {
return this._currentRenderTarget.height;
}
return this._renderingCanvas?.height ?? 0;
}
//------------------------------------------------------------------------------
// Errors
//------------------------------------------------------------------------------
/**
* Get the current error code of the WebGPU context
* @returns the error code
*/
getError() {
// TODO WEBGPU. from the webgpu errors.
return 0;
}
//------------------------------------------------------------------------------
// External Textures
//------------------------------------------------------------------------------
/**
* Creates an external texture
* @param video video element
* @returns the external texture, or null if external textures are not supported by the engine
*/
createExternalTexture(video) {
const texture = new WebGPUExternalTexture(video);
return texture;
}
/**
* Sets an internal texture to the according uniform.
* @param name The name of the uniform in the effect
* @param texture The texture to apply
*/
setExternalTexture(name, texture) {
if (!texture) {
this._currentMaterialContext.setTexture(name, null);
return;
}
this._setInternalTexture(name, texture);
}
//------------------------------------------------------------------------------
// Samplers
//------------------------------------------------------------------------------
/**
* Sets a texture sampler to the according uniform.
* @param name The name of the uniform in the effect
* @param sampler The sampler to apply
*/
setTextureSampler(name, sampler) {
this._currentMaterialContext?.setSampler(name, sampler);
}
//------------------------------------------------------------------------------
// Storage Buffers
//------------------------------------------------------------------------------
/**
* Creates a storage buffer
* @param data the data for the storage buffer or the size of the buffer
* @param creationFlags flags to use when creating the buffer (see Constants.BUFFER_CREATIONFLAG_XXX). The BUFFER_CREATIONFLAG_STORAGE flag will be automatically added
* @param label defines the label of the buffer (for debug purpose)
* @returns the new buffer
*/
createStorageBuffer(data, creationFlags, label) {
return this._createBuffer(data, creationFlags | Constants.BUFFER_CREATIONFLAG_STORAGE, label);
}
/**
* Clears a storage buffer to zeroes
* @param storageBuffer the storage buffer to clear
* @param byteOffset the byte offset to start clearing (optional)
* @param byteLength the byte length to clear (optional)
*/
clearStorageBuffer(storageBuffer, byteOffset, byteLength) {
// clearBuffer is a command encoder operation and cannot be recorded while a render pass is open on the same encoder.
this._endCurrentRenderPass();
this._renderEncoder.clearBuffer(storageBuffer.underlyingResource, byteOffset, byteLength);
}
/**
* Updates a storage buffer
* @param buffer the storage buffer to update
* @param data the data used to update the storage buffer
* @param byteOffset the byte offset of the data
* @param byteLength the byte length of the data
*/
updateStorageBuffer(buffer, data, byteOffset, byteLength) {
const dataBuffer = buffer;
if (byteOffset === undefined) {
byteOffset = 0;
}
let view;
if (byteLength === undefined) {
if (data instanceof Array) {
view = new Float32Array(data);
}
else if (ArrayBuffer.isView(data)) {
view = data;
}
else {
view = new Uint8Array(data);
}
byteLength = view.byteLength;
}
else {
if (data instanceof Array) {
view = new Float32Array(data);
}
else if (ArrayBuffer.isView(data)) {
view = data;
}
else {
view = new Uint8Array(data);
}
}
this._bufferManager.setSubData(dataBuffer, byteOffset, view, 0, byteLength);
}
async _readFromGPUBuffer(gpuBuffer, size, buffer, noDelay) {
return await new Promise((resolve, reject) => {
const readFromBuffer = () => {
// eslint-disable-next-line github/no-then
gpuBuffer.mapAsync(1 /* WebGPUConstants.MapMode.Read */, 0, size).then(() => {
const copyArrayBuffer = gpuBuffer.getMappedRange(0, size);
let data = buffer;
if (data === undefined) {
data = new Uint8Array(size);
data.set(new Uint8Array(copyArrayBuffer));
}
else {
const ctor = data.constructor; // we want to create result data with the same type as buffer (Uint8Array, Float32Array, ...)
data = new ctor(data.buffer);
data.set(new ctor(copyArrayBuffer));
}
gpuBuffer.unmap();
this._bufferManager.releaseBuffer(gpuBuffer);
resolve(data);
}, (reason) => {
if (this.isDisposed) {
resolve(new Uint8Array());
}
else {
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
reject(reason);
}
});
};
if (noDelay) {
this.flushFramebuffer();
readFromBuffer();
}
else {
// we are using onEndFrameObservable because we need to map the gpuBuffer AFTER the command buffers
// have been submitted, else we get the error: "Buffer used in a submit while mapped"
this.onEndFrameObservable.addOnce(() => {
readFromBuffer();
});
}
});
}
/**
* Read data from a storage buffer
* @param storageBuffer The storage buffer to read from
* @param offset The offset in the storage buffer to start reading from (default: 0)
* @param size The number of bytes to read from the storage buffer (default: capacity of the buffer)
* @param buffer The buffer to write the data we have read from the storage buffer to (optional)
* @param noDelay If true, a call to flushFramebuffer will be issued so that the data can be read back immediately and not in engine.onEndFrameObservable. This can speed up data retrieval, at the cost of a small perf penalty (default: false).
* @returns If not undefined, returns the (promise) buffer (as provided by the 4th parameter) filled with the data, else it returns a (promise) Uint8Array with the data read from the storage buffer
*/
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/promise-function-async
readFromStorageBuffer(storageBuffer, offset, size, buffer, noDelay) {
size = size || storageBuffer.capacity;
const gpuBuffer = this._bufferManager.createRawBuffer(size, BufferUsage.MapRead | BufferUsage.CopyDst, undefined, "TempReadFromStorageBuffer");
this._renderEncoder.copyBufferToBuffer(storageBuffer.underlyingResource, offset ?? 0, gpuBuffer, 0, size);
return this._readFromGPUBuffer(gpuBuffer, size, buffer, noDelay);
}
/**
* Read data from multiple storage buffers
* @param storageBuffers The list of storage buffers to read from
* @param offset The offset in the storage buffer to start reading from (default: 0). This is the same offset for all storage buffers!
* @param size The number of bytes to read from each storage buffer (default: capacity of the first buffer)
* @param buffer The buffer to write the data we have read from the storage buffers to (optional). If provided, the buffer should be large enough to hold the data from all storage buffers!
* @param noDelay If true, a call to flushFramebuffer will be issued so that the data can be read back immediately and not in engine.onEndFrameObservable. This can speed up data retrieval, at the cost of a small perf penalty (default: false).
* @returns If not undefined, returns the (promise) buffer (as provided by the 4th parameter) filled with the data, else it returns a (promise) Uint8Array with the data read from the storage buffer
*/
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/promise-function-async
readFromMultipleStorageBuffers(storageBuffers, offset, size, buffer, noDelay) {
size = size || storageBuffers[0].capacity;
const gpuBuffer = this._bufferManager.createRawBuffer(size * storageBuffers.length, BufferUsage.MapRead | BufferUsage.CopyDst, undefined, "TempReadFromMultipleStorageBuffers");
for (let i = 0; i < storageBuffers.length; i++) {
this._renderEncoder.copyBufferToBuffer(storageBuffers[i].underlyingResource, offset ?? 0, gpuBuffer, i * size, size);
}
return this._readFromGPUBuffer(gpuBuffer, size * storageBuffers.length, buffer, noDelay);
}
/**
* Sets a storage buffer in the shader
* @param name Defines the name of the storage buffer as defined in the shader
* @param buffer Defines the value to give to the uniform
*/
setStorageBuffer(name, buffer) {
this._currentDrawContext?.setBuffer(name, buffer?.getBuffer() ?? null);
}
}
// Default glslang options.
WebGPUEngine._GlslangDefaultOptions = {
jsPath: `${Tools._DefaultCdnUrl}/glslang/glslang.js`,
wasmPath: `${Tools._DefaultCdnUrl}/glslang/glslang.wasm`,
};
WebGPUEngine._InstanceId = 0;
export { ThinWebGPUEngine as T, WebGPUEngine as W, WebGPUCacheRenderPipeline as a, WebGPUTextureHelper as b, WebGPUPerfCounter as c, WebGPURenderItemBeginOcclusionQuery as d, WebGPURenderItemEndOcclusionQuery as e };
//# sourceMappingURL=webgpuEngine.pure-BsxyiCJX.esm.js.map