Przeglądaj źródła

Handle multiple GPU buffers in UniformBuffer class

Popov72 4 lat temu
rodzic
commit
aefa21201d

+ 0 - 3
src/Engines/IPipelineContext.ts

@@ -27,9 +27,6 @@ export interface IPipelineContext {
     /** @hidden */
     _fillEffectInformation(effect: Effect, uniformBuffersNames: { [key: string]: number }, uniformsNames: string[], uniforms: { [key: string]: Nullable<WebGLUniformLocation> }, samplerList: string[], samplers: { [key: string]: number }, attributesNames: string[], attributes: number[]): void;
 
-    /** @hidden */
-    _useNewBindings(): void;
-
     /** Releases the resources associated with the pipeline. */
     dispose(): void;
 

+ 0 - 5
src/Engines/WebGL/webGLPipelineContext.ts

@@ -76,11 +76,6 @@ export class WebGLPipelineContext implements IPipelineContext {
         }
     }
 
-    /** @hidden */
-    public _useNewBindings(): void {
-        // nothing to do in WebGL
-    }
-
     /**
      * Release all associated resources.
      **/

+ 6 - 23
src/Engines/WebGPU/webgpuPipelineContext.ts

@@ -151,10 +151,6 @@ export class WebGPUPipelineContext implements IPipelineContext {
     }
 
     /** @hidden */
-    public _useNewBindings(): void {
-        this.buildUniformLayout();
-    }
-
     /**
      * Build the uniform buffer used in the material.
      */
@@ -169,27 +165,14 @@ export class WebGPUPipelineContext implements IPipelineContext {
             // Calling _rebuild() is enough to recreate the underlying hardware buffer without removing any other data.
             // That means the user does not need to re-set all the uniforms of the buffer, the new buffer is in the same state than the previous one.
 
-            // TODO WEBGPU don't recreate a new buffer each time (it is created in _rebuild()), keeps an array of buffers and reuse them. It means
-            // we need to be notified when a frame starts so that we can reset the pointer into this array
-            const buffer = this.uniformBuffer.getBuffer();
-            if (buffer) {
-                this.engine._releaseBuffer(buffer);
-            }
-            this.uniformBuffer._rebuild();
-        } else {
-            this.uniformBuffer = new UniformBuffer(this.engine);
-
-            for (let leftOverUniform of this.leftOverUniforms) {
-                const size = _uniformSizes[leftOverUniform.type];
-                this.uniformBuffer.addUniform(leftOverUniform.name, size, leftOverUniform.length);
-                // TODO WEBGPU. Replace with info from uniform buffer class
-                this.leftOverUniformsByName[leftOverUniform.name] = leftOverUniform.type;
-            }
-
-            this.uniformBuffer.create();
+        for (let leftOverUniform of this.leftOverUniforms) {
+            const size = _uniformSizes[leftOverUniform.type];
+            this.uniformBuffer.addUniform(leftOverUniform.name, size, leftOverUniform.length);
+            // TODO WEBGPU. Replace with info from uniform buffer class
+            this.leftOverUniformsByName[leftOverUniform.name] = leftOverUniform.type;
         }
 
-        this.bindGroups = null as any;
+        this.uniformBuffer.create();
     }
 
     /**

+ 6 - 0
src/Engines/engineFeatures.ts

@@ -19,4 +19,10 @@ export interface EngineFeatures {
 
     /** Indicates that prefiltered mipmaps can be generated in some processes (for eg when loading an HDR cube texture) */
     allowTexturePrefiltering: boolean;
+
+    /** Indicates to track the usage of ubos and to create new ones as necessary during a frame duration */
+    trackUbosInFrame: boolean;
+
+    /** @hidden */
+    _collectUbosUpdatedInFrame: boolean;
 }

+ 0 - 4
src/Engines/nativeEngine.ts

@@ -167,10 +167,6 @@ class NativePipelineContext implements IPipelineContext {
         attributes.push(...engine.getAttributes(this, attributesNames));
     }
 
-    /** @hidden */
-    public _useNewBindings(): void {
-    }
-
     /**
      * Release all associated resources.
      **/

+ 11 - 0
src/Engines/thinEngine.ts

@@ -182,6 +182,8 @@ export class ThinEngine {
         supportShadowSamplers: false,
         uniformBufferHardCheckMatrix: false,
         allowTexturePrefiltering: false,
+        trackUbosInFrame: false,
+        _collectUbosUpdatedInFrame: false,
     };
 
     /**
@@ -259,6 +261,14 @@ export class ThinEngine {
      */
     public disableUniformBuffers = false;
 
+    private _frameId = 0;
+    /**
+     * Gets the current frame id
+     */
+    public get frameId(): number {
+        return this._frameId;
+    }
+
     /** @hidden */
     public _uniformBuffers = new Array<UniformBuffer>();
 
@@ -1335,6 +1345,7 @@ export class ThinEngine {
         if (this._badOS) {
             this.flushFramebuffer();
         }
+        this._frameId++;
     }
 
     /**

+ 18 - 0
src/Engines/webgpuEngine.ts

@@ -261,6 +261,8 @@ export class WebGPUEngine extends Engine {
         ThinEngine.Features.supportShadowSamplers = true;
         ThinEngine.Features.uniformBufferHardCheckMatrix = true;
         ThinEngine.Features.allowTexturePrefiltering = true;
+        ThinEngine.Features.trackUbosInFrame = true;
+        ThinEngine.Features._collectUbosUpdatedInFrame = true;
 
         options.deviceDescriptor = options.deviceDescriptor || { };
         options.swapChainFormat = options.swapChainFormat || WebGPUConstants.TextureFormat.BGRA8Unorm;
@@ -2427,6 +2429,22 @@ export class WebGPUEngine extends Engine {
         this._renderEncoder = this._device.createCommandEncoder(this._renderEncoderDescriptor);
         this._renderTargetEncoder = this._device.createCommandEncoder(this._renderTargetEncoderDescriptor);
 
+        if (ThinEngine.Features._collectUbosUpdatedInFrame) {
+            if (dbgVerboseLogsForFirstFrames) {
+                if (!(this as any)._count || (this as any)._count < dbgVerboseLogsNumFrames) {
+                    const list: Array<string> = [];
+                    for (const name in UniformBuffer._updatedUbosInFrame) {
+                        list.push(name + ":" + UniformBuffer._updatedUbosInFrame[name]);
+                    }
+                    console.log("updated ubos in frame #" + (this as any)._count, " -", list.join(", "));
+                }
+            }
+            UniformBuffer._updatedUbosInFrame = {};
+        }
+
+        this._counters.numPipelineDescriptorCreation = 0;
+        this._counters.numBindGroupsCreation = 0;
+
         super.endFrame();
 
         if (dbgVerboseLogsForFirstFrames) {

+ 0 - 2
src/Materials/Textures/Filtering/hdrFiltering.ts

@@ -138,8 +138,6 @@ export class HDRFiltering {
                 effect.setFloat("alphaG", alpha);
 
                 this._effectRenderer.draw();
-
-                effect.useNewBindings();
             }
         }
 

+ 0 - 7
src/Materials/effect.ts

@@ -1233,13 +1233,6 @@ export class Effect implements IDisposable {
     }
 
     /**
-     * Instructs the underlying rendering system that we want to use a new set of parameter bindings for the next rendering operation using this effect
-     */
-    public useNewBindings(): void {
-        this._pipelineContext!._useNewBindings();
-    }
-
-    /**
      * Release all associated resources.
      **/
     public dispose() {

+ 97 - 5
src/Materials/uniformBuffer.ts

@@ -22,8 +22,15 @@ import "../Engines/Extensions/engine.uniformBuffer";
  * https://www.khronos.org/opengl/wiki/Uniform_Buffer_Object
  */
 export class UniformBuffer {
+    /** @hidden */
+    public static _updatedUbosInFrame: { [name: string]: number } = {};
+
     private _engine: Engine;
     private _buffer: Nullable<DataBuffer>;
+    private _buffers : Array<[Nullable<DataBuffer>, Float32Array, { [name: string]: number }]>;
+    private _bufferIndex: number;
+    private _createBufferOnWrite: boolean;
+    private _advanceToNextBuffer: boolean;
     private _data: number[];
     private _bufferData: Float32Array;
     private _dynamic?: boolean;
@@ -34,6 +41,7 @@ export class UniformBuffer {
     private _needSync: boolean;
     private _noUBO: boolean;
     private _currentEffect: Effect;
+    private _currentFrameId: number;
 
     /** @hidden */
     public _alreadyBound = false;
@@ -166,6 +174,14 @@ export class UniformBuffer {
         this._uniformLocationPointer = 0;
         this._needSync = false;
 
+        if (ThinEngine.Features.trackUbosInFrame) {
+            this._buffers = [];
+            this._bufferIndex = -1;
+            this._createBufferOnWrite = false;
+            this._advanceToNextBuffer = false;
+            this._currentFrameId = 0;
+        }
+
         if (this._noUBO) {
             this.updateMatrix3x3 = this._updateMatrix3x3ForEffect;
             this.updateMatrix2x2 = this._updateMatrix2x2ForEffect;
@@ -458,6 +474,21 @@ export class UniformBuffer {
         } else {
             this._buffer = this._engine.createUniformBuffer(this._bufferData);
         }
+
+        if (ThinEngine.Features.trackUbosInFrame) {
+            this._buffers.push([this._buffer, this._bufferData, this._valueCache]);
+            this._bufferIndex = this._buffers.length - 1;
+            this._createBufferOnWrite = false;
+        }
+    }
+
+    /** @hidden */
+    public get _numBuffers(): number {
+        return this._buffers.length;
+    }
+
+    public get name(): string {
+        return this._name;
     }
 
     /**
@@ -472,12 +503,55 @@ export class UniformBuffer {
         }
 
         if (!this._dynamic && !this._needSync) {
+            this._advanceToNextBuffer = ThinEngine.Features.trackUbosInFrame;
+            this._createBufferOnWrite = ThinEngine.Features.trackUbosInFrame;
             return;
         }
 
         this._engine.updateUniformBuffer(this._buffer, this._bufferData);
 
+        if (ThinEngine.Features._collectUbosUpdatedInFrame) {
+            if (!UniformBuffer._updatedUbosInFrame[this._name]) {
+                UniformBuffer._updatedUbosInFrame[this._name] = 0;
+            }
+            UniformBuffer._updatedUbosInFrame[this._name]++;
+        }
+
         this._needSync = false;
+        this._createBufferOnWrite = ThinEngine.Features.trackUbosInFrame;
+        this._advanceToNextBuffer = ThinEngine.Features.trackUbosInFrame;
+    }
+
+    private _createNewBuffer(): void {
+        this._bufferData = this._bufferData.slice();
+        this._valueCache = { ...this._valueCache }; // clone
+        this._rebuild();
+    }
+
+    private _checkBuffers(): void {
+        if (this._advanceToNextBuffer) {
+            if (this._bufferIndex + 1 < this._buffers.length) {
+                this._bufferIndex++;
+                this._buffer = this._buffers[this._bufferIndex][0];
+                this._bufferData = this._buffers[this._bufferIndex][1];
+                this._valueCache = this._buffers[this._bufferIndex][2];
+                this._createBufferOnWrite = false;
+            }
+            this._advanceToNextBuffer = false;
+        }
+
+        if (ThinEngine.Features.trackUbosInFrame && this._currentFrameId !== this._engine.frameId) {
+            this._currentFrameId = this._engine.frameId;
+            this._createBufferOnWrite = false;
+            if (this._buffers && this._buffers.length > 0) {
+                this._bufferIndex = 0;
+                this._buffer = this._buffers[this._bufferIndex][0];
+                this._bufferData = this._buffers[this._bufferIndex][1];
+                this._valueCache = this._buffers[this._bufferIndex][2];
+            } else {
+                this._bufferIndex = -1;
+            }
+        }
     }
 
     /**
@@ -487,6 +561,7 @@ export class UniformBuffer {
      * @param size Define the size of the data.
      */
     public updateUniform(uniformName: string, data: FloatArray, size: number) {
+        this._checkBuffers();
 
         var location = this._uniformLocations[uniformName];
         if (location === undefined) {
@@ -510,6 +585,9 @@ export class UniformBuffer {
             for (var i = 0; i < size; i++) {
                 if ((size === 16 && !ThinEngine.Features.uniformBufferHardCheckMatrix) || this._bufferData[location + i] !== data[i]) {
                     changed = true;
+                    if (this._createBufferOnWrite) {
+                        this._createNewBuffer();
+                    }
                     this._bufferData[location + i] = data[i];
                 }
             }
@@ -517,6 +595,9 @@ export class UniformBuffer {
             this._needSync = this._needSync || changed;
         } else {
             // No cache for dynamic
+            if (this._createBufferOnWrite) {
+                this._createNewBuffer();
+            }
             for (var i = 0; i < size; i++) {
                 this._bufferData[location + i] = data[i];
             }
@@ -530,6 +611,7 @@ export class UniformBuffer {
      * @param size Define the size of the data.
      */
     public updateUniformArray(uniformName: string, data: FloatArray, size: number) {
+        this._checkBuffers();
 
         var location = this._uniformLocations[uniformName];
         if (location === undefined) {
@@ -551,6 +633,9 @@ export class UniformBuffer {
             for (var i = 0; i < size; i++) {
                 if (this._bufferData[location + baseStride * 4 + countToFour] !== data[i]) {
                     changed = true;
+                    if (this._createBufferOnWrite) {
+                        this._createNewBuffer();
+                    }
                     this._bufferData[location + baseStride * 4 + countToFour] = data[i];
                 }
                 countToFour++;
@@ -566,6 +651,9 @@ export class UniformBuffer {
             this._needSync = this._needSync || changed;
         } else {
             // No cache for dynamic
+            if (this._createBufferOnWrite) {
+                this._createNewBuffer();
+            }
             for (var i = 0; i < size; i++) {
                 this._bufferData[location + i] = data[i];
             }
@@ -573,8 +661,10 @@ export class UniformBuffer {
     }
 
     // Matrix cache
-    private _valueCache: { [key: string]: any } = {};
+    private _valueCache: { [key: string]: number } = {};
     private _cacheMatrix(name: string, matrix: IMatrixLike): boolean {
+        this._checkBuffers();
+
         var cache = this._valueCache[name];
         var flag = matrix.updateFlag;
         if (cache !== undefined && cache === flag) {
@@ -786,10 +876,12 @@ export class UniformBuffer {
             uniformBuffers.pop();
         }
 
-        if (!this._buffer) {
-            return;
-        }
-        if (this._engine._releaseBuffer(this._buffer)) {
+        if (ThinEngine.Features.trackUbosInFrame && this._buffers) {
+            for (let i = 0; i < this._buffers.length; ++i) {
+                const buffer = this._buffers[i][0];
+                this._engine._releaseBuffer(buffer!);
+            }
+        } else if (this._buffer && this._engine._releaseBuffer(this._buffer)) {
             this._buffer = null;
         }
     }

+ 0 - 2
src/Rendering/boundingBoxRenderer.ts

@@ -273,8 +273,6 @@ export class BoundingBoxRenderer implements ISceneComponent {
 
                 // Draw order
                 engine.drawElementsType(Material.LineListDrawMode, 0, 24);
-
-                this._colorShader.getEffect()?.useNewBindings();
             }
 
             // Front

+ 19 - 53
src/scene.ts

@@ -55,6 +55,7 @@ import { Frustum } from './Maths/math.frustum';
 import { UniqueIdGenerator } from './Misc/uniqueIdGenerator';
 import { FileTools, LoadFileError, RequestFileError, ReadFileError } from './Misc/fileTools';
 import { IClipPlanesHolder } from './Misc/interfaces/iClipPlanesHolder';
+import { ThinEngine } from './Engines/thinEngine';
 
 declare type Ray = import("./Culling/ray").Ray;
 declare type TrianglePickingPredicate = import("./Culling/ray").TrianglePickingPredicate;
@@ -1170,9 +1171,7 @@ export class Scene extends AbstractScene implements IAnimatable, IClipPlanesHold
     public _activeAnimatables = new Array<Animatable>();
 
     private _transformMatrix = Matrix.Zero();
-    private _sceneUbo: { viewUpdateFlag: number, projectionUpdateFlag: number, index: number, buffer: UniformBuffer };
-    private _sceneUbos: Array<{ viewUpdateFlag: number, projectionUpdateFlag: number, index: number, buffer: UniformBuffer }> = [];
-    private _sceneUboNextIndex = 0;
+    private _sceneUbo: UniformBuffer;
 
     /** @hidden */
     public _viewMatrix: Matrix;
@@ -1412,6 +1411,9 @@ export class Scene extends AbstractScene implements IAnimatable, IClipPlanesHold
             this.attachControl();
         }
 
+        // Uniform Buffer
+        this._createUbo();
+
         // Default Image processing definition
         if (ImageProcessingConfiguration) {
             this._imageProcessingConfiguration = new ImageProcessingConfiguration();
@@ -1653,14 +1655,11 @@ export class Scene extends AbstractScene implements IAnimatable, IClipPlanesHold
         this._renderId++;
     }
 
-    private _createUbo(): UniformBuffer {
-        const ubo = new UniformBuffer(this._engine, undefined, true);
-
-        ubo.addUniform("viewProjection", 16);
-        ubo.addUniform("view", 16);
-        ubo.addUniform("viewPosition", 4);
-
-        return ubo;
+    private _createUbo(): void {
+        this._sceneUbo = new UniformBuffer(this._engine, undefined, !ThinEngine.Features.trackUbosInFrame, "scene");
+        this._sceneUbo.addUniform("viewProjection", 16);
+        this._sceneUbo.addUniform("view", 16);
+        this._sceneUbo.addUniform("viewPosition", 4);
     }
 
     /**
@@ -2004,12 +2003,8 @@ export class Scene extends AbstractScene implements IAnimatable, IClipPlanesHold
      * @param projectionR defines the right Projection matrix to use (if provided)
      */
     public setTransformMatrix(viewL: Matrix, projectionL: Matrix, viewR?: Matrix, projectionR?: Matrix): void {
-        if (this._engine.supportsUniformBuffers) {
-            if (this._sceneUbo && this._sceneUbo.viewUpdateFlag === viewL.updateFlag && this._sceneUbo.projectionUpdateFlag === projectionL.updateFlag) {
-                this._sceneUboNextIndex = this._sceneUbo.index + 1;
-                return;
-            }
-        } else if (this._viewUpdateFlag === viewL.updateFlag && this._projectionUpdateFlag === projectionL.updateFlag) {
+        // TODO WEBGPU handle case where the matrices didn't change but the viewPosition will still be different than previously
+        if (this._viewUpdateFlag === viewL.updateFlag && this._projectionUpdateFlag === projectionL.updateFlag) {
             return;
         }
 
@@ -2028,53 +2023,29 @@ export class Scene extends AbstractScene implements IAnimatable, IClipPlanesHold
         }
 
         if (this._multiviewSceneUbo && this._multiviewSceneUbo.useUbo) {
-            // TODO WEBGPU handle multiview ubo the same way we handle scene ubo
             this._updateMultiviewUbo(viewR, projectionR);
-        } else if (this._engine.supportsUniformBuffers) {
-            this._getNewSceneUniformBuffer(false);
-
-            this._sceneUbo.viewUpdateFlag = viewL.updateFlag;
-            this._sceneUbo.projectionUpdateFlag = projectionL.updateFlag;
-
-            this._sceneUbo.buffer.updateMatrix("viewProjection", this._transformMatrix);
-            this._sceneUbo.buffer.updateMatrix("view", this._viewMatrix);
+        } else if (this._sceneUbo.useUbo) {
+            this._sceneUbo.updateMatrix("viewProjection", this._transformMatrix);
+            this._sceneUbo.updateMatrix("view", this._viewMatrix);
 
             const eyePosition = this._forcedViewPosition ? this._forcedViewPosition : (this._mirroredCameraPosition ? this._mirroredCameraPosition : (this.activeCamera!).globalPosition);
             const invertNormal = (this.useRightHandedSystem === (this._mirroredCameraPosition != null));
-            this._sceneUbo.buffer.updateFloat4("viewPosition",
+            this._sceneUbo.updateFloat4("viewPosition",
                 eyePosition.x,
                 eyePosition.y,
                 eyePosition.z,
                 invertNormal ? -1 : 1);
 
-            this._sceneUbo.buffer.update();
+            this._sceneUbo.update();
         }
     }
 
-    /** @hidden */
-    public _getNewSceneUniformBuffer(resetFlags = true): UniformBuffer {
-        if (this._sceneUboNextIndex == this._sceneUbos.length) {
-            this._sceneUbos.push({ viewUpdateFlag: -1, projectionUpdateFlag: -1, index: this._sceneUboNextIndex, buffer: this._createUbo() });
-        }
-
-        this._sceneUbo = this._sceneUbos[this._sceneUboNextIndex++];
-        if (resetFlags) {
-            this._sceneUbo.viewUpdateFlag = this._sceneUbo.projectionUpdateFlag = -1;
-        }
-
-        return this._sceneUbo.buffer;
-    }
-
-    private _resetSceneUniformBuffer() {
-        this._sceneUboNextIndex = 0;
-        this._sceneUbo = this._sceneUbos[0];
-    }
     /**
      * Gets the uniform buffer used to store scene data
      * @returns a UniformBuffer
      */
     public getSceneUniformBuffer(): UniformBuffer {
-        return this._multiviewSceneUbo ? this._multiviewSceneUbo : this._sceneUbo.buffer;
+        return this._multiviewSceneUbo ? this._multiviewSceneUbo : this._sceneUbo;
     }
 
     /**
@@ -3991,7 +3962,6 @@ export class Scene extends AbstractScene implements IAnimatable, IClipPlanesHold
         }
 
         this._frameId++;
-        this._resetSceneUniformBuffer();
 
         // Register components that have been associated lately to the scene.
         this._registerTransientComponents();
@@ -4390,11 +4360,7 @@ export class Scene extends AbstractScene implements IAnimatable, IClipPlanesHold
         }
 
         // Release UBO
-        if (this._sceneUbos) {
-            for (let i = 0; i < this._sceneUbos.length; ++i) {
-                this._sceneUbos[i].buffer.dispose();
-            }
-        }
+        this._sceneUbo.dispose();
 
         if (this._multiviewSceneUbo) {
             this._multiviewSceneUbo.dispose();