Explorar o código

Merge pull request #9568 from Popov72/webgpu_cache

First iteration of WebGPU cache implementation
David Catuhe %!s(int64=4) %!d(string=hai) anos
pai
achega
914b88d040

+ 951 - 0
src/Engines/WebGPU/webgpuCacheRenderPipeline.ts

@@ -0,0 +1,951 @@
+import { Constants } from "../constants";
+import * as WebGPUConstants from './webgpuConstants';
+import { Effect } from "../../Materials/effect";
+import { InternalTexture } from "../../Materials/Textures/internalTexture";
+import { VertexBuffer } from "../../Meshes/buffer";
+import { DataBuffer } from "../../Meshes/dataBuffer";
+import { Nullable } from "../../types";
+import { WebGPUHardwareTexture } from "./webgpuHardwareTexture";
+import { WebGPUPipelineContext } from "./webgpuPipelineContext";
+
+enum StatePosition {
+    ShaderStage = 0,
+    RasterizationState = 1,
+    //DepthBias = 2, // not used, so remove it to improve perf
+    //DepthBiasClamp = 3, // not used, so remove it to improve perf
+    DepthBiasSlopeScale = 2,
+    MRTAttachments1 = 3,
+    MRTAttachments2 = 4,
+    ColorStates = 5,
+    DepthStencilState = 6,
+    StencilReadMask = 7,
+    StencilWriteMask = 8,
+    VertexState = 9, // vertex state will consume positions 9, 10, ... depending on the number of vertex inputs
+
+    NumStates = 10
+}
+
+const textureFormatToIndex: { [name: string]: number } = {
+    "" : 0,
+    "r8unorm": 1,
+    "r8snorm": 2,
+    "r8uint": 3,
+    "r8sint": 4,
+    "r16uint": 5,
+    "r16sint": 6,
+    "r16float": 7,
+    "rg8unorm": 8,
+    "rg8snorm": 9,
+    "rg8uint": 10,
+    "rg8sint": 11,
+    "r32uint": 12,
+    "r32sint": 13,
+    "r32float": 14,
+    "rg16uint": 15,
+    "rg16sint": 16,
+    "rg16float": 17,
+    "rgba8unorm": 18,
+    "rgba8unorm-srgb": 19,
+    "rgba8snorm": 20,
+    "rgba8uint": 21,
+    "rgba8sint": 22,
+    "bgra8unorm": 23,
+    "bgra8unorm-srgb": 24,
+    "rgb9e5ufloat": 25,
+    "rgb10a2unorm": 26,
+    "rg11b10ufloat": 27,
+    "rg32uint": 28,
+    "rg32sint": 29,
+    "rg32float": 30,
+    "rgba16uint": 31,
+    "rgba16sint": 32,
+    "rgba16float": 33,
+    "rgba32uint": 34,
+    "rgba32sint": 35,
+    "rgba32float": 36,
+    "stencil8": 37,
+    "depth16unorm": 38,
+    "depth24plus": 39,
+    "depth24plus-stencil8": 40,
+    "depth32float": 41,
+    "bc1-rgba-unorm": 42,
+    "bc1-rgba-unorm-srgb": 43,
+    "bc2-rgba-unorm": 44,
+    "bc2-rgba-unorm-srgb": 45,
+    "bc3-rgba-unorm": 46,
+    "bc3-rgba-unorm-srgb": 47,
+    "bc4-r-unorm": 48,
+    "bc4-r-snorm": 49,
+    "bc5-rg-unorm": 50,
+    "bc5-rg-snorm": 51,
+    "bc6h-rgb-ufloat": 52,
+    "bc6h-rgb-float": 53,
+    "bc7-rgba-unorm": 54,
+    "bc7-rgba-unorm-srgb": 55,
+    "depth24unorm-stencil8": 56,
+    "depth32float-stencil8": 57,
+};
+
+const alphaBlendFactorToIndex: { [name: number]: number } = {
+    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: 12, // BlendColor (alpha)
+    0x8004: 13, // OneMinusBlendColor (alpha)
+};
+
+const stencilOpToIndex: { [name: number]: number } = {
+    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
+};
+
+/** @hidden */
+export class WebGPUCacheRenderPipeline {
+
+    public static NumCacheHitWithoutHash = 0;
+    public static NumCacheHitWithHash = 0;
+    public static NumCacheMiss = 0;
+    public static NumPipelineCreationLastFrame = 0;
+
+    public disabled: boolean;
+
+    private static _Cache: { [hash: string]: GPURenderPipeline } = {};
+    private static _NumPipelineCreationCurrentFrame = 0;
+
+    private _device: GPUDevice;
+    private _states: string[];
+    private _isDirty: boolean;
+    private _currentRenderPipeline: GPURenderPipeline;
+    private _emptyVertexBuffer: VertexBuffer;
+
+    private _shaderId: number;
+    private _alphaToCoverageEnabled: boolean;
+    private _frontFace: number;
+    private _cullEnabled: boolean;
+    private _cullFace: number;
+    private _clampDepth: boolean;
+    private _rasterizationState: number;
+    private _depthBias: number;
+    private _depthBiasClamp: number;
+    private _depthBiasSlopeScale: number;
+    private _colorFormat: number;
+    private _webgpuColorFormat: GPUTextureFormat;
+    private _mrtAttachments1: number;
+    private _mrtAttachments2: number;
+    private _mrtFormats: GPUTextureFormat[];
+    private _alphaBlendEnabled: boolean;
+    private _alphaBlendFuncParams: Array<Nullable<number>>;
+    private _alphaBlendEqParams: Array<Nullable<number>>;
+    private _writeMask: number;
+    private _colorStates: number;
+    private _depthStencilFormat: number;
+    private _webgpuDepthStencilFormat: GPUTextureFormat | undefined;
+    private _depthTestEnabled: boolean;
+    private _depthWriteEnabled: boolean;
+    private _depthCompare: number;
+    private _stencilEnabled: boolean;
+    private _stencilFrontCompare: number;
+    private _stencilFrontDepthFailOp: number;
+    private _stencilFrontPassOp: number;
+    private _stencilFrontFailOp: number;
+    private _stencilReadMask: number;
+    private _stencilWriteMask: number;
+    private _depthStencilState: number;
+    private _vertexBuffers: Nullable<{ [key: string]: Nullable<VertexBuffer> }>;
+    private _indexBuffer: Nullable<DataBuffer>;
+
+    constructor(device: GPUDevice, emptyVertexBuffer: VertexBuffer) {
+        this._device = device;
+        this._states = [];
+        this._states.length = StatePosition.NumStates;
+        this._emptyVertexBuffer = emptyVertexBuffer;
+        this._mrtFormats = [];
+        this.disabled = false;
+        this.reset();
+    }
+
+    public reset(): void {
+        this._isDirty = true;
+        this.setAlphaToCoverage(false);
+        this.resetDepthCullingState();
+        this.setClampDepth(false);
+        //this.setDepthBias(0);
+        //this.setDepthBiasClamp(0);
+        this.setColorFormat(WebGPUConstants.TextureFormat.BGRA8Unorm);
+        this.setMRTAttachments([], []);
+        this.setAlphaBlendEnabled(false);
+        this.setAlphaBlendFactors([null, null, null, null], [null, null]);
+        this.setWriteMask(0xF);
+        this.setDepthStencilFormat(WebGPUConstants.TextureFormat.Depth24PlusStencil8);
+        this.setStencilEnabled(false);
+        this.resetStencilState();
+        this.setBuffers(null, null);
+    }
+
+    public getRenderPipeline(fillMode: number, effect: Effect, sampleCount: number): GPURenderPipeline {
+        if (this.disabled) {
+            const topology = WebGPUCacheRenderPipeline._GetTopology(fillMode);
+
+            this._currentRenderPipeline = this._createRenderPipeline(effect, topology, sampleCount);
+
+            WebGPUCacheRenderPipeline.NumCacheMiss++;
+            WebGPUCacheRenderPipeline._NumPipelineCreationCurrentFrame++;
+
+            return this._currentRenderPipeline;
+        }
+
+        this._setShaderStage(effect.uniqueId);
+        this._setRasterizationState(fillMode, sampleCount);
+        this._setColorStates();
+        this._setDepthStencilState();
+        this._setVertexState(effect);
+
+        if (!this._isDirty && this._currentRenderPipeline) {
+            WebGPUCacheRenderPipeline.NumCacheHitWithoutHash++;
+            return this._currentRenderPipeline;
+        }
+
+        this._isDirty = false;
+
+        let hash = this._states.join();
+        let pipeline = WebGPUCacheRenderPipeline._Cache[hash];
+
+        if (pipeline) {
+            this._currentRenderPipeline = pipeline;
+            WebGPUCacheRenderPipeline.NumCacheHitWithHash++;
+            return pipeline;
+        }
+
+        const topology = WebGPUCacheRenderPipeline._GetTopology(fillMode);
+
+        this._currentRenderPipeline = this._createRenderPipeline(effect, topology, sampleCount);
+        WebGPUCacheRenderPipeline._Cache[hash] = this._currentRenderPipeline;
+
+        WebGPUCacheRenderPipeline.NumCacheMiss++;
+        WebGPUCacheRenderPipeline._NumPipelineCreationCurrentFrame++;
+
+        return this._currentRenderPipeline;
+    }
+
+    public endFrame(): void {
+        WebGPUCacheRenderPipeline.NumPipelineCreationLastFrame = WebGPUCacheRenderPipeline._NumPipelineCreationCurrentFrame;
+        WebGPUCacheRenderPipeline._NumPipelineCreationCurrentFrame = 0;
+    }
+
+    public setAlphaToCoverage(enabled: boolean): void {
+        this._alphaToCoverageEnabled = enabled;
+    }
+
+    public setFrontFace(frontFace: number): void {
+        this._frontFace = frontFace;
+    }
+
+    public setCullEnabled(enabled: boolean): void {
+        this._cullEnabled = enabled;
+    }
+
+    public setCullFace(cullFace: number): void {
+        this._cullFace = cullFace;
+    }
+
+    public setClampDepth(clampDepth: boolean): void {
+        this._clampDepth = clampDepth;
+    }
+
+    public resetDepthCullingState(): void {
+        this.setDepthCullingState(false, 2, 1, 0, true, true, Constants.ALWAYS);
+    }
+
+    public setDepthCullingState(cullEnabled: boolean, frontFace: number, cullFace: number, zOffset: number, depthTestEnabled: boolean, depthWriteEnabled: boolean, depthCompare: Nullable<number>): void {
+        this._depthWriteEnabled = depthWriteEnabled;
+        this._depthTestEnabled = depthTestEnabled;
+        this._depthCompare = (depthCompare ?? Constants.ALWAYS) - 0x0200;
+        this._cullFace = cullFace;
+        this._cullEnabled = cullEnabled;
+        this._frontFace = frontFace;
+        this.setDepthBiasSlopeScale(zOffset);
+    }
+
+    /*public setDepthBias(depthBias: number): void {
+        if (this._depthBias !== depthBias) {
+            this._depthBias = depthBias;
+            this._states[StatePosition.DepthBias] = depthBias.toString();
+            this._isDirty = true;
+        }
+    }
+
+    public setDepthBiasClamp(depthBiasClamp: number): void {
+        if (this._depthBiasClamp !== depthBiasClamp) {
+            this._depthBiasClamp = depthBiasClamp;
+            this._states[StatePosition.DepthBiasClamp] = depthBiasClamp.toString();
+            this._isDirty = true;
+        }
+    }*/
+
+    public setDepthBiasSlopeScale(depthBiasSlopeScale: number): void {
+        if (this._depthBiasSlopeScale !== depthBiasSlopeScale) {
+            this._depthBiasSlopeScale = depthBiasSlopeScale;
+            this._states[StatePosition.DepthBiasSlopeScale] = depthBiasSlopeScale.toString();
+            this._isDirty = true;
+        }
+    }
+
+    public setColorFormat(format: GPUTextureFormat): void {
+        this._webgpuColorFormat = format;
+        this._colorFormat = textureFormatToIndex[format];
+    }
+
+    public setMRTAttachments(attachments: number[], textureArray: InternalTexture[]): void {
+        if (attachments.length > 10) {
+            // If we want more than 10 attachments we need to change this method but 10 seems plenty
+            // Note we can do better without changing this method if only dealing with texture formats that can be used as output attachments
+            // It could allow to use 5 bits (or even less) to code a texture format. For the time being, we use 6 bits as we need 58 different values (2^6=64)
+            // so we can encode 5 texture formats in 32 bits
+            throw "Can't handle more than 10 attachments for a MRT in cache render pipeline!";
+        }
+        let bits: number[] = [0, 0], indexBits = 0, mask = 0;
+        this._mrtFormats.length = attachments.length;
+        for (let i = 0; i < attachments.length; ++i) {
+            const index = attachments[i];
+            const texture = textureArray[index - 1];
+            const gpuWrapper = texture?._hardwareTexture as Nullable<WebGPUHardwareTexture>;
+
+            this._mrtFormats[i] = gpuWrapper?.format ?? this._webgpuColorFormat;
+
+            bits[indexBits] += textureFormatToIndex[this._mrtFormats[i]] << mask;
+            mask += 6;
+
+            if (mask >= 32) {
+                mask = 0;
+                indexBits++;
+            }
+        }
+        if (this._mrtAttachments1 !== bits[0] || this._mrtAttachments2 !== bits[1]) {
+            this._mrtAttachments1 = bits[0];
+            this._mrtAttachments2 = bits[1];
+            this._states[StatePosition.MRTAttachments1] = bits[0].toString();
+            this._states[StatePosition.MRTAttachments2] = bits[1].toString();
+            this._isDirty = true;
+        }
+    }
+
+    public setAlphaBlendEnabled(enabled: boolean): void {
+        this._alphaBlendEnabled = enabled;
+    }
+
+    public setAlphaBlendFactors(factors: Array<Nullable<number>>, operations: Array<Nullable<number>>): void {
+        this._alphaBlendFuncParams = factors;
+        this._alphaBlendEqParams = operations;
+    }
+
+    public setWriteMask(mask: number): void {
+        this._writeMask = mask;
+    }
+
+    public setDepthStencilFormat(format: GPUTextureFormat | undefined): void {
+        this._webgpuDepthStencilFormat = format;
+        this._depthStencilFormat = format === undefined ? 0 : textureFormatToIndex[format];
+    }
+
+    public setDepthTestEnabled(enabled: boolean): void {
+        this._depthTestEnabled = enabled;
+    }
+
+    public setDepthWriteEnabled(enabled: boolean): void {
+        this._depthWriteEnabled = enabled;
+    }
+
+    public setDepthCompare(func: Nullable<number>): void {
+        this._depthCompare = (func ?? Constants.ALWAYS) - 0x0200;
+    }
+
+    public setStencilEnabled(enabled: boolean): void {
+        this._stencilEnabled = enabled;
+    }
+
+    public setStencilCompare(func: Nullable<number>): void {
+        this._stencilFrontCompare = (func ?? Constants.ALWAYS) - 0x0200;
+    }
+
+    public setStencilDepthFailOp(op: Nullable<number>): void {
+        this._stencilFrontDepthFailOp = op === null ? 1 /* KEEP */ : stencilOpToIndex[op];
+    }
+
+    public setStencilPassOp(op: Nullable<number>): void {
+        this._stencilFrontPassOp = op === null ? 2 /* REPLACE */ : stencilOpToIndex[op];
+    }
+
+    public setStencilFailOp(op: Nullable<number>): void {
+        this._stencilFrontFailOp = op === null ? 1 /* KEEP */ : stencilOpToIndex[op];
+    }
+
+    public setStencilReadMask(mask: number): void {
+        if (this._stencilReadMask !== mask) {
+            this._stencilReadMask = mask;
+            this._states[StatePosition.StencilReadMask] = mask.toString();
+            this._isDirty = true;
+        }
+    }
+
+    public setStencilWriteMask(mask: number): void {
+        if (this._stencilWriteMask !== mask) {
+            this._stencilWriteMask = mask;
+            this._states[StatePosition.StencilWriteMask] = mask.toString();
+            this._isDirty = true;
+        }
+    }
+
+    public resetStencilState(): void {
+        this.setStencilState(false, Constants.ALWAYS, Constants.KEEP, Constants.REPLACE, Constants.KEEP, 0xFF, 0xFF);
+    }
+
+    public setStencilState(stencilEnabled: boolean, compare: Nullable<number>, depthFailOp: Nullable<number>, passOp: Nullable<number>, failOp: Nullable<number>, readMask: number, writeMask: number): void {
+        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.setStencilReadMask(readMask);
+        this.setStencilWriteMask(writeMask);
+    }
+
+    public setBuffers(vertexBuffers: Nullable<{ [key: string]: Nullable<VertexBuffer> }>, indexBuffer: Nullable<DataBuffer>): void {
+        this._vertexBuffers = vertexBuffers;
+        this._indexBuffer = indexBuffer;
+    }
+
+    private static _GetTopology(fillMode: number): GPUPrimitiveTopology {
+        switch (fillMode) {
+            // Triangle views
+            case Constants.MATERIAL_TriangleFillMode:
+                return WebGPUConstants.PrimitiveTopology.TriangleList;
+            case Constants.MATERIAL_PointFillMode:
+                return WebGPUConstants.PrimitiveTopology.PointList;
+            case Constants.MATERIAL_WireFrameFillMode:
+                return WebGPUConstants.PrimitiveTopology.LineList;
+            // Draw modes
+            case Constants.MATERIAL_PointListDrawMode:
+                return WebGPUConstants.PrimitiveTopology.PointList;
+            case Constants.MATERIAL_LineListDrawMode:
+                return WebGPUConstants.PrimitiveTopology.LineList;
+            case Constants.MATERIAL_LineLoopDrawMode:
+                // return this._gl.LINE_LOOP;
+                // TODO WEBGPU. Line Loop Mode Fallback at buffer load time.
+                throw "LineLoop is an unsupported fillmode in WebGPU";
+            case Constants.MATERIAL_LineStripDrawMode:
+                return WebGPUConstants.PrimitiveTopology.LineStrip;
+            case Constants.MATERIAL_TriangleStripDrawMode:
+                return WebGPUConstants.PrimitiveTopology.TriangleStrip;
+            case Constants.MATERIAL_TriangleFanDrawMode:
+                // return this._gl.TRIANGLE_FAN;
+                // TODO WEBGPU. Triangle Fan Mode Fallback at buffer load time.
+                throw "TriangleFan is an unsupported fillmode in WebGPU";
+            default:
+                return WebGPUConstants.PrimitiveTopology.TriangleList;
+        }
+    }
+
+    private static _GetAphaBlendOperation(operation: Nullable<number>): GPUBlendOperation {
+        switch (operation) {
+            case 0x8006:
+                return WebGPUConstants.BlendOperation.Add;
+            case 0x800A:
+                return WebGPUConstants.BlendOperation.Subtract;
+            case 0x800B:
+                return WebGPUConstants.BlendOperation.ReverseSubtract;
+            case 0x8007:
+                return WebGPUConstants.BlendOperation.Min;
+            case 0x8008:
+                return WebGPUConstants.BlendOperation.Max;
+            default:
+                return WebGPUConstants.BlendOperation.Add;
+        }
+    }
+
+    private static _GetAphaBlendFactor(factor: Nullable<number>): GPUBlendFactor {
+        switch (factor) {
+            case 0:
+                return WebGPUConstants.BlendFactor.Zero;
+            case 1:
+                return WebGPUConstants.BlendFactor.One;
+            case 0x0300:
+                return WebGPUConstants.BlendFactor.SrcColor;
+            case 0x0301:
+                return WebGPUConstants.BlendFactor.OneMinusSrcColor;
+            case 0x0302:
+                return WebGPUConstants.BlendFactor.SrcAlpha;
+            case 0x0303:
+                return WebGPUConstants.BlendFactor.OneMinusSrcAlpha;
+            case 0x0304:
+                return WebGPUConstants.BlendFactor.DstAlpha;
+            case 0x0305:
+                return WebGPUConstants.BlendFactor.OneMinusDstAlpha;
+            case 0x0306:
+                return WebGPUConstants.BlendFactor.DstColor;
+            case 0x0307:
+                return WebGPUConstants.BlendFactor.OneMinusDstColor;
+            case 0x0308:
+                return WebGPUConstants.BlendFactor.SrcAlphaSaturated;
+            case 0x8001:
+                return WebGPUConstants.BlendFactor.BlendColor;
+            case 0x8002:
+                return WebGPUConstants.BlendFactor.OneMinusBlendColor;
+            case 0x8003:
+                return WebGPUConstants.BlendFactor.BlendColor;
+            case 0x8004:
+                return WebGPUConstants.BlendFactor.OneMinusBlendColor;
+            default:
+                return WebGPUConstants.BlendFactor.One;
+        }
+    }
+
+    private static _GetCompareFunction(compareFunction: number): GPUCompareFunction {
+        switch (compareFunction) {
+            case 0: // NEVER
+                return WebGPUConstants.CompareFunction.Never;
+            case 1: // LESS
+                return WebGPUConstants.CompareFunction.Less;
+            case 2: // EQUAL
+                return WebGPUConstants.CompareFunction.Equal;
+            case 3: // LEQUAL
+                return WebGPUConstants.CompareFunction.LessEqual;
+            case 4: // GREATER
+                return WebGPUConstants.CompareFunction.Greater;
+            case 5: // NOTEQUAL
+                return WebGPUConstants.CompareFunction.NotEqual;
+            case 6: // GEQUAL
+                return WebGPUConstants.CompareFunction.GreaterEqual;
+            case 7: // ALWAYS
+                return WebGPUConstants.CompareFunction.Always;
+        }
+        return WebGPUConstants.CompareFunction.Never;
+    }
+
+    private static _GetStencilOpFunction(operation: number): GPUStencilOperation {
+        switch (operation) {
+            case 0:
+                return WebGPUConstants.StencilOperation.Zero;
+            case 1:
+                return WebGPUConstants.StencilOperation.Keep;
+            case 2:
+                return WebGPUConstants.StencilOperation.Replace;
+            case 3:
+                return WebGPUConstants.StencilOperation.IncrementClamp;
+            case 4:
+                return WebGPUConstants.StencilOperation.DecrementClamp;
+            case 5:
+                return WebGPUConstants.StencilOperation.Invert;
+            case 6:
+                return WebGPUConstants.StencilOperation.IncrementWrap;
+            case 7:
+                return WebGPUConstants.StencilOperation.DecrementWrap;
+        }
+        return WebGPUConstants.StencilOperation.Keep;
+    }
+
+    private static _GetVertexInputDescriptorFormat(vertexBuffer: VertexBuffer): GPUVertexFormat {
+        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 ? WebGPUConstants.VertexFormat.Char2Norm : WebGPUConstants.VertexFormat.Char2;
+                    case 3:
+                    case 4:
+                        return normalized ? WebGPUConstants.VertexFormat.Char4Norm : WebGPUConstants.VertexFormat.Char4;
+                }
+                break;
+            case VertexBuffer.UNSIGNED_BYTE:
+                switch (size) {
+                    case 1:
+                    case 2:
+                        return normalized ? WebGPUConstants.VertexFormat.Uchar2Norm : WebGPUConstants.VertexFormat.Uchar2;
+                    case 3:
+                    case 4:
+                        return normalized ? WebGPUConstants.VertexFormat.Uchar4Norm : WebGPUConstants.VertexFormat.Uchar4;
+                }
+                break;
+            case VertexBuffer.SHORT:
+                switch (size) {
+                    case 1:
+                    case 2:
+                        return normalized ? WebGPUConstants.VertexFormat.Short2Norm : WebGPUConstants.VertexFormat.Short2;
+                    case 3:
+                    case 4:
+                        return normalized ? WebGPUConstants.VertexFormat.Short4Norm : WebGPUConstants.VertexFormat.Short4;
+                }
+                break;
+            case VertexBuffer.UNSIGNED_SHORT:
+                switch (size) {
+                    case 1:
+                    case 2:
+                        return normalized ? WebGPUConstants.VertexFormat.Ushort2Norm : WebGPUConstants.VertexFormat.Ushort2;
+                    case 3:
+                    case 4:
+                        return normalized ? WebGPUConstants.VertexFormat.Ushort4Norm : WebGPUConstants.VertexFormat.Ushort4;
+                }
+                break;
+            case VertexBuffer.INT:
+                switch (size) {
+                    case 1:
+                        return WebGPUConstants.VertexFormat.Int;
+                    case 2:
+                        return WebGPUConstants.VertexFormat.Int2;
+                    case 3:
+                        return WebGPUConstants.VertexFormat.Int3;
+                    case 4:
+                        return WebGPUConstants.VertexFormat.Int4;
+                }
+                break;
+            case VertexBuffer.UNSIGNED_INT:
+                switch (size) {
+                    case 1:
+                        return WebGPUConstants.VertexFormat.Uint;
+                    case 2:
+                        return WebGPUConstants.VertexFormat.Uint2;
+                    case 3:
+                        return WebGPUConstants.VertexFormat.Uint3;
+                    case 4:
+                        return WebGPUConstants.VertexFormat.Uint4;
+                }
+                break;
+            case VertexBuffer.FLOAT:
+                switch (size) {
+                    case 1:
+                        return WebGPUConstants.VertexFormat.Float;
+                    case 2:
+                        return WebGPUConstants.VertexFormat.Float2;
+                    case 3:
+                        return WebGPUConstants.VertexFormat.Float3;
+                    case 4:
+                        return WebGPUConstants.VertexFormat.Float4;
+                }
+                break;
+        }
+
+        throw new Error(`Invalid Format '${vertexBuffer.getKind()}' - type=${type}, normalized=${normalized}, size=${size}`);
+    }
+
+    private _getAphaBlendState(): GPUBlendDescriptor {
+        if (!this._alphaBlendEnabled) {
+            return { };
+        }
+
+        return {
+            srcFactor: WebGPUCacheRenderPipeline._GetAphaBlendFactor(this._alphaBlendFuncParams[2]),
+            dstFactor: WebGPUCacheRenderPipeline._GetAphaBlendFactor(this._alphaBlendFuncParams[3]),
+            operation: WebGPUCacheRenderPipeline._GetAphaBlendOperation(this._alphaBlendEqParams[1]),
+        };
+    }
+
+    private _getColorBlendState(): GPUBlendDescriptor {
+        if (!this._alphaBlendEnabled) {
+            return { };
+        }
+
+        return {
+            srcFactor: WebGPUCacheRenderPipeline._GetAphaBlendFactor(this._alphaBlendFuncParams[0]),
+            dstFactor: WebGPUCacheRenderPipeline._GetAphaBlendFactor(this._alphaBlendFuncParams[1]),
+            operation: WebGPUCacheRenderPipeline._GetAphaBlendOperation(this._alphaBlendEqParams[0]),
+        };
+    }
+
+    private _setShaderStage(id: number): void {
+        if (this._shaderId !== id) {
+            this._shaderId = id;
+            this._states[StatePosition.ShaderStage] = id.toString();
+            this._isDirty = true;
+        }
+    }
+
+    private _setRasterizationState(topology: number, sampleCount: number): void {
+        const frontFace = this._frontFace;
+        const cullMode = this._cullEnabled ? this._cullFace : 0;
+        const clampDepth = this._clampDepth ? 1 : 0;
+        const alphaToCoverage = this._alphaToCoverageEnabled ? 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.toString();
+            this._isDirty = true;
+        }
+    }
+
+    private _setColorStates(): void {
+        let colorStates = ((this._writeMask ? 1 : 0) << 22) + (this._colorFormat << 23);
+
+        if (this._alphaBlendEnabled) {
+            colorStates +=
+                ((this._alphaBlendFuncParams[0] === null ? 2 : alphaBlendFactorToIndex[this._alphaBlendFuncParams[0]]) << 0) +
+                ((this._alphaBlendFuncParams[1] === null ? 2 : alphaBlendFactorToIndex[this._alphaBlendFuncParams[1]]) << 4) +
+                ((this._alphaBlendFuncParams[2] === null ? 2 : alphaBlendFactorToIndex[this._alphaBlendFuncParams[2]]) << 8) +
+                ((this._alphaBlendFuncParams[3] === null ? 2 : alphaBlendFactorToIndex[this._alphaBlendFuncParams[3]]) << 12) +
+                ((this._alphaBlendEqParams[0] === null ? 1 : this._alphaBlendEqParams[0] - 0x8005) << 16) +
+                ((this._alphaBlendEqParams[1] === null ? 1 : this._alphaBlendEqParams[1] - 0x8005) << 19);
+        }
+
+        if (colorStates !== this._colorStates) {
+            this._colorStates = colorStates;
+            this._states[StatePosition.ColorStates] = this._colorStates.toString();
+            this._isDirty = true;
+        }
+    }
+
+    private _setDepthStencilState(): void {
+        let stencilState = !this._stencilEnabled ?
+            7 /* ALWAYS */ + (1 /* KEEP */ << 3) + (1 /* KEEP */ << 6) + (1 /* KEEP */ << 9) :
+            this._stencilFrontCompare + (this._stencilFrontDepthFailOp << 3) + (this._stencilFrontPassOp << 6) + (this._stencilFrontFailOp << 9);
+
+        const depthStencilState =
+                this._depthStencilFormat +
+                ((this._depthWriteEnabled ? 1 : 0) << 6) +
+                ((this._depthTestEnabled ? this._depthCompare : 7 /* ALWAYS */) << 7) +
+                (stencilState << 10); // stencil front - stencil back is the same
+
+        if (this._depthStencilState !== depthStencilState) {
+            this._depthStencilState = depthStencilState;
+            this._states[StatePosition.DepthStencilState] = this._depthStencilState.toString();
+            this._isDirty = true;
+        }
+    }
+
+    private _setVertexState(effect: Effect): void {
+        const currStateLen = this._states.length;
+        let newNumStates = StatePosition.VertexState;
+
+        const attributes = effect.getAttributesNames();
+        for (var index = 0; index < attributes.length; index++) {
+            const location = effect.getAttributeLocation(index);
+
+            if (location >= 0) {
+                let  vertexBuffer = 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;
+                }
+
+                const type = vertexBuffer.type - 5120;
+                const normalized = vertexBuffer.normalized ? 1 : 0;
+                const size = vertexBuffer.getSize();
+                const stepMode = vertexBuffer.getIsInstanced() ? 1 : 0;
+                const stride = vertexBuffer.byteStride;
+
+                const vid =
+                    ((type << 0) +
+                    (normalized << 3) +
+                    (size << 4) +
+                    (stepMode << 6) +
+                    (location << 7) +
+                    (stride << 12)).toString();
+
+                this._isDirty = this._isDirty || this._states[newNumStates] !== vid;
+                this._states[newNumStates++] = vid;
+            }
+        }
+
+        this._states.length = newNumStates;
+        this._isDirty = this._isDirty || newNumStates !== currStateLen;
+    }
+
+    private _createPipelineLayout(webgpuPipelineContext: WebGPUPipelineContext): GPUPipelineLayout {
+        const bindGroupLayouts: GPUBindGroupLayout[] = [];
+
+        for (let i = 0; i < webgpuPipelineContext.shaderProcessingContext.orderedUBOsAndSamplers.length; i++) {
+            const setDefinition = webgpuPipelineContext.shaderProcessingContext.orderedUBOsAndSamplers[i];
+            if (setDefinition === undefined) {
+                const entries: GPUBindGroupLayoutEntry[] = [];
+                const uniformsBindGroupLayout = this._device.createBindGroupLayout({
+                    entries,
+                });
+                bindGroupLayouts[i] = uniformsBindGroupLayout;
+                continue;
+            }
+
+            const entries: GPUBindGroupLayoutEntry[] = [];
+            for (let j = 0; j < setDefinition.length; j++) {
+                const bindingDefinition = webgpuPipelineContext.shaderProcessingContext.orderedUBOsAndSamplers[i][j];
+                if (bindingDefinition === undefined) {
+                    continue;
+                }
+
+                let visibility = 0;
+                if (bindingDefinition.usedInVertex) {
+                    visibility = visibility | WebGPUConstants.ShaderStage.Vertex;
+                }
+                if (bindingDefinition.usedInFragment) {
+                    visibility = visibility | WebGPUConstants.ShaderStage.Fragment;
+                }
+
+                if (bindingDefinition.isSampler) {
+                    entries.push({
+                        binding: j,
+                        visibility,
+                        type: bindingDefinition.isComparisonSampler ? WebGPUConstants.BindingType.ComparisonSampler : WebGPUConstants.BindingType.Sampler
+                    });
+                } else if (bindingDefinition.isTexture) {
+                    entries.push({
+                        binding: j,
+                        visibility,
+                        type: WebGPUConstants.BindingType.SampledTexture,
+                        viewDimension: bindingDefinition.textureDimension,
+                        textureComponentType: bindingDefinition.componentType,
+                        // TODO WEBGPU.
+                        // hasDynamicOffset?: boolean;
+                        // storageTextureFormat?: GPUTextureFormat;
+                        // minBufferBindingSize?: number;
+                    });
+                } else {
+                    entries.push({
+                        binding: j,
+                        visibility,
+                        type: WebGPUConstants.BindingType.UniformBuffer,
+                    });
+                }
+            }
+
+            if (entries.length > 0) {
+                const uniformsBindGroupLayout = this._device.createBindGroupLayout({
+                    entries,
+                });
+                bindGroupLayouts[i] = uniformsBindGroupLayout;
+            }
+        }
+
+        webgpuPipelineContext.bindGroupLayouts = bindGroupLayouts;
+
+        return this._device.createPipelineLayout({ bindGroupLayouts });
+    }
+
+    private _getVertexInputDescriptor(effect: Effect, topology: GPUPrimitiveTopology): GPUVertexStateDescriptor {
+        const descriptors: GPUVertexBufferLayoutDescriptor[] = [];
+        const attributes = effect.getAttributesNames();
+        for (var index = 0; index < attributes.length; index++) {
+            const location = effect.getAttributeLocation(index);
+
+            if (location >= 0) {
+                let  vertexBuffer = 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;
+                }
+
+                const attributeDescriptor: GPUVertexAttributeDescriptor = {
+                    shaderLocation: location,
+                    offset: 0, // not available in WebGL
+                    format: WebGPUCacheRenderPipeline._GetVertexInputDescriptorFormat(vertexBuffer),
+                };
+
+                // TODO WEBGPU. Factorize the one with the same underlying buffer.
+                const vertexBufferDescriptor: GPUVertexBufferLayoutDescriptor = {
+                    arrayStride: vertexBuffer.byteStride,
+                    stepMode: vertexBuffer.getIsInstanced() ? WebGPUConstants.InputStepMode.Instance : WebGPUConstants.InputStepMode.Vertex,
+                    attributes: [attributeDescriptor]
+                };
+
+               descriptors.push(vertexBufferDescriptor);
+            }
+        }
+
+        const inputStateDescriptor: GPUVertexStateDescriptor = {
+            vertexBuffers: descriptors
+        };
+
+        if (topology === WebGPUConstants.PrimitiveTopology.LineStrip || topology === WebGPUConstants.PrimitiveTopology.TriangleStrip) {
+            inputStateDescriptor.indexFormat = !this._indexBuffer || this._indexBuffer.is32Bits ? WebGPUConstants.IndexFormat.Uint32 : WebGPUConstants.IndexFormat.Uint16;
+        }
+
+        return inputStateDescriptor;
+    }
+
+    private _createRenderPipeline(effect: Effect, topology: GPUPrimitiveTopology, sampleCount: number, createLayout = true): GPURenderPipeline {
+        const webgpuPipelineContext = effect._pipelineContext as WebGPUPipelineContext;
+        const inputStateDescriptor = this._getVertexInputDescriptor(effect, topology);
+        const pipelineLayout = createLayout ? this._createPipelineLayout(webgpuPipelineContext) : undefined;
+
+        const colorStates: Array<GPUColorStateDescriptor> = [];
+        const alphaBlend = this._getAphaBlendState();
+        const colorBlend = this._getColorBlendState();
+
+        if (this._mrtAttachments1 > 0) {
+            for (let i = 0; i < this._mrtFormats.length; ++i) {
+                colorStates.push({
+                    format: this._mrtFormats[i],
+                    alphaBlend,
+                    colorBlend,
+                    writeMask: this._writeMask,
+                });
+            }
+        } else {
+            colorStates.push({
+                format: this._webgpuColorFormat,
+                alphaBlend,
+                colorBlend,
+                writeMask: this._writeMask,
+            });
+        }
+
+        const stencilFrontBack: GPUStencilStateFaceDescriptor = {
+            compare: WebGPUCacheRenderPipeline._GetCompareFunction(this._stencilFrontCompare),
+            depthFailOp: WebGPUCacheRenderPipeline._GetStencilOpFunction(this._stencilFrontDepthFailOp),
+            failOp: WebGPUCacheRenderPipeline._GetStencilOpFunction(this._stencilFrontFailOp),
+            passOp: WebGPUCacheRenderPipeline._GetStencilOpFunction(this._stencilFrontPassOp)
+        };
+
+        return this._device.createRenderPipeline({
+            layout: pipelineLayout,
+            ...webgpuPipelineContext.stages!,
+            primitiveTopology: topology,
+            rasterizationState: {
+                frontFace: this._frontFace === 1 ? WebGPUConstants.FrontFace.CCW : WebGPUConstants.FrontFace.CW,
+                cullMode: !this._cullEnabled ? WebGPUConstants.CullMode.None : this._cullFace === 2 ? WebGPUConstants.CullMode.Front : WebGPUConstants.CullMode.Back,
+                depthBias: this._depthBias,
+                depthBiasClamp: this._depthBiasClamp,
+                depthBiasSlopeScale: this._depthBiasSlopeScale,
+            },
+            colorStates,
+
+            sampleCount,
+            depthStencilState: this._webgpuDepthStencilFormat === undefined ? undefined : {
+                depthWriteEnabled: this._depthWriteEnabled,
+                depthCompare: this._depthTestEnabled ? WebGPUCacheRenderPipeline._GetCompareFunction(this._depthCompare) : WebGPUConstants.CompareFunction.Always,
+                format: this._webgpuDepthStencilFormat,
+                stencilFront: stencilFrontBack,
+                stencilBack: stencilFrontBack,
+                stencilReadMask: this._stencilReadMask,
+                stencilWriteMask: this._stencilWriteMask,
+            },
+
+            vertexState: inputStateDescriptor,
+        });
+    }
+
+}

+ 7 - 0
src/Engines/WebGPU/webgpuCacheSampler.ts

@@ -54,8 +54,11 @@ export class WebGPUCacheSampler {
     private _samplers: { [hash: number]: GPUSampler } = {};
     private _device: GPUDevice;
 
+    public disabled: boolean;
+
     constructor(device: GPUDevice) {
         this._device = device;
+        this.disabled = false;
     }
 
     private static _GetSamplerHashCode(texture: InternalTexture): number {
@@ -232,6 +235,10 @@ export class WebGPUCacheSampler {
     }
 
     public getSampler(internalTexture: InternalTexture, bypassCache = false): GPUSampler {
+        if (this.disabled) {
+            return this._device.createSampler(WebGPUCacheSampler._GetSamplerDescriptor(internalTexture));
+        }
+
         const hash = bypassCache ? 0 : WebGPUCacheSampler._GetSamplerHashCode(internalTexture);
 
         let sampler = bypassCache ? undefined : this._samplers[hash];

+ 3 - 0
src/Engines/WebGPU/webgpuPipelineContext.ts

@@ -71,6 +71,7 @@ export class WebGPUPipelineContext implements IPipelineContext {
     public textures: { [name: string]: Nullable<IWebGPUPipelineContextTextureCache> } = { };
 
     public bindGroupLayouts: GPUBindGroupLayout[];
+    public bindGroupsCache: { [key: string]: GPUBindGroup[] };
 
     /**
      * Stores the uniform buffer
@@ -99,6 +100,8 @@ export class WebGPUPipelineContext implements IPipelineContext {
         this._name = "unnamed";
         this.shaderProcessingContext = shaderProcessingContext;
         this.leftOverUniformsByName = {};
+        this.engine = engine;
+        this.bindGroupsCache = {};
     }
 
     public _handlesSpectorRebuildCallback(onCompiled: (program: any) => void): void {

+ 2 - 0
src/Engines/WebGPU/webgpuShaderProcessingContext.ts

@@ -56,6 +56,7 @@ export class WebGPUShaderProcessingContext implements ShaderProcessingContext {
 
     public orderedAttributes: string[];
     public orderedUBOsAndSamplers: WebGPUBindingDescription[][];
+    public uniformBufferNames: string[];
 
     private _attributeNextLocation: number;
     private _varyingNextLocation: number;
@@ -73,6 +74,7 @@ export class WebGPUShaderProcessingContext implements ShaderProcessingContext {
 
         this.orderedAttributes = [];
         this.orderedUBOsAndSamplers = [];
+        this.uniformBufferNames = [];
 
         this.leftOverUniforms = [];
     }

+ 14 - 0
src/Engines/WebGPU/webgpuShaderProcessors.ts

@@ -441,6 +441,20 @@ export class WebGPUShaderProcessor implements IShaderProcessor {
             fragmentCode = ubo + fragmentCode;
         }
 
+        // collect all the buffer names for faster processing later in _getBindGroupsToRender
+        for (let i = 0; i < webgpuProcessingContext.orderedUBOsAndSamplers.length; i++) {
+            const setDefinition = webgpuProcessingContext.orderedUBOsAndSamplers[i];
+            if (setDefinition === undefined) {
+                continue;
+            }
+            for (let j = 0; j < setDefinition.length; j++) {
+                const bindingDefinition = webgpuProcessingContext.orderedUBOsAndSamplers[i][j];
+                if (bindingDefinition && !bindingDefinition.isSampler && !bindingDefinition.isTexture) {
+                    webgpuProcessingContext.uniformBufferNames.push(bindingDefinition.name);
+                }
+            }
+        }
+
         this._preProcessors = null as any;
 
         return { vertexCode, fragmentCode };

+ 1 - 1
src/Engines/WebGPU/webgpuTextureHelper.ts

@@ -840,7 +840,7 @@ export class WebGPUTextureHelper {
                         arrayLayerCount: 1,
                         baseArrayLayer: faceIndex,
                     }),
-                    loadValue: { r: 0.0, g: 0.0, b: 0.0, a: 0.0 },
+                    loadValue: WebGPUConstants.LoadOp.Load,
                 }],
             });
 

+ 1 - 0
src/Engines/index.ts

@@ -10,6 +10,7 @@ export * from "./IPipelineContext";
 export * from "./WebGL/webGLPipelineContext";
 export * from "./WebGPU/webgpuConstants";
 export * from "./webgpuEngine";
+export * from "./WebGPU/webgpuCacheRenderPipeline";
 export * from "./WebGL/webGL2ShaderProcessors";
 export * from "./nativeEngine";
 export * from "./Processors/shaderCodeInliner";

+ 128 - 549
src/Engines/webgpuEngine.ts

@@ -11,7 +11,7 @@ import { _TimeToken } from "../Instrumentation/timeToken";
 import { Constants } from "./constants";
 import * as WebGPUConstants from './WebGPU/webgpuConstants';
 import { VertexBuffer } from "../Meshes/buffer";
-import { WebGPUPipelineContext, IWebGPUPipelineContextVertexInputsCache, IWebGPURenderPipelineStageDescriptor } from './WebGPU/webgpuPipelineContext';
+import { WebGPUPipelineContext, IWebGPURenderPipelineStageDescriptor } from './WebGPU/webgpuPipelineContext';
 import { IPipelineContext } from './IPipelineContext';
 import { DataBuffer } from '../Meshes/dataBuffer';
 import { WebGPUDataBuffer } from '../Meshes/WebGPU/webgpuDataBuffer';
@@ -35,6 +35,7 @@ import { WebGPURenderPassWrapper } from './WebGPU/webgpuRenderPassWrapper';
 import { IMultiRenderTargetOptions } from '../Materials/Textures/multiRenderTarget';
 import { WebGPUCacheSampler } from "./WebGPU/webgpuCacheSampler";
 import { WebGPUShaderManager } from "./WebGPU/webgpuShaderManager";
+import { WebGPUCacheRenderPipeline } from "./WebGPU/webgpuCacheRenderPipeline";
 
 import "../Shaders/clearQuad.vertex";
 import "../Shaders/clearQuad.fragment";
@@ -173,19 +174,16 @@ export class WebGPUEngine extends Engine {
     private _bufferManager: WebGPUBufferManager;
     private _shaderManager: WebGPUShaderManager;
     private _cacheSampler: WebGPUCacheSampler;
+    private _cacheRenderPipeline: WebGPUCacheRenderPipeline;
     private _emptyVertexBuffer: VertexBuffer;
     private _lastCachedWrapU: number;
     private _lastCachedWrapV: number;
     private _lastCachedWrapR: number;
     private _mrtAttachments: number[];
     private _counters: {
-        numPipelineDescriptorCreation: number;
         numBindGroupsCreation: number;
-        numVertexInputCacheCreation: number;
     } = {
-        numPipelineDescriptorCreation: 0,
         numBindGroupsCreation: 0,
-        numVertexInputCacheCreation: 0,
     };
 
     // Some of the internal state might change during the render pass.
@@ -234,6 +232,32 @@ export class WebGPUEngine extends Engine {
     public dbgShowWarningsNotImplemented = false;
 
     /**
+     * Sets this to true to disable the cache for the samplers. You should do it only for testing purpose!
+     */
+    public get disableCacheSamplers(): boolean {
+        return this._cacheSampler ? this._cacheSampler.disabled : false;
+    }
+
+    public set disableCacheSamplers(disable: boolean) {
+        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!
+     */
+    public get disableCacheRenderPipelines(): boolean {
+        return this._cacheRenderPipeline ? this._cacheRenderPipeline.disabled : false;
+    }
+
+    public set disableCacheRenderPipelines(disable: boolean) {
+        if (this._cacheRenderPipeline) {
+            this._cacheRenderPipeline.disabled = disable;
+        }
+    }
+
+    /**
      * Gets a boolean indicating if the engine can be instanciated (ie. if a WebGPU context can be found)
      * @returns true if the engine can be created
      */
@@ -309,7 +333,7 @@ export class WebGPUEngine extends Engine {
         options.stencil = options.stencil ?? true;
         options.enableGPUDebugMarkers = options.enableGPUDebugMarkers ?? false;
 
-        Logger.Log(`Babylon.js v${Engine.Version} - WebGPU engine`);
+        Logger.Log(`Babylon.js v${Engine.Version} - ${this.description} engine`);
         if (!navigator.gpu) {
             Logger.Error("WebGPU is not supported by your browser.");
             return;
@@ -416,10 +440,15 @@ export class WebGPUEngine extends Engine {
                 this._renderEncoder = this._device.createCommandEncoder(this._renderEncoderDescriptor);
                 this._renderTargetEncoder = this._device.createCommandEncoder(this._renderTargetEncoderDescriptor);
 
-                this._textureHelper.setCommandEncoder(this._uploadEncoder);
-
                 this._emptyVertexBuffer = new VertexBuffer(this, [0], "", false, false, 1, false, 0, 1);
 
+                this._cacheRenderPipeline = new WebGPUCacheRenderPipeline(this._device, this._emptyVertexBuffer);
+
+                this._cacheRenderPipeline.setDepthCompare(this._depthCullingState.depthFunc);
+                //this._cacheRenderPipeline.disabled = true;
+
+                this._textureHelper.setCommandEncoder(this._uploadEncoder);
+
                 this._initializeLimits();
                 this._initializeContextAndSwapChain();
                 this._initializeMainAttachments();
@@ -428,6 +457,9 @@ export class WebGPUEngine extends Engine {
             .catch((e: any) => {
                 Logger.Error("Can not create WebGPU Device and/or context.");
                 Logger.Error(e);
+                if (console.trace) {
+                    console.trace();
+                }
             });
     }
 
@@ -511,7 +543,7 @@ export class WebGPUEngine extends Engine {
             supportRenderAndCopyToLodForFloatTextures: true,
             supportDepthStencilTexture: true,
             supportShadowSamplers: true,
-            uniformBufferHardCheckMatrix: false,
+            uniformBufferHardCheckMatrix: true,
             allowTexturePrefiltering: true,
             trackUbosInFrame: true,
             supportCSM: true,
@@ -672,20 +704,26 @@ export class WebGPUEngine extends Engine {
         this._forceEnableEffect = true;
         this._currentIndexBuffer = null;
         this._currentVertexBuffers = null;
+        this._cacheRenderPipeline.setBuffers(null, null);
 
         if (bruteForce) {
             this._currentProgram = null;
 
             this._stencilState.reset();
+            this._cacheRenderPipeline.resetStencilState();
 
             this._depthCullingState.reset();
             this._depthCullingState.depthFunc = Constants.LEQUAL;
+            this._cacheRenderPipeline.resetDepthCullingState();
+            this._cacheRenderPipeline.setDepthCompare(Constants.LEQUAL);
 
             this._alphaState.reset();
             this._alphaMode = Constants.ALPHA_ADD;
             this._alphaEquation = Constants.ALPHA_DISABLE;
+            this._cacheRenderPipeline.setAlphaBlendFactors(this._alphaState._blendFunctionParameters, this._alphaState._blendEquationParameters);
+            this._cacheRenderPipeline.setAlphaBlendEnabled(false);
 
-            this.__colorWrite = true;
+            this.setColorWrite(true);
         }
 
         this._cachedVertexBuffers = null;
@@ -699,6 +737,7 @@ export class WebGPUEngine extends Engine {
      */
     public setColorWrite(enable: boolean): void {
         this.__colorWrite = enable;
+        this._cacheRenderPipeline.setWriteMask(enable ? 0xF : 0);
     }
 
     /**
@@ -1029,6 +1068,7 @@ export class WebGPUEngine extends Engine {
     public bindBuffers(vertexBuffers: { [key: string]: Nullable<VertexBuffer> }, indexBuffer: Nullable<DataBuffer>, effect: Effect): void {
         this._currentIndexBuffer = indexBuffer;
         this._currentVertexBuffers = vertexBuffers;
+        this._cacheRenderPipeline.setBuffers(vertexBuffers, indexBuffer);
     }
 
     /** @hidden */
@@ -1185,9 +1225,7 @@ export class WebGPUEngine extends Engine {
      * @returns the new pipeline
      */
     public createPipelineContext(shaderProcessingContext: Nullable<ShaderProcessingContext>): IPipelineContext {
-        var pipelineContext = new WebGPUPipelineContext(shaderProcessingContext! as WebGPUShaderProcessingContext, this);
-        pipelineContext.engine = this;
-        return pipelineContext;
+        return new WebGPUPipelineContext(shaderProcessingContext! as WebGPUShaderProcessingContext, this);
     }
 
     /** @hidden */
@@ -1760,8 +1798,7 @@ export class WebGPUEngine extends Engine {
 
             if (webgpuPipelineContext.textures[name]) {
                 if (webgpuPipelineContext.textures[name]!.texture !== internalTexture) {
-                    // TODO WEBGPU when the bindGroups has a caching mechanism set up, clear this cache
-                    //webgpuPipelineContext.bindGroups = null as any; // the bind groups need to be rebuilt (at least the bind group owning this texture, but it's easier to just have them all rebuilt)
+                    webgpuPipelineContext.bindGroupsCache = {}; // the bind groups need to be rebuilt (at least the bind group owning this texture, but it's easier to just have them all rebuilt)
                 }
                 webgpuPipelineContext.textures[name]!.texture = internalTexture!;
             }
@@ -1815,8 +1852,7 @@ export class WebGPUEngine extends Engine {
             const webgpuPipelineContext = this._currentEffect._pipelineContext as WebGPUPipelineContext;
             if (!texture) {
                 if (webgpuPipelineContext.textures[name] && webgpuPipelineContext.textures[name]!.texture) {
-                    // TODO WEBGPU when the bindGroups has a caching mechanism set up, clear this cache
-                    //webgpuPipelineContext.bindGroups = null as any; // the bind groups need to be rebuilt (at least the bind group owning this texture, but it's easier to just have them all rebuilt)
+                    webgpuPipelineContext.bindGroupsCache = {}; // the bind groups need to be rebuilt (at least the bind group owning this texture, but it's easier to just have them all rebuilt)
                 }
                 webgpuPipelineContext.textures[name] = null;
                 return false;
@@ -2641,8 +2677,7 @@ export class WebGPUEngine extends Engine {
         if (this.dbgVerboseLogsForFirstFrames) {
             if ((this as any)._count === undefined) { (this as any)._count = 0; }
             if (!(this as any)._count || (this as any)._count < this.dbgVerboseLogsNumFrames) {
-                console.log("frame #" + (this as any)._count + " - counters - numPipelineDescriptorCreation=", this._counters.numPipelineDescriptorCreation, ", numBindGroupsCreation=", this._counters.numBindGroupsCreation,
-                    ", numVertexInputCacheCreation=", this._counters.numVertexInputCacheCreation);
+                console.log("frame #" + (this as any)._count + " - counters - numBindGroupsCreation=", this._counters.numBindGroupsCreation);
             }
         }
 
@@ -2663,9 +2698,8 @@ export class WebGPUEngine extends Engine {
             UniformBuffer._updatedUbosInFrame = {};
         }
 
-        this._counters.numPipelineDescriptorCreation = 0;
         this._counters.numBindGroupsCreation = 0;
-        this._counters.numVertexInputCacheCreation = 0;
+        this._cacheRenderPipeline.endFrame();
 
         this._pendingDebugCommands.length = 0;
 
@@ -2767,6 +2801,7 @@ export class WebGPUEngine extends Engine {
                 }
             }
             this._mrtAttachments = internalTexture._attachments;
+            this._cacheRenderPipeline.setMRTAttachments(this._mrtAttachments, internalTexture._textureArray);
         } else {
             // single render target
             const gpuMSAATexture = gpuWrapper.msaaTexture;
@@ -2838,10 +2873,6 @@ export class WebGPUEngine extends Engine {
         return this._currentRenderPass!;
     }
 
-    private _currentRenderPassIsMRT(): boolean {
-        return !!this._currentRenderTarget?._attachments ?? false;
-    }
-
     private _startMainRenderPass(setClearStates: boolean, clearColor?: Nullable<IColor4Like>, clearDepth?: boolean, clearStencil?: boolean): void {
         if (this._mainRenderPassWrapper.renderPass) {
             this._endMainRenderPass();
@@ -3115,6 +3146,7 @@ export class WebGPUEngine extends Engine {
         this._currentRenderTarget = null;
 
         this._mrtAttachments = [];
+        this._cacheRenderPipeline.setMRTAttachments(this._mrtAttachments, []);
         this._currentRenderPass = this._mainRenderPassWrapper.renderPass;
         this._setDepthTextureFormat(this._mainRenderPassWrapper);
         this._setColorFormat(this._mainRenderPassWrapper);
@@ -3147,6 +3179,8 @@ export class WebGPUEngine extends Engine {
 
         this._currentRenderTarget = null;
 
+        this._mrtAttachments = [];
+        this._cacheRenderPipeline.setMRTAttachments(this._mrtAttachments, []);
         this._currentRenderPass = this._mainRenderPassWrapper.renderPass;
         this._setDepthTextureFormat(this._mainRenderPassWrapper);
         this._setColorFormat(this._mainRenderPassWrapper);
@@ -3184,6 +3218,7 @@ export class WebGPUEngine extends Engine {
 
     private _setColorFormat(wrapper: WebGPURenderPassWrapper): void {
         const format = wrapper.colorAttachmentGPUTextures[0].format;
+        this._cacheRenderPipeline.setColorFormat(format);
         if (this._colorFormat === format) {
             return;
         }
@@ -3191,6 +3226,7 @@ export class WebGPUEngine extends Engine {
     }
 
     private _setDepthTextureFormat(wrapper: WebGPURenderPassWrapper): void {
+        this._cacheRenderPipeline.setDepthStencilFormat(wrapper.depthTextureFormat);
         if (this._depthTextureFormat === wrapper.depthTextureFormat) {
             return;
         }
@@ -3295,89 +3331,6 @@ export class WebGPUEngine extends Engine {
         }
     }
 
-    private _indexFormatInRenderPass(topology: GPUPrimitiveTopology): boolean {
-        return  topology === WebGPUConstants.PrimitiveTopology.PointList ||
-                topology === WebGPUConstants.PrimitiveTopology.LineList ||
-                topology === WebGPUConstants.PrimitiveTopology.TriangleList;
-    }
-
-    private _getTopology(fillMode: number): GPUPrimitiveTopology {
-        switch (fillMode) {
-            // Triangle views
-            case Constants.MATERIAL_TriangleFillMode:
-                return WebGPUConstants.PrimitiveTopology.TriangleList;
-            case Constants.MATERIAL_PointFillMode:
-                return WebGPUConstants.PrimitiveTopology.PointList;
-            case Constants.MATERIAL_WireFrameFillMode:
-                return WebGPUConstants.PrimitiveTopology.LineList;
-            // Draw modes
-            case Constants.MATERIAL_PointListDrawMode:
-                return WebGPUConstants.PrimitiveTopology.PointList;
-            case Constants.MATERIAL_LineListDrawMode:
-                return WebGPUConstants.PrimitiveTopology.LineList;
-            case Constants.MATERIAL_LineLoopDrawMode:
-                // return this._gl.LINE_LOOP;
-                // TODO WEBGPU. Line Loop Mode Fallback at buffer load time.
-                throw "LineLoop is an unsupported fillmode in WebGPU";
-            case Constants.MATERIAL_LineStripDrawMode:
-                return WebGPUConstants.PrimitiveTopology.LineStrip;
-            case Constants.MATERIAL_TriangleStripDrawMode:
-                return WebGPUConstants.PrimitiveTopology.TriangleStrip;
-            case Constants.MATERIAL_TriangleFanDrawMode:
-                // return this._gl.TRIANGLE_FAN;
-                // TODO WEBGPU. Triangle Fan Mode Fallback at buffer load time.
-                throw "TriangleFan is an unsupported fillmode in WebGPU";
-            default:
-                return WebGPUConstants.PrimitiveTopology.TriangleList;
-        }
-    }
-
-    private _getOpFunction(operation: Nullable<number>, defaultOp: GPUStencilOperation): GPUStencilOperation {
-        switch (operation) {
-            case Constants.KEEP:
-                return WebGPUConstants.StencilOperation.Keep;
-            case Constants.ZERO:
-                return WebGPUConstants.StencilOperation.Zero;
-            case Constants.REPLACE:
-                return WebGPUConstants.StencilOperation.Replace;
-            case Constants.INVERT:
-                return WebGPUConstants.StencilOperation.Invert;
-            case Constants.INCR:
-                return WebGPUConstants.StencilOperation.IncrementClamp;
-            case Constants.DECR:
-                return WebGPUConstants.StencilOperation.DecrementClamp;
-            case Constants.INCR_WRAP:
-                return WebGPUConstants.StencilOperation.IncrementWrap;
-            case Constants.DECR_WRAP:
-                return WebGPUConstants.StencilOperation.DecrementWrap;
-            default:
-                return defaultOp;
-        }
-    }
-
-    private _getDepthStencilStateDescriptor(): GPUDepthStencilStateDescriptor | undefined {
-        if (this._depthTextureFormat === undefined) {
-            return undefined;
-        }
-
-        const stencilFrontBack: GPUStencilStateFaceDescriptor = {
-            compare: WebGPUTextureHelper.GetCompareFunction(this._stencilState.stencilFunc),
-            depthFailOp: this._getOpFunction(this._stencilState.stencilOpDepthFail, WebGPUConstants.StencilOperation.Keep),
-            failOp: this._getOpFunction(this._stencilState.stencilOpStencilFail, WebGPUConstants.StencilOperation.Keep),
-            passOp: this._getOpFunction(this._stencilState.stencilOpStencilDepthPass, WebGPUConstants.StencilOperation.Replace)
-        };
-
-        return {
-            depthWriteEnabled: this.getDepthWrite(),
-            depthCompare: this.getDepthBuffer() ? WebGPUTextureHelper.GetCompareFunction(this.getDepthFunction()) : WebGPUConstants.CompareFunction.Always,
-            format: this._depthTextureFormat,
-            stencilFront: stencilFrontBack,
-            stencilBack: stencilFrontBack,
-            stencilReadMask: this._stencilState.stencilFuncMask,
-            stencilWriteMask: this._stencilState.stencilMask,
-        };
-    }
-
     /**
      * Set various states to the context
      * @param culling defines backface culling state
@@ -3409,45 +3362,6 @@ export class WebGPUEngine extends Engine {
         }
     }
 
-    private _getFrontFace(): GPUFrontFace {
-        switch (this._depthCullingState.frontFace) {
-            case 1:
-                return WebGPUConstants.FrontFace.CCW;
-            default:
-                return WebGPUConstants.FrontFace.CW;
-        }
-    }
-
-    private _getCullMode(): GPUCullMode {
-        if (this._depthCullingState.cull === false) {
-            return WebGPUConstants.CullMode.None;
-        }
-
-        if (this._depthCullingState.cullFace === 2) {
-            return WebGPUConstants.CullMode.Front;
-        }
-        else {
-            return WebGPUConstants.CullMode.Back;
-        }
-    }
-
-    private _getRasterizationStateDescriptor(): GPURasterizationStateDescriptor {
-        return {
-            frontFace: this._getFrontFace(),
-            cullMode: this._getCullMode(),
-            depthBias: 0,
-            depthBiasClamp: 0,
-            depthBiasSlopeScale: this._depthCullingState.zOffset,
-        };
-    }
-
-    private _getWriteMask(): number {
-        if (this.__colorWrite) {
-            return WebGPUConstants.ColorWrite.All;
-        }
-        return 0;
-    }
-
     /**
      * Sets the current alpha mode
      * @param mode defines the mode to use (one of the Engine.ALPHA_XXX)
@@ -3516,414 +3430,50 @@ export class WebGPUEngine extends Engine {
         }
         if (!noDepthWriteChange) {
             this.setDepthWrite(mode === Engine.ALPHA_DISABLE);
+            this._cacheRenderPipeline.setDepthWriteEnabled(mode === Engine.ALPHA_DISABLE);
         }
         this._alphaMode = mode;
+        this._cacheRenderPipeline.setAlphaBlendEnabled(this._alphaState.alphaBlend);
+        this._cacheRenderPipeline.setAlphaBlendFactors(this._alphaState._blendFunctionParameters, this._alphaState._blendEquationParameters);
     }
 
-    private _getAphaBlendOperation(operation: Nullable<number>): GPUBlendOperation {
-        switch (operation) {
-            case 0x8006:
-                return WebGPUConstants.BlendOperation.Add;
-            case 0x800A:
-                return WebGPUConstants.BlendOperation.Subtract;
-            case 0x800B:
-                return WebGPUConstants.BlendOperation.ReverseSubtract;
-            default:
-                return WebGPUConstants.BlendOperation.Add;
-        }
-    }
-
-    private _getAphaBlendFactor(factor: Nullable<number>): GPUBlendFactor {
-        switch (factor) {
-            case 0:
-                return WebGPUConstants.BlendFactor.Zero;
-            case 1:
-                return WebGPUConstants.BlendFactor.One;
-            case 0x0300:
-                return WebGPUConstants.BlendFactor.SrcColor;
-            case 0x0301:
-                return WebGPUConstants.BlendFactor.OneMinusSrcColor;
-            case 0x0302:
-                return WebGPUConstants.BlendFactor.SrcAlpha;
-            case 0x0303:
-                return WebGPUConstants.BlendFactor.OneMinusSrcAlpha;
-            case 0x0304:
-                return WebGPUConstants.BlendFactor.DstAlpha;
-            case 0x0305:
-                return WebGPUConstants.BlendFactor.OneMinusDstAlpha;
-            case 0x0306:
-                return WebGPUConstants.BlendFactor.DstColor;
-            case 0x0307:
-                return WebGPUConstants.BlendFactor.OneMinusDstColor;
-            case 0x0308:
-                return WebGPUConstants.BlendFactor.SrcAlphaSaturated;
-            case 0x8001:
-                return WebGPUConstants.BlendFactor.BlendColor;
-            case 0x8002:
-                return WebGPUConstants.BlendFactor.OneMinusBlendColor;
-            case 0x8003:
-                return WebGPUConstants.BlendFactor.BlendColor;
-            case 0x8004:
-                return WebGPUConstants.BlendFactor.OneMinusBlendColor;
-            default:
-                return WebGPUConstants.BlendFactor.One;
-        }
-    }
-
-    private _getAphaBlendState(): GPUBlendDescriptor {
-        if (!this._alphaState.alphaBlend) {
-            return { };
-        }
-
-        return {
-            srcFactor: this._getAphaBlendFactor(this._alphaState._blendFunctionParameters[2]),
-            dstFactor: this._getAphaBlendFactor(this._alphaState._blendFunctionParameters[3]),
-            operation: this._getAphaBlendOperation(this._alphaState._blendEquationParameters[1]),
-        };
-    }
-
-    private _getColorBlendState(): GPUBlendDescriptor {
-        if (!this._alphaState.alphaBlend) {
-            return { };
-        }
-
-        return {
-            srcFactor: this._getAphaBlendFactor(this._alphaState._blendFunctionParameters[0]),
-            dstFactor: this._getAphaBlendFactor(this._alphaState._blendFunctionParameters[1]),
-            operation: this._getAphaBlendOperation(this._alphaState._blendEquationParameters[0]),
-        };
-    }
-
-    private _getColorStateDescriptors(): GPUColorStateDescriptor[] {
-        const descriptors: GPUColorStateDescriptor[] = [];
-
-        const alphaBlend = this._getAphaBlendState();
-        const colorBlend = this._getColorBlendState();
-        const writeMask = this._getWriteMask();
-
-        if (this._currentRenderPassIsMRT()) {
-            const textureArray = this._currentRenderTarget!._textureArray!;
-            for (let i = 0; i < this._mrtAttachments.length; ++i) {
-                const index = this._mrtAttachments[i];
-                if (index > 0) {
-                    const mrtTexture = textureArray[index - 1];
-                    const gpuMRTWrapper = mrtTexture?._hardwareTexture as Nullable<WebGPUHardwareTexture>;
-                    descriptors.push({
-                        format: gpuMRTWrapper?.format ?? this._colorFormat,
-                        alphaBlend,
-                        colorBlend,
-                        writeMask,
-                    });
-                } else {
-                    descriptors.push(undefined as any); // TODO WEBGPU what to do when this._mrtAttachments[i] === 0? The corresponding texture should be bound as an "empty" texture
-                }
-            }
-        } else {
-            descriptors.push({
-                format: this._colorFormat,
-                alphaBlend,
-                colorBlend,
-                writeMask,
-            });
-        }
+    /**
+     * Sets the current alpha equation
+     * @param equation defines the equation to use (one of the Engine.ALPHA_EQUATION_XXX)
+     */
+    public setAlphaEquation(equation: number): void {
+        super.setAlphaEquation(equation);
 
-        return descriptors;
+        this._cacheRenderPipeline.setAlphaBlendFactors(this._alphaState._blendFunctionParameters, this._alphaState._blendEquationParameters);
     }
 
-    private _getStages(): IWebGPURenderPipelineStageDescriptor {
+    private _getBindGroupsToRender(): GPUBindGroup[] {
         const webgpuPipelineContext = this._currentEffect!._pipelineContext as WebGPUPipelineContext;
-        return webgpuPipelineContext.stages!;
-    }
-
-    private _getVertexInputDescriptorFormat(vertexBuffer: VertexBuffer): GPUVertexFormat {
-        const kind = vertexBuffer.getKind();
-        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 ? WebGPUConstants.VertexFormat.Char2Norm : WebGPUConstants.VertexFormat.Char2;
-                    case 3:
-                    case 4:
-                        return normalized ? WebGPUConstants.VertexFormat.Char4Norm : WebGPUConstants.VertexFormat.Char4;
-                }
-                break;
-            case VertexBuffer.UNSIGNED_BYTE:
-                switch (size) {
-                    case 1:
-                    case 2:
-                        return normalized ? WebGPUConstants.VertexFormat.Uchar2Norm : WebGPUConstants.VertexFormat.Uchar2;
-                    case 3:
-                    case 4:
-                        return normalized ? WebGPUConstants.VertexFormat.Uchar4Norm : WebGPUConstants.VertexFormat.Uchar4;
-                }
-                break;
-            case VertexBuffer.SHORT:
-                switch (size) {
-                    case 1:
-                    case 2:
-                        return normalized ? WebGPUConstants.VertexFormat.Short2Norm : WebGPUConstants.VertexFormat.Short2;
-                    case 3:
-                    case 4:
-                        return normalized ? WebGPUConstants.VertexFormat.Short4Norm : WebGPUConstants.VertexFormat.Short4;
-                }
-                break;
-            case VertexBuffer.UNSIGNED_SHORT:
-                switch (size) {
-                    case 1:
-                    case 2:
-                        return normalized ? WebGPUConstants.VertexFormat.Ushort2Norm : WebGPUConstants.VertexFormat.Ushort2;
-                    case 3:
-                    case 4:
-                        return normalized ? WebGPUConstants.VertexFormat.Ushort4Norm : WebGPUConstants.VertexFormat.Ushort4;
-                }
-                break;
-            case VertexBuffer.INT:
-                switch (size) {
-                    case 1:
-                        return WebGPUConstants.VertexFormat.Int;
-                    case 2:
-                        return WebGPUConstants.VertexFormat.Int2;
-                    case 3:
-                        return WebGPUConstants.VertexFormat.Int3;
-                    case 4:
-                        return WebGPUConstants.VertexFormat.Int4;
-                }
-                break;
-            case VertexBuffer.UNSIGNED_INT:
-                switch (size) {
-                    case 1:
-                        return WebGPUConstants.VertexFormat.Uint;
-                    case 2:
-                        return WebGPUConstants.VertexFormat.Uint2;
-                    case 3:
-                        return WebGPUConstants.VertexFormat.Uint3;
-                    case 4:
-                        return WebGPUConstants.VertexFormat.Uint4;
-                }
-                break;
-            case VertexBuffer.FLOAT:
-                switch (size) {
-                    case 1:
-                        return WebGPUConstants.VertexFormat.Float;
-                    case 2:
-                        return WebGPUConstants.VertexFormat.Float2;
-                    case 3:
-                        return WebGPUConstants.VertexFormat.Float3;
-                    case 4:
-                        return WebGPUConstants.VertexFormat.Float4;
-                }
-                break;
-        }
-
-        throw new Error(`Invalid Format '${kind}' - type=${type}, normalized=${normalized}, size=${size}`);
-    }
-
-    private _getVertexInputDescriptor(topology: GPUPrimitiveTopology): GPUVertexStateDescriptor {
-        const descriptors: GPUVertexBufferLayoutDescriptor[] = [];
-        const effect = this._currentEffect!;
-        const attributes = effect.getAttributesNames();
-        for (var index = 0; index < attributes.length; index++) {
-            const location = effect.getAttributeLocation(index);
-
-            if (location >= 0) {
-                let  vertexBuffer = this._currentVertexBuffers![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;
-                }
-
-                const positionAttributeDescriptor: GPUVertexAttributeDescriptor = {
-                    shaderLocation: location,
-                    offset: 0, // not available in WebGL
-                    format: this._getVertexInputDescriptorFormat(vertexBuffer),
-                };
-
-                // TODO WEBGPU. Factorize the one with the same underlying buffer.
-                const vertexBufferDescriptor: GPUVertexBufferLayoutDescriptor = {
-                    arrayStride: vertexBuffer.byteStride,
-                    stepMode: vertexBuffer.getIsInstanced() ? WebGPUConstants.InputStepMode.Instance : WebGPUConstants.InputStepMode.Vertex,
-                    attributes: [positionAttributeDescriptor]
-                };
-
-               descriptors.push(vertexBufferDescriptor);
-            }
-        }
 
-        if (!this._currentIndexBuffer) {
-            return {
-                indexFormat: !this._indexFormatInRenderPass(topology) ? WebGPUConstants.IndexFormat.Uint32 : undefined,
-                vertexBuffers: descriptors
-            };
-        }
-
-        const inputStateDescriptor: GPUVertexStateDescriptor = {
-            vertexBuffers: descriptors
-        };
-
-        if (!this._indexFormatInRenderPass(topology)) {
-            inputStateDescriptor.indexFormat = this._currentIndexBuffer!.is32Bits ? WebGPUConstants.IndexFormat.Uint32 : WebGPUConstants.IndexFormat.Uint16;
+        if (webgpuPipelineContext.uniformBuffer) {
+            this.bindUniformBufferBase(webgpuPipelineContext.uniformBuffer.getBuffer()!, 0, "LeftOver");
+            webgpuPipelineContext.uniformBuffer.update();
         }
 
-        return inputStateDescriptor;
-    }
-
-    private _getPipelineLayout(): GPUPipelineLayout {
-        const bindGroupLayouts: GPUBindGroupLayout[] = [];
-        const webgpuPipelineContext = this._currentEffect!._pipelineContext as WebGPUPipelineContext;
-
-        for (let i = 0; i < webgpuPipelineContext.shaderProcessingContext.orderedUBOsAndSamplers.length; i++) {
-            const setDefinition = webgpuPipelineContext.shaderProcessingContext.orderedUBOsAndSamplers[i];
-            if (setDefinition === undefined) {
-                const entries: GPUBindGroupLayoutEntry[] = [];
-                const uniformsBindGroupLayout = this._device.createBindGroupLayout({
-                    entries,
-                });
-                bindGroupLayouts[i] = uniformsBindGroupLayout;
-                continue;
-            }
-
-            const entries: GPUBindGroupLayoutEntry[] = [];
-            for (let j = 0; j < setDefinition.length; j++) {
-                const bindingDefinition = webgpuPipelineContext.shaderProcessingContext.orderedUBOsAndSamplers[i][j];
-                if (bindingDefinition === undefined) {
-                    continue;
-                }
-
-                let visibility = 0;
-                if (bindingDefinition.usedInVertex) {
-                    visibility = visibility | WebGPUConstants.ShaderStage.Vertex;
-                }
-                if (bindingDefinition.usedInFragment) {
-                    visibility = visibility | WebGPUConstants.ShaderStage.Fragment;
-                }
-
-                if (bindingDefinition.isSampler) {
-                    entries.push({
-                        binding: j,
-                        visibility,
-                        type: bindingDefinition.isComparisonSampler ? WebGPUConstants.BindingType.ComparisonSampler : WebGPUConstants.BindingType.Sampler
-                    });
-                } else if (bindingDefinition.isTexture) {
-                    entries.push({
-                        binding: j,
-                        visibility,
-                        type: WebGPUConstants.BindingType.SampledTexture,
-                        viewDimension: bindingDefinition.textureDimension,
-                        textureComponentType: bindingDefinition.componentType,
-                        // TODO WEBGPU.
-                        // hasDynamicOffset?: boolean;
-                        // storageTextureFormat?: GPUTextureFormat;
-                        // minBufferBindingSize?: number;
-                    });
-                } else {
-                    entries.push({
-                        binding: j,
-                        visibility,
-                        type: WebGPUConstants.BindingType.UniformBuffer,
-                    });
-                }
-            }
-
-            if (entries.length > 0) {
-                const uniformsBindGroupLayout = this._device.createBindGroupLayout({
-                    entries,
-                });
-                bindGroupLayouts[i] = uniformsBindGroupLayout;
+        let bufferKey = "";
+        for (let i = 0; i < webgpuPipelineContext.shaderProcessingContext.uniformBufferNames.length; ++i) {
+            const bufferName = webgpuPipelineContext.shaderProcessingContext.uniformBufferNames[i];
+            const dataBuffer = this._uniformsBuffers[bufferName];
+            if (dataBuffer) {
+                bufferKey += dataBuffer.uniqueId + "_";
             }
         }
 
-        webgpuPipelineContext.bindGroupLayouts = bindGroupLayouts;
-        return this._device.createPipelineLayout({ bindGroupLayouts });
-    }
-
-    private _getRenderPipeline(topology: GPUPrimitiveTopology, createLayout = true): GPURenderPipeline {
-        this._counters.numPipelineDescriptorCreation++;
-
-        // Unsupported at the moment but needs to be extracted from the MSAA param.
-        const rasterizationStateDescriptor = this._getRasterizationStateDescriptor();
-        const depthStateDescriptor = this._getDepthStencilStateDescriptor();
-        const colorStateDescriptors = this._getColorStateDescriptors();
-        const stages = this._getStages();
-        const inputStateDescriptor = this._getVertexInputDescriptor(topology);
-        const pipelineLayout = createLayout ? this._getPipelineLayout() : undefined;
-
-        return this._device.createRenderPipeline({
-            sampleCount: this._currentRenderTarget ? this._currentRenderTarget.samples : this._mainPassSampleCount,
-            primitiveTopology: topology,
-            rasterizationState: rasterizationStateDescriptor,
-            depthStencilState: depthStateDescriptor,
-            colorStates: colorStateDescriptors,
-
-            ...stages,
-            vertexState: inputStateDescriptor,
-            layout: pipelineLayout,
-        });
-    }
-
-    private _getVertexInputsToRender(): IWebGPUPipelineContextVertexInputsCache {
-        const effect = this._currentEffect!;
-
-        this._counters.numVertexInputCacheCreation++;
-
-        let vertexInputs: IWebGPUPipelineContextVertexInputsCache = {
-            indexBuffer: null,
-            indexOffset: 0,
-
-            vertexStartSlot: 0,
-            vertexBuffers: [],
-            vertexOffsets: [],
-        };
-
-        if (this._currentIndexBuffer) {
-            // TODO WEBGPU. Check if cache would be worth it.
-            vertexInputs.indexBuffer = this._currentIndexBuffer.underlyingResource;
-            vertexInputs.indexOffset = 0;
-        }
-        else {
-            vertexInputs.indexBuffer = null;
-        }
-
-        const attributes = effect.getAttributesNames();
-        for (var index = 0; index < attributes.length; index++) {
-            const order = effect.getAttributeLocation(index);
-
-            if (order >= 0) {
-                let vertexBuffer = this._currentVertexBuffers![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;
-                }
-
-                var buffer = vertexBuffer.getBuffer();
-                if (buffer) {
-                    vertexInputs.vertexBuffers.push(buffer.underlyingResource);
-                    vertexInputs.vertexOffsets.push(vertexBuffer.byteOffset);
-                }
-            }
+        let bindGroups: GPUBindGroup[] = webgpuPipelineContext.bindGroupsCache[bufferKey];
+        if (bindGroups) {
+            return bindGroups;
         }
 
-        // TODO WEBGPU. Optimize buffer reusability and types as more are now allowed.
-        return vertexInputs;
-    }
-
-    private _getBindGroupsToRender(): GPUBindGroup[] {
-        const webgpuPipelineContext = this._currentEffect!._pipelineContext as WebGPUPipelineContext;
-        let bindGroups: GPUBindGroup[] = [];
+        bindGroups = [];
 
+        webgpuPipelineContext.bindGroupsCache[bufferKey] = bindGroups;
         this._counters.numBindGroupsCreation++;
 
-        if (webgpuPipelineContext.uniformBuffer) {
-            this.bindUniformBufferBase(webgpuPipelineContext.uniformBuffer.getBuffer()!, 0, "LeftOver");
-            webgpuPipelineContext.uniformBuffer.update();
-        }
-
         const bindGroupLayouts = webgpuPipelineContext.bindGroupLayouts;
 
         for (let i = 0; i < webgpuPipelineContext.shaderProcessingContext.orderedUBOsAndSamplers.length; i++) {
@@ -4010,19 +3560,31 @@ export class WebGPUEngine extends Engine {
         return bindGroups;
     }
 
-    private _bindVertexInputs(vertexInputs: IWebGPUPipelineContextVertexInputsCache): void {
+    private _bindVertexInputs(): void {
         const renderPass = this._bundleEncoder || this._getCurrentRenderPass();
 
-        if (vertexInputs.indexBuffer) {
-            // TODO WEBGPU. Check if cache would be worth it.
-            renderPass.setIndexBuffer(vertexInputs.indexBuffer, this._currentIndexBuffer!.is32Bits ? WebGPUConstants.IndexFormat.Uint32 : WebGPUConstants.IndexFormat.Uint16, vertexInputs.indexOffset);
+        if (this._currentIndexBuffer) {
+            renderPass.setIndexBuffer(this._currentIndexBuffer.underlyingResource, this._currentIndexBuffer!.is32Bits ? WebGPUConstants.IndexFormat.Uint32 : WebGPUConstants.IndexFormat.Uint16, 0);
         }
 
-        // TODO WEBGPU. Optimize buffer reusability and types as more are now allowed.
-        for (let i = 0; i < vertexInputs.vertexBuffers.length; i++) {
-            const buf = vertexInputs.vertexBuffers[i];
-            if (buf) {
-                renderPass.setVertexBuffer(vertexInputs.vertexStartSlot + i, buf, vertexInputs.vertexOffsets[i]);
+        const effect = this._currentEffect!;
+        const attributes = effect.getAttributesNames();
+        let bufferIdx = 0;
+        for (var index = 0; index < attributes.length; index++) {
+            const order = effect.getAttributeLocation(index);
+
+            if (order >= 0) {
+                let vertexBuffer = this._currentVertexBuffers![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;
+                }
+
+                const buffer = vertexBuffer.getBuffer();
+                if (buffer) {
+                    renderPass.setVertexBuffer(bufferIdx++, buffer.underlyingResource, vertexBuffer.byteOffset);
+                }
             }
         }
     }
@@ -4038,13 +3600,30 @@ export class WebGPUEngine extends Engine {
     private _setRenderPipeline(fillMode: number): void {
         const renderPass = this._bundleEncoder || this._getCurrentRenderPass();
 
-        const topology = this._getTopology(fillMode);
+        this._cacheRenderPipeline.setDepthCullingState(
+            !!this._depthCullingState.cull,
+            this._depthCullingState.frontFace ?? 2,
+            this._depthCullingState.cullFace ?? 1,
+            this._depthCullingState.zOffset,
+            this._depthCullingState.depthTest,
+            this._depthCullingState.depthMask,
+            this._depthCullingState.depthFunc
+        );
+
+        this._cacheRenderPipeline.setStencilState(
+            this._stencilState.stencilTest,
+            this._stencilState.stencilFunc,
+            this._stencilState.stencilOpDepthFail,
+            this._stencilState.stencilOpStencilDepthPass,
+            this._stencilState.stencilOpStencilFail,
+            this._stencilState.stencilFuncMask,
+            this._stencilState.stencilMask
+        );
 
-        const pipeline = this._getRenderPipeline(topology);
+        const pipeline = this._cacheRenderPipeline.getRenderPipeline(fillMode, this._currentEffect!, this._currentRenderTarget ? this._currentRenderTarget.samples : this._mainPassSampleCount);
         renderPass.setPipeline(pipeline);
 
-        const vertexInputs = this._getVertexInputsToRender();
-        this._bindVertexInputs(vertexInputs);
+        this._bindVertexInputs();
 
         const bindGroups = this._getBindGroupsToRender();
         this._setRenderBindGroups(bindGroups);

+ 9 - 0
src/Meshes/buffer.ts

@@ -200,6 +200,9 @@ export class Buffer {
      * Specialized buffer used to store vertex data
      */
 export class VertexBuffer {
+
+    private static _Counter = 0;
+
     /** @hidden */
     public _buffer: Buffer;
     private _kind: string;
@@ -280,6 +283,11 @@ export class VertexBuffer {
     public readonly type: number;
 
     /**
+     * Gets the unique id of this vertex buffer
+     */
+    public readonly uniqueId: number;
+
+    /**
      * Constructor
      * @param engine the engine
      * @param data the data to use for this vertex buffer
@@ -310,6 +318,7 @@ export class VertexBuffer {
             this._ownsBuffer = true;
         }
 
+        this.uniqueId = VertexBuffer._Counter++;
         this._kind = kind;
 
         if (type == undefined) {

+ 14 - 0
src/Meshes/dataBuffer.ts

@@ -2,6 +2,8 @@
  * Class used to store gfx data (like WebGLBuffer)
  */
 export class DataBuffer {
+    private static _Counter = 0;
+
     /**
      * Gets or sets the number of objects referencing this buffer
      */
@@ -19,4 +21,16 @@ export class DataBuffer {
     public get underlyingResource(): any {
         return null;
     }
+
+    /**
+     * Gets the unique id of this buffer
+     */
+    public readonly uniqueId: number;
+
+    /**
+     * Constructs the buffer
+     */
+    constructor() {
+        this.uniqueId = DataBuffer._Counter++;
+    }
 }

BIN=BIN
tests/validation/ReferenceImages/webgpu/particle system matrix like.png


+ 4 - 2
tests/validation/validation.js

@@ -462,7 +462,7 @@ function init(_engineName) {
             wasmPath: "../../dist/preview%20release/glslang/glslang.wasm"
         };
 
-        engine = new BABYLON.WebGPUEngine(canvas, {
+        const options = {
             deviceDescriptor: {
                 extensions: [
                     "texture-compression-bc",
@@ -474,7 +474,9 @@ function init(_engineName) {
                 ]
             },
             antialiasing: false,
-        });
+        };
+
+        engine = new BABYLON.WebGPUEngine(canvas, options);
         engine.enableOfflineSupport = false;
         return new Promise((resolve) => {
             engine.initAsync(glslangOptions).then(() => resolve());

+ 5 - 0
tests/validation/webgpu.json

@@ -134,6 +134,11 @@
             "title": "create environment texture",
             "playgroundId": "#L4RXKN#4",
             "excludedEngines": ["webgl1"]
+        },
+        {
+            "title": "particle system matrix like",
+            "renderCount": 100,
+            "playgroundId": "#WL44T7"
         }
     ]
 }