Parcourir la source

Improve perf of the render pipeline cache

Popov72 il y a 4 ans
Parent
commit
27983effa7

+ 14 - 0
.vscode/launch.json

@@ -107,6 +107,20 @@
                 "--enable-unsafe-es3-apis"
             ]
         },
+        {
+            "name": "Launch playground (Chrome Canary)",
+            "type": "chrome",
+            "request": "launch",
+            "url": "http://localhost:1338/Playground/index-local.html",
+            "webRoot": "${workspaceRoot}/",
+            "sourceMaps": true,
+            "preLaunchTask": "run",
+            "userDataDir": "${workspaceRoot}/.tempChromeCanaryProfileForDebug",
+            "runtimeExecutable": "C:/Users/alexis/AppData/Local/Google/Chrome SxS/Application/Chrome.exe",
+            "runtimeArgs": [
+                "--enable-unsafe-es3-apis"
+            ]
+        },
         { 
             "name": "Launch playground (Edge)",
             "type": "edge",

+ 40 - 49
src/Engines/WebGPU/webgpuCacheRenderPipeline.ts

@@ -9,17 +9,17 @@ 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
+    //DepthBias = 0, // not used, so remove it to improve perf
+    //DepthBiasClamp = 1, // not used, so remove it to improve perf
+    StencilReadMask = 0,
+    StencilWriteMask = 1,
     DepthBiasSlopeScale = 2,
     MRTAttachments1 = 3,
     MRTAttachments2 = 4,
-    ColorStates = 5,
-    DepthStencilState = 6,
-    StencilReadMask = 7,
-    StencilWriteMask = 8,
+    DepthStencilState = 5,
+    RasterizationState = 6,
+    ColorStates = 7,
+    ShaderStage = 8,
     VertexState = 9, // vertex state will consume positions 9, 10, ... depending on the number of vertex inputs
 
     NumStates = 10
@@ -116,7 +116,7 @@ const stencilOpToIndex: { [name: number]: number } = {
 };
 
 /** @hidden */
-export class WebGPUCacheRenderPipeline {
+export abstract class WebGPUCacheRenderPipeline {
 
     public static NumCacheHitWithoutHash = 0;
     public static NumCacheHitWithHash = 0;
@@ -125,14 +125,14 @@ export class WebGPUCacheRenderPipeline {
 
     public disabled: boolean;
 
-    private static _Cache: { [hash: string]: GPURenderPipeline } = {};
     private static _NumPipelineCreationCurrentFrame = 0;
 
+    protected _states: number[];
+
     private _device: GPUDevice;
-    private _states: string[];
     private _isDirty: boolean;
-    private _currentRenderPipeline: GPURenderPipeline;
     private _emptyVertexBuffer: VertexBuffer;
+    private _parameter: { token: any, pipeline: Nullable<GPURenderPipeline> };
 
     private _shaderId: number;
     private _alphaToCoverageEnabled: boolean;
@@ -176,6 +176,7 @@ export class WebGPUCacheRenderPipeline {
         this._states.length = StatePosition.NumStates;
         this._emptyVertexBuffer = emptyVertexBuffer;
         this._mrtFormats = [];
+        this._parameter = { token: undefined, pipeline: null };
         this.disabled = false;
         this.reset();
     }
@@ -198,16 +199,19 @@ export class WebGPUCacheRenderPipeline {
         this.setBuffers(null, null);
     }
 
+    protected abstract _getRenderPipeline(param: { token: any, pipeline: Nullable<GPURenderPipeline> }): void;
+    protected abstract _setRenderPipeline(param: { token: any, pipeline: Nullable<GPURenderPipeline> }): void;
+
     public getRenderPipeline(fillMode: number, effect: Effect, sampleCount: number): GPURenderPipeline {
         if (this.disabled) {
             const topology = WebGPUCacheRenderPipeline._GetTopology(fillMode);
 
-            this._currentRenderPipeline = this._createRenderPipeline(effect, topology, sampleCount);
+            this._parameter.pipeline = this._createRenderPipeline(effect, topology, sampleCount);
 
             WebGPUCacheRenderPipeline.NumCacheMiss++;
             WebGPUCacheRenderPipeline._NumPipelineCreationCurrentFrame++;
 
-            return this._currentRenderPipeline;
+            return this._parameter.pipeline;
         }
 
         this._setShaderStage(effect.uniqueId);
@@ -216,31 +220,29 @@ export class WebGPUCacheRenderPipeline {
         this._setDepthStencilState();
         this._setVertexState(effect);
 
-        if (!this._isDirty && this._currentRenderPipeline) {
+        if (!this._isDirty && this._parameter.pipeline) {
             WebGPUCacheRenderPipeline.NumCacheHitWithoutHash++;
-            return this._currentRenderPipeline;
+            return this._parameter.pipeline;
         }
 
         this._isDirty = false;
 
-        let hash = this._states.join();
-        let pipeline = WebGPUCacheRenderPipeline._Cache[hash];
+        this._getRenderPipeline(this._parameter);
 
-        if (pipeline) {
-            this._currentRenderPipeline = pipeline;
+        if (this._parameter.pipeline) {
             WebGPUCacheRenderPipeline.NumCacheHitWithHash++;
-            return pipeline;
+            return this._parameter.pipeline;
         }
 
         const topology = WebGPUCacheRenderPipeline._GetTopology(fillMode);
 
-        this._currentRenderPipeline = this._createRenderPipeline(effect, topology, sampleCount);
-        WebGPUCacheRenderPipeline._Cache[hash] = this._currentRenderPipeline;
+        this._parameter.pipeline = this._createRenderPipeline(effect, topology, sampleCount);
+        this._setRenderPipeline(this._parameter);
 
         WebGPUCacheRenderPipeline.NumCacheMiss++;
         WebGPUCacheRenderPipeline._NumPipelineCreationCurrentFrame++;
 
-        return this._currentRenderPipeline;
+        return this._parameter.pipeline;
     }
 
     public endFrame(): void {
@@ -301,7 +303,7 @@ export class WebGPUCacheRenderPipeline {
     public setDepthBiasSlopeScale(depthBiasSlopeScale: number): void {
         if (this._depthBiasSlopeScale !== depthBiasSlopeScale) {
             this._depthBiasSlopeScale = depthBiasSlopeScale;
-            this._states[StatePosition.DepthBiasSlopeScale] = depthBiasSlopeScale.toString();
+            this._states[StatePosition.DepthBiasSlopeScale] = depthBiasSlopeScale;
             this._isDirty = true;
         }
     }
@@ -339,8 +341,8 @@ export class WebGPUCacheRenderPipeline {
         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._states[StatePosition.MRTAttachments1] = bits[0];
+            this._states[StatePosition.MRTAttachments2] = bits[1];
             this._isDirty = true;
         }
     }
@@ -398,7 +400,7 @@ export class WebGPUCacheRenderPipeline {
     public setStencilReadMask(mask: number): void {
         if (this._stencilReadMask !== mask) {
             this._stencilReadMask = mask;
-            this._states[StatePosition.StencilReadMask] = mask.toString();
+            this._states[StatePosition.StencilReadMask] = mask;
             this._isDirty = true;
         }
     }
@@ -406,7 +408,7 @@ export class WebGPUCacheRenderPipeline {
     public setStencilWriteMask(mask: number): void {
         if (this._stencilWriteMask !== mask) {
             this._stencilWriteMask = mask;
-            this._states[StatePosition.StencilWriteMask] = mask.toString();
+            this._states[StatePosition.StencilWriteMask] = mask;
             this._isDirty = true;
         }
     }
@@ -673,7 +675,7 @@ export class WebGPUCacheRenderPipeline {
     private _setShaderStage(id: number): void {
         if (this._shaderId !== id) {
             this._shaderId = id;
-            this._states[StatePosition.ShaderStage] = id.toString();
+            this._states[StatePosition.ShaderStage] = id;
             this._isDirty = true;
         }
     }
@@ -693,13 +695,15 @@ export class WebGPUCacheRenderPipeline {
 
         if (this._rasterizationState !== rasterizationState) {
             this._rasterizationState = rasterizationState;
-            this._states[StatePosition.RasterizationState] = this._rasterizationState.toString();
+            this._states[StatePosition.RasterizationState] = this._rasterizationState;
             this._isDirty = true;
         }
     }
 
     private _setColorStates(): void {
-        let colorStates = ((this._writeMask ? 1 : 0) << 22) + (this._colorFormat << 23);
+        let colorStates =
+            ((this._writeMask ? 1 : 0) << 22) + (this._colorFormat << 23) +
+            ((this._depthWriteEnabled ? 1 : 0) << 29); // this 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)
 
         if (this._alphaBlendEnabled) {
             colorStates +=
@@ -713,7 +717,7 @@ export class WebGPUCacheRenderPipeline {
 
         if (colorStates !== this._colorStates) {
             this._colorStates = colorStates;
-            this._states[StatePosition.ColorStates] = this._colorStates.toString();
+            this._states[StatePosition.ColorStates] = this._colorStates;
             this._isDirty = true;
         }
     }
@@ -725,13 +729,12 @@ export class WebGPUCacheRenderPipeline {
 
         const depthStencilState =
                 this._depthStencilFormat +
-                ((this._depthWriteEnabled ? 1 : 0) << 6) +
-                ((this._depthTestEnabled ? this._depthCompare : 7 /* ALWAYS */) << 7) +
+                ((this._depthTestEnabled ? this._depthCompare : 7 /* ALWAYS */) << 6) +
                 (stencilState << 10); // stencil front - stencil back is the same
 
         if (this._depthStencilState !== depthStencilState) {
             this._depthStencilState = depthStencilState;
-            this._states[StatePosition.DepthStencilState] = this._depthStencilState.toString();
+            this._states[StatePosition.DepthStencilState] = this._depthStencilState;
             this._isDirty = true;
         }
     }
@@ -752,19 +755,7 @@ export class WebGPUCacheRenderPipeline {
                 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();
+            const vid = vertexBuffer.hashCode + (location << 7);
 
             this._isDirty = this._isDirty || this._states[newNumStates] !== vid;
             this._states[newNumStates++] = vid;

+ 18 - 0
src/Engines/WebGPU/webgpuCacheRenderPipelineString.ts

@@ -0,0 +1,18 @@
+import { Nullable } from "../../types";
+import { WebGPUCacheRenderPipeline } from "./webgpuCacheRenderPipeline";
+
+/** @hidden */
+export class WebGPUCacheRenderPipelineString extends WebGPUCacheRenderPipeline {
+
+    private static _Cache: { [hash: string]: GPURenderPipeline } = {};
+
+    protected _getRenderPipeline(param: { token: any, pipeline: Nullable<GPURenderPipeline> }): void {
+        let hash = this._states.join();
+        param.token = hash;
+        param.pipeline = WebGPUCacheRenderPipelineString._Cache[hash];
+    }
+
+    protected _setRenderPipeline(param: { token: any, pipeline: Nullable<GPURenderPipeline> }): void {
+        WebGPUCacheRenderPipelineString._Cache[param.token] = param.pipeline!;
+    }
+}

+ 105 - 0
src/Engines/WebGPU/webgpuCacheRenderPipelineTree.ts

@@ -0,0 +1,105 @@
+import { VertexBuffer } from "../../Meshes/buffer";
+import { Nullable } from "../../types";
+import { WebGPUCacheRenderPipeline } from "./webgpuCacheRenderPipeline";
+
+class NodeState {
+    public values: { [name: number]: NodeState };
+    public pipeline: GPURenderPipeline;
+
+    constructor() {
+        this.values = {};
+    }
+
+    public count(): [number, number] {
+        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];
+    }
+}
+
+/** @hidden */
+export class WebGPUCacheRenderPipelineTree extends WebGPUCacheRenderPipeline {
+
+    private static _Cache: NodeState;
+
+    public static GetNodeCounts(): { nodeCount: number, pipelineCount: number } {
+        const counts = WebGPUCacheRenderPipelineTree._Cache.count();
+
+        return { nodeCount: counts[0], pipelineCount: counts[1] };
+    }
+
+    constructor(device: GPUDevice, emptyVertexBuffer: VertexBuffer) {
+        super(device, emptyVertexBuffer);
+        WebGPUCacheRenderPipelineTree._Cache = new NodeState();
+    }
+
+    protected _getRenderPipeline(param: { token: any, pipeline: Nullable<GPURenderPipeline> }): void {
+        const n0 = WebGPUCacheRenderPipelineTree._Cache;
+        let n1 = n0.values[this._states[0]];
+        if (!n1) {
+            n1 = new NodeState();
+            n0.values[this._states[0]] = n1;
+        }
+        let n2 = n1.values[this._states[1]];
+        if (!n2) {
+            n2 = new NodeState();
+            n1.values[this._states[1]] =  n2;
+        }
+        let n3 = n2.values[this._states[2]];
+        if (!n3) {
+            n3 = new NodeState();
+            n2.values[this._states[2]] = n3;
+        }
+        let n4 = n3.values[this._states[3]];
+        if (!n4) {
+            n4 = new NodeState();
+            n3.values[this._states[3]] =  n4;
+        }
+        let n5 = n4.values[this._states[4]];
+        if (!n5) {
+            n5 = new NodeState();
+            n4.values[this._states[4]] = n5;
+        }
+        let n6 = n5.values[this._states[5]];
+        if (!n6) {
+            n6 = new NodeState();
+            n5.values[this._states[5]] = n6;
+        }
+        let n7 = n6.values[this._states[6]];
+        if (!n7) {
+            n7 = new NodeState();
+            n6.values[this._states[6]] = n7;
+        }
+        let n8 = n7.values[this._states[7]];
+        if (!n8) {
+            n8 = new NodeState();
+            n7.values[this._states[7]] = n8;
+        }
+        let n9 = n8.values[this._states[8]];
+        if (!n9) {
+            n9 = new NodeState();
+            n8.values[this._states[8]] = n9;
+        }
+        for (let i = 9; i < this._states.length; ++i) {
+            let nn: NodeState | undefined = n9!.values[this._states[i]];
+            if (!nn) {
+                nn = new NodeState();
+                n9!.values[this._states[i]] = nn;
+            }
+            n9 = nn;
+        }
+
+        param.token = n9;
+        param.pipeline = n9.pipeline;
+    }
+
+    protected _setRenderPipeline(param: { token: NodeState, pipeline: Nullable<GPURenderPipeline> }): void {
+        param.token.pipeline = param.pipeline!;
+    }
+}

+ 1 - 0
src/Engines/index.ts

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

+ 20 - 0
src/Meshes/buffer.ts

@@ -260,6 +260,7 @@ export class VertexBuffer {
         } else {
             this._instanced = true;
         }
+        this._computeHashCode();
     }
 
     /**
@@ -288,6 +289,12 @@ export class VertexBuffer {
     public readonly uniqueId: number;
 
     /**
+     * Gets a hash code representing the format (type, normalized, size, isntanced, stride) of this buffer
+     * All buffers with the same format will have the same hash code
+     */
+    public readonly hashCode: number;
+
+    /**
      * Constructor
      * @param engine the engine
      * @param data the data to use for this vertex buffer
@@ -352,6 +359,19 @@ export class VertexBuffer {
 
         this._instanced = instanced !== undefined ? instanced : false;
         this._instanceDivisor = instanced ? divisor : 0;
+
+        this._computeHashCode();
+    }
+
+    private _computeHashCode(): void {
+        // note: cast to any because the property is declared readonly
+        (this.hashCode as any) =
+            (((this.type - 5120) << 0) +
+            ((this.normalized ? 1 : 0) << 3) +
+            (this._size << 4) +
+            ((this._instanced ? 1 : 0) << 6) +
+            /* keep 5 bits free */
+            (this.byteStride << 12));
     }
 
     /** @hidden */