瀏覽代碼

Merge pull request #1133 from nockawa/engine2d

Canvas2D Disposable + Engine.externalData + Scene.OnDisposeObserver fix
David Catuhe 9 年之前
父節點
當前提交
a37fb41af8

+ 35 - 0
src/Canvas2d/babylon.canvas2d.ts

@@ -1,4 +1,23 @@
 module BABYLON {
+
+    export class Canvas2DEngineBoundData {
+        public GetOrAddModelCache<TInstData>(key: string, factory: (key: string) => ModelRenderCache): ModelRenderCache {
+            return this._modelCache.getOrAddWithFactory(key, factory);
+        }
+
+        private _modelCache: StringDictionary<ModelRenderCache> = new StringDictionary<ModelRenderCache>();
+
+        public DisposeModelRenderCache(modelRenderCache: ModelRenderCache): boolean {
+            if (!modelRenderCache.isDisposed) {
+                return false;
+            }
+
+            this._modelCache.remove(modelRenderCache.modelKey);
+
+            return true;
+        }
+    }
+
     @className("Canvas2D")
     export class Canvas2D extends Group2D {
         /**
@@ -79,6 +98,7 @@
         }
 
         protected setupCanvas(scene: Scene, name: string, size: Size, isScreenSpace: boolean = true, cachingstrategy: number = Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
+            this._engineData = scene.getEngine().getOrAddExternalDataWithFactory("__BJSCANVAS2D__", k => new Canvas2DEngineBoundData());
             this._cachingStrategy = cachingstrategy;
             this._depthLevel = 0;
             this._hierarchyMaxDepth = 100;
@@ -92,6 +112,11 @@
             this._engine = scene.getEngine();
             this._renderingSize = new Size(0, 0);
 
+            // Register scene dispose to also dispose the canvas when it'll happens
+            scene.onDisposeObservable.add((d, s) => {
+                this.dispose();
+            });
+
             if (cachingstrategy !== Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
                 this._background = Rectangle2D.Create(this, "###CANVAS BACKGROUND###", 0, 0, size.width, size.height);
                 this._background.origin = Vector2.Zero();
@@ -127,6 +152,11 @@
                 this._scene.onAfterRenderObservable.remove(this._afterRenderObserver);
                 this._afterRenderObserver = null;
             }
+
+            if (this._groupCacheMaps) {
+                this._groupCacheMaps.forEach(m => m.dispose());
+                this._groupCacheMaps = null;
+            }
         }
 
         /**
@@ -221,6 +251,10 @@
             this._background.isVisible = true;
         }
 
+        public get engineData(): Canvas2DEngineBoundData {
+            return this._engineData;
+        }
+
         private checkBackgroundAvailability() {
             if (this._cachingStrategy === Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
                 throw Error("Can't use Canvas Background with the caching strategy TOPLEVELGROUPS");
@@ -241,6 +275,7 @@
             return this._hierarchyLevelZFactor;
         }
 
+        private _engineData: Canvas2DEngineBoundData;
         private _mapCounter = 0;
         private _background: Rectangle2D;
         private _scene: Scene;

+ 31 - 0
src/Canvas2d/babylon.group2d.ts

@@ -53,6 +53,37 @@
             this._unbindCacheTarget();
         }
 
+        public dispose(): boolean {
+            if (!super.dispose()) {
+                return false;
+            }
+
+            if (this._cacheRenderSprite) {
+                this._cacheRenderSprite.dispose();
+                this._cacheRenderSprite = null;
+            }
+
+            if (this._cacheTexture && this._cacheNode) {
+                this._cacheTexture.freeRect(this._cacheNode);
+                this._cacheTexture = null;
+                this._cacheNode = null;
+            }
+
+            if(this._primDirtyList) {
+                this._primDirtyList.splice(0);
+                this._primDirtyList = null;
+            }
+
+            if (this.groupRenderInfo) {
+                this.groupRenderInfo.forEach((k, v) => {
+                    v.dispose();
+                });
+                this.groupRenderInfo = null;
+            }
+
+            return true;
+        }
+
         /**
          * Create an instance of the Group Primitive.
          * A group act as a container for many sub primitives, if features:

+ 57 - 1
src/Canvas2d/babylon.modelRenderCache.ts

@@ -7,6 +7,7 @@
         constructor(owner: Group2D, cache: ModelRenderCache) {
             this._owner = owner;
             this._modelCache = cache;
+            this._modelCache.addRef();
             this._instancesPartsData = new Array<DynamicFloatArray>();
             this._instancesPartsBuffer = new Array<WebGLBuffer>();
             this._instancesPartsBufferSize = new Array<number>();
@@ -14,6 +15,31 @@
             this._instancesPartsUsedShaderCategories = new Array<string>();
         }
 
+        public dispose(): boolean {
+            if (this._isDisposed) {
+                return false;
+            }
+
+            if (this._modelCache) {
+                this._modelCache.dispose();
+            }
+
+            let engine = this._owner.owner.engine;
+            if (this._instancesPartsBuffer) {
+                this._instancesPartsBuffer.forEach(b => {
+                    engine._releaseBuffer(b);
+                });
+            }
+
+            this._partIndexFromId = null;
+            this._instancesPartsData = null;
+            this._instancesPartsBufferSize = null;
+            this._instancesPartsUsedShaderCategories = null;
+
+            return true;
+        }
+
+        _isDisposed: boolean;
         _owner: Group2D;
         _modelCache: ModelRenderCache;
         _partIndexFromId: StringDictionary<number>;
@@ -25,13 +51,41 @@
     }
 
     export class ModelRenderCache {
-        constructor(modelKey: string, isTransparent: boolean) {
+        constructor(engine: Engine, modelKey: string, isTransparent: boolean) {
+            this._engine = engine;
             this._modelKey = modelKey;
             this._isTransparent = isTransparent;
             this._nextKey = 1;
+            this._refCounter = 1;
             this._instancesData = new StringDictionary<InstanceDataBase[]>();
         }
 
+        public dispose(): boolean {
+            if (--this._refCounter !== 0) {
+                return false;
+            }
+
+            // Remove the Model Render Cache from the global dictionary
+            let edata = this._engine.getExternalData<Canvas2DEngineBoundData>("__BJSCANVAS2D__");
+            if (edata) {
+                edata.DisposeModelRenderCache(this);
+            }
+
+            return true;
+        }
+
+        public get isDisposed(): boolean {
+            return this._refCounter <= 0;
+        }
+
+        public addRef(): number {
+            return ++this._refCounter;
+        }
+
+        public get modelKey(): string {
+            return this._modelKey;
+        }
+
         /**
          * Render the model instances
          * @param instanceInfo
@@ -147,6 +201,7 @@
             });
         }
 
+        protected _engine: Engine;
         private _modelKey: string;
         private _isTransparent: boolean;
 
@@ -157,6 +212,7 @@
         _instancesData: StringDictionary<InstanceDataBase[]>;
 
         private _nextKey: number;
+        private _refCounter: number;
         _partIdList: number[];
         _partsDataStride: number[];
         _partsUsedCategories: Array<string[]>;

+ 25 - 0
src/Canvas2d/babylon.prim2dBase.ts

@@ -212,6 +212,31 @@
 
         }
 
+        public dispose(): boolean {
+            if (!super.dispose()) {
+                return false;
+            }
+
+            // If there's a parent, remove this object from its parent list
+            if (this._parent) {
+                let i = this._parent._children.indexOf(this);
+                if (i!==undefined) {
+                    this._parent._children.splice(i, 1);
+                }
+                this._parent = null;
+            }
+
+            // Recurse dispose to children
+            if (this._children) {
+                while (this._children.length > 0) {
+                    this._children[this._children.length - 1].dispose();
+                }
+                this._children = null;
+            }
+
+            return true;
+        }
+
         protected getActualZOffset(): number {
             return this._zOrder || 1-(this._siblingDepthOffset + this._hierarchyDepthOffset);
         }

+ 41 - 3
src/Canvas2d/babylon.rectangle2d.ts

@@ -12,8 +12,8 @@
         instancingBorderAttributes: InstancingAttributeInfo[];
         effectBorder: Effect;
 
-        constructor(modelKey: string, isTransparent: boolean) {
-            super(modelKey, isTransparent);
+        constructor(engine: Engine, modelKey: string, isTransparent: boolean) {
+            super(engine, modelKey, isTransparent);
         }
 
         render(instanceInfo: GroupInstanceInfo, context: Render2DContext): boolean {
@@ -90,6 +90,44 @@
             }
             return true;
         }
+
+        public dispose(): boolean {
+            if (!super.dispose()) {
+                return false;
+            }
+
+            if (this.fillVB) {
+                this._engine._releaseBuffer(this.fillVB);
+                this.fillVB = null;
+            }
+
+            if (this.fillIB) {
+                this._engine._releaseBuffer(this.fillIB);
+                this.fillIB = null;
+            }
+
+            if (this.effectFill) {
+                this._engine._releaseEffect(this.effectFill);
+                this.effectFill = null;
+            }
+
+            if (this.borderVB) {
+                this._engine._releaseBuffer(this.borderVB);
+                this.borderVB = null;
+            }
+
+            if (this.borderIB) {
+                this._engine._releaseBuffer(this.borderIB);
+                this.borderIB = null;
+            }
+
+            if (this.effectBorder) {
+                this._engine._releaseEffect(this.effectBorder);
+                this.effectBorder = null;
+            }
+
+            return true;
+        }
     }
 
     export class Rectangle2DInstanceData extends Shape2DInstanceData {
@@ -172,7 +210,7 @@
         public static roundSubdivisions = 16;
 
         protected createModelRenderCache(modelKey: string, isTransparent: boolean): ModelRenderCache {
-            let renderCache = new Rectangle2DRenderCache(modelKey, isTransparent);
+            let renderCache = new Rectangle2DRenderCache(this.owner.engine, modelKey, isTransparent);
             return renderCache;
         }
 

+ 25 - 1
src/Canvas2d/babylon.renderablePrim2d.ts

@@ -306,6 +306,25 @@
             this._isTransparent = false;
         }
 
+        public dispose(): boolean {
+            if (!super.dispose()) {
+                return false;
+            }
+
+            if (this._modelRenderInstanceID) {
+                this._modelRenderCache.removeInstanceData(this._modelRenderInstanceID);
+                this._modelRenderInstanceID = null;
+            }
+
+            if (this._modelRenderCache) {
+                this._modelRenderCache.dispose();
+                this._modelRenderCache = null;
+            }
+            this._instanceDataParts = null;
+
+            return true;
+        }
+
         public _prepareRenderPre(context: Render2DContext) {
             super._prepareRenderPre(context);
 
@@ -318,12 +337,17 @@
             // Need to create the model?
             let setupModelRenderCache = false;
             if (!this._modelRenderCache || this._modelDirty) {
-                this._modelRenderCache = SmartPropertyPrim.GetOrAddModelCache(this.modelKey, (key: string) => {
+                this._modelRenderCache = this.owner.engineData.GetOrAddModelCache(this.modelKey, (key: string) => {
                     let mrc = this.createModelRenderCache(key, this.isTransparent);
                     setupModelRenderCache = true;
                     return mrc;
                 });
                 this._modelDirty = false;
+
+                // if this is still false it means the MRC already exists, so we add a reference to it
+                if (!setupModelRenderCache) {
+                    this._modelRenderCache.addRef();
+                }
             }
 
             // Need to create the instance?

+ 5 - 6
src/Canvas2d/babylon.smartPropertyPrim.ts

@@ -185,12 +185,6 @@
             return (this._instanceDirtyFlags !== 0) || this._modelDirty;
         }
 
-        protected static GetOrAddModelCache<TInstData>(key: string, factory: (key: string) => ModelRenderCache): ModelRenderCache {
-            return SmartPropertyPrim.ModelCache.getOrAddWithFactory(key, factory);
-        }
-
-        protected static ModelCache: StringDictionary<ModelRenderCache> = new StringDictionary<ModelRenderCache>();
-
         private get propDic(): StringDictionary<Prim2DPropInfo> {
             if (!this._propInfo) {
                 let cti = ClassTreeInfo.get<Prim2DClassInfo, Prim2DPropInfo>(Object.getPrototypeOf(this));
@@ -328,6 +322,11 @@
 
                 // Overload the property setter implementation to add our own logic
                 descriptor.set = function (val) {
+                    // check for disposed first, do nothing
+                    if (this.isDisposed) {
+                        return;
+                    }
+
                     let curVal = getter.call(this);
 
                     if (SmartPropertyPrim._checkUnchanged(curVal, val)) {

+ 28 - 3
src/Canvas2d/babylon.sprite2d.ts

@@ -2,8 +2,6 @@
     export class Sprite2DRenderCache extends ModelRenderCache {
         vb: WebGLBuffer;
         ib: WebGLBuffer;
-        borderVB: WebGLBuffer;
-        borderIB: WebGLBuffer;
         instancingAttributes: InstancingAttributeInfo[];
 
         texture: Texture;
@@ -41,6 +39,33 @@
 
             engine.setAlphaMode(cur);
 
+            return true;
+        }
+
+        public dispose(): boolean {
+            if (!super.dispose()) {
+                return false;
+            }
+
+            if (this.vb) {
+                this._engine._releaseBuffer(this.vb);
+                this.vb = null;
+            }
+
+            if (this.ib) {
+                this._engine._releaseBuffer(this.ib);
+                this.ib = null;
+            }
+
+            if (this.texture) {
+                this.texture.dispose();
+                this.texture = null;
+            }
+
+            if (this.effect) {
+                this._engine._releaseEffect(this.effect);
+                this.effect = null;
+            }
 
             return true;
         }
@@ -157,7 +182,7 @@
         }
 
         protected createModelRenderCache(modelKey: string, isTransparent: boolean): ModelRenderCache {
-            let renderCache = new Sprite2DRenderCache(modelKey, isTransparent);
+            let renderCache = new Sprite2DRenderCache(this.owner.engine, modelKey, isTransparent);
             return renderCache;
         }
 

+ 29 - 3
src/Canvas2d/babylon.text2d.ts

@@ -2,8 +2,6 @@
     export class Text2DRenderCache extends ModelRenderCache {
         vb: WebGLBuffer;
         ib: WebGLBuffer;
-        borderVB: WebGLBuffer;
-        borderIB: WebGLBuffer;
         instancingAttributes: InstancingAttributeInfo[];
         fontTexture: FontTexture;
         effect: Effect;
@@ -42,9 +40,37 @@
 
             engine.setAlphaMode(cur);
 
+            return true;
+        }
+
+        public dispose(): boolean {
+            if (!super.dispose()) {
+                return false;
+            }
+
+            if (this.vb) {
+                this._engine._releaseBuffer(this.vb);
+                this.vb = null;
+            }
+
+            if (this.ib) {
+                this._engine._releaseBuffer(this.ib);
+                this.ib = null;
+            }
+
+            if (this.fontTexture) {
+                this.fontTexture.dispose();
+                this.fontTexture = null;
+            }
+
+            if (this.effect) {
+                this._engine._releaseEffect(this.effect);
+                this.effect = null;
+            }
 
             return true;
         }
+
     }
 
     export class Text2DInstanceData extends InstanceDataBase {
@@ -213,7 +239,7 @@
         }
 
         protected createModelRenderCache(modelKey: string, isTransparent: boolean): ModelRenderCache {
-            let renderCache = new Text2DRenderCache(modelKey, isTransparent);
+            let renderCache = new Text2DRenderCache(this.owner.engine, modelKey, isTransparent);
             return renderCache;
         }
 

+ 9 - 1
src/Canvas2d/babylon.worldSpaceCanvas2d.ts

@@ -5,7 +5,15 @@
 
             this._canvas = canvas;
         }
-        private _canvas: Canvas2D;
 
+        public dispose(): void {
+            super.dispose();
+            if (this._canvas) {
+                this._canvas.dispose();
+                this._canvas = null;
+            }
+        }
+
+        private _canvas: Canvas2D;
     }
 }

+ 43 - 0
src/babylon.engine.ts

@@ -351,6 +351,7 @@
         private _workingCanvas: HTMLCanvasElement;
         private _workingContext: CanvasRenderingContext2D;
 
+        private _externalData: StringDictionary<Object>;
         private _bindedRenderFunction: any;
 
         /**
@@ -362,6 +363,8 @@
         constructor(canvas: HTMLCanvasElement, antialias?: boolean, options?: { antialias?: boolean, preserveDrawingBuffer?: boolean, limitDeviceRatio?: number }, adaptToDeviceRatio = true) {
             this._renderingCanvas = canvas;
 
+            this._externalData = new StringDictionary<Object>();
+
             options = options || {};
             options.antialias = antialias;
 
@@ -2264,6 +2267,46 @@
             return data;
         }
 
+        /**
+         * Add an externaly attached data from its key.
+         * This method call will fail and return false, if such key already exists.
+         * If you don't care and just want to get the data no matter what, use the more convenient getOrAddExternalDataWithFactory() method.
+         * @param key the unique key that identifies the data
+         * @param data the data object to associate to the key for this Engine instance
+         * @return true if no such key were already present and the data was added successfully, false otherwise
+         */
+        public addExternalData<T>(key: string, data: T): boolean {
+            return this._externalData.add(key, data);
+        }
+
+        /**
+         * Get an externaly attached data from its key
+         * @param key the unique key that identifies the data
+         * @return the associated data, if present (can be null), or undefined if not present
+         */
+        public getExternalData<T>(key: string): T {
+            return <T>this._externalData.get(key);
+        }
+
+        /**
+         * Get an externaly attached data from its key, create it using a factory if it's not already present
+         * @param key the unique key that identifies the data
+         * @param factory the factory that will be called to create the instance if and only if it doesn't exists
+         * @return the associated data, can be null if the factory returned null.
+         */
+        public getOrAddExternalDataWithFactory<T>(key: string, factory: (k: string) => T): T {
+            return <T>this._externalData.getOrAddWithFactory(key, factory);
+        }
+
+        /**
+         * Remove an externaly attached data from the Engine instance
+         * @param key the unique key that identifies the data
+         * @return true if the data was successfully removed, false if it doesn't exist
+         */
+        public removeExternalData(key): boolean {
+            return this._externalData.remove(key);
+        }
+
         public releaseInternalTexture(texture: WebGLTexture): void {
             if (!texture) {
                 return;

+ 2 - 2
src/babylon.scene.ts

@@ -2361,8 +2361,8 @@
             this.debugLayer.hide();
 
             // Events
-            if (this.onDispose) {
-                this.onDispose();
+            if (this.onDisposeObservable) {
+                this.onDisposeObservable.notifyObservers(this);
             }
 
             this.onBeforeRenderObservable.clear();