Pārlūkot izejas kodu

Merge pull request #1134 from nockawa/engine2d

Fixed DynamicFloatArray.grow() method
David Catuhe 9 gadi atpakaļ
vecāks
revīzija
9afee65506

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

@@ -120,6 +120,7 @@
             if (cachingstrategy !== Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
                 this._background = Rectangle2D.Create(this, "###CANVAS BACKGROUND###", 0, 0, size.width, size.height);
                 this._background.origin = Vector2.Zero();
+                this._background.levelVisible = false;
             }
             this._isScreeSpace = isScreenSpace;
 

+ 40 - 28
src/Canvas2d/babylon.group2d.ts

@@ -224,7 +224,7 @@
 
                         // We need to check if prepare is needed because even if the primitive is in the dirtyList, its parent primitive may also have been modified, then prepared, then recurse on its children primitives (this one for instance) if the changes where impacting them.
                         // For instance: a Rect's position change, the position of its children primitives will also change so a prepare will be call on them. If a child was in the dirtyList we will avoid a second prepare by making this check.
-                        if (p.needPrepare()) {
+                        if (!p.isDisposed && p.needPrepare()) {
                             p._prepareRender(context);
                         }
                     });
@@ -258,39 +258,51 @@
                 }
 
                 // For each different model of primitive to render
+                let totalRenderCount = 0;
                 this.groupRenderInfo.forEach((k, v) => {
-                    for (let i = 0; i < v._instancesPartsData.length; i++) {
-                        // If the instances of the model was changed, pack the data
-                        let instanceData = v._instancesPartsData[i].pack();
 
-                        // Compute the size the instance buffer should have
-                        let neededSize = v._instancesPartsData[i].usedElementCount * v._instancesPartsData[i].stride * 4;
-
-                        // Check if we have to (re)create the instancesBuffer because there's none or the size doesn't match
-                        if (!v._instancesPartsBuffer[i] || (v._instancesPartsBufferSize[i] !== neededSize)) {
-                            if (v._instancesPartsBuffer[i]) {
-                                engine.deleteInstancesBuffer(v._instancesPartsBuffer[i]);
+                    // This part will pack the dynfloatarray and update the instanced array WebGLBufffer
+                    // Skip it if instanced arrays are not supported
+                    if (this.owner.supportInstancedArray) {
+                        for (let i = 0; i < v._instancesPartsData.length; i++) {
+                            // If the instances of the model was changed, pack the data
+                            let array = v._instancesPartsData[i];
+                            let instanceData = array.pack();
+                            totalRenderCount += array.usedElementCount;
+
+                            // Compute the size the instance buffer should have
+                            let neededSize = array.usedElementCount * array.stride * 4;
+
+                            // Check if we have to (re)create the instancesBuffer because there's none or the size is too small
+                            if (!v._instancesPartsBuffer[i] || (v._instancesPartsBufferSize[i] <= neededSize)) {
+                                if (v._instancesPartsBuffer[i]) {
+                                    engine.deleteInstancesBuffer(v._instancesPartsBuffer[i]);
+                                }
+                                v._instancesPartsBuffer[i] = engine.createInstancesBuffer(neededSize);
+                                v._instancesPartsBufferSize[i] = neededSize;
+                                v._dirtyInstancesData = true;
+
+                                // Update the WebGL buffer to match the new content of the instances data
+                                engine._gl.bufferSubData(engine._gl.ARRAY_BUFFER, 0, instanceData);
+                            } else if (v._dirtyInstancesData) {
+                                // Update the WebGL buffer to match the new content of the instances data
+                                engine._gl.bindBuffer(engine._gl.ARRAY_BUFFER, v._instancesPartsBuffer[i]);
+                                engine._gl.bufferSubData(engine._gl.ARRAY_BUFFER, 0, instanceData);
+
+                                v._dirtyInstancesData = false;
                             }
-                            v._instancesPartsBuffer[i] = engine.createInstancesBuffer(neededSize);
-                            v._instancesPartsBufferSize[i] = neededSize;
-                            v._dirtyInstancesData = true;
-
-                            // Update the WebGL buffer to match the new content of the instances data
-                            engine._gl.bufferSubData(engine._gl.ARRAY_BUFFER, 0, instanceData);
-                        } else if (v._dirtyInstancesData) {
-                            // Update the WebGL buffer to match the new content of the instances data
-                            engine._gl.bindBuffer(engine._gl.ARRAY_BUFFER, v._instancesPartsBuffer[i]);
-                            engine._gl.bufferSubData(engine._gl.ARRAY_BUFFER, 0, instanceData);
-
-                            v._dirtyInstancesData = false;
                         }
                     }
-                    // render all the instances of this model, if the render method returns true then our instances are no longer dirty
-                    let renderFailed = !v._modelCache.render(v, context);
 
-                    // Update dirty flag/related
-                    v._dirtyInstancesData = renderFailed;
-                    failedCount += renderFailed ? 1 : 0;
+                    // Submit render only if we have something to render (everything may be hiden and the floatarray empty)
+                    if (!this.owner.supportInstancedArray || totalRenderCount > 0) {
+                        // render all the instances of this model, if the render method returns true then our instances are no longer dirty
+                        let renderFailed = !v._modelCache.render(v, context);
+
+                        // Update dirty flag/related
+                        v._dirtyInstancesData = renderFailed;
+                        failedCount += renderFailed ? 1 : 0;
+                    }
                 });
 
                 // The group's content is no longer dirty

+ 11 - 3
src/Canvas2d/babylon.prim2dBase.ts

@@ -231,7 +231,6 @@
                 while (this._children.length > 0) {
                     this._children[this._children.length - 1].dispose();
                 }
-                this._children = null;
             }
 
             return true;
@@ -248,7 +247,7 @@
         }
 
         public needPrepare(): boolean {
-            return this._modelDirty || (this._instanceDirtyFlags !== 0) || (this._globalTransformProcessStep !== this._globalTransformStep);
+            return (this.isVisible || this._visibilityChanged) && (this._modelDirty || (this._instanceDirtyFlags !== 0) || (this._globalTransformProcessStep !== this._globalTransformStep));
         }
 
         public _prepareRender(context: Render2DContext) {
@@ -301,6 +300,10 @@
         }
 
         protected updateGlobalTransVis(recurse: boolean) {
+            if (this.isDisposed) {
+                return;
+            }
+
             // Check if the parent is synced
             if (this._parent && this._parent._globalTransformProcessStep !== this.owner._globalTransformProcessStep) {
                 this._parent.updateGlobalTransVis(false);
@@ -308,11 +311,15 @@
 
             // Check if we must update this prim
             if (this === <any>this.owner || this._globalTransformProcessStep !== this.owner._globalTransformProcessStep) {
+                let curVisibleState = this.isVisible;
                 this.isVisible = (!this._parent || this._parent.isVisible) && this.levelVisible;
 
+                // Detect a change of visibility
+                this._visibilityChanged = (curVisibleState!==undefined) && curVisibleState !== this.isVisible;
+
                 // Detect if either the parent or this node changed
                 let tflags = Prim2DBase.positionProperty.flagId | Prim2DBase.rotationProperty.flagId | Prim2DBase.scaleProperty.flagId;
-                if ((this._parent && this._parent._globalTransformStep !== this._parentTransformStep) || this.checkPropertiesDirty(tflags)) {
+                if (this.isVisible && (this._parent && this._parent._globalTransformStep !== this._parentTransformStep) || this.checkPropertiesDirty(tflags)) {
                     var rot = Quaternion.RotationAxis(new Vector3(0, 0, 1), this._rotation);
                     var local = Matrix.Compose(new Vector3(this._scale, this._scale, this._scale), rot, new Vector3(this._position.x, this._position.y, 0));
 
@@ -345,6 +352,7 @@
         private _zOrder: number;
         private _levelVisible: boolean;
         public _boundingInfoDirty: boolean;
+        protected _visibilityChanged;
         private _isVisible: boolean;
         private _id: string;
         private _position: Vector2;

+ 36 - 5
src/Canvas2d/babylon.renderablePrim2d.ts

@@ -320,7 +320,13 @@
                 this._modelRenderCache.dispose();
                 this._modelRenderCache = null;
             }
-            this._instanceDataParts = null;
+
+            if (this._instanceDataParts) {
+                this._instanceDataParts.forEach(p => {
+                    p.freeElements();
+                });
+                this._instanceDataParts = null;
+            }
 
             return true;
         }
@@ -337,6 +343,9 @@
             // Need to create the model?
             let setupModelRenderCache = false;
             if (!this._modelRenderCache || this._modelDirty) {
+                if (this._modelRenderCache) {
+                    this._modelRenderCache.dispose();
+                }
                 this._modelRenderCache = this.owner.engineData.GetOrAddModelCache(this.modelKey, (key: string) => {
                     let mrc = this.createModelRenderCache(key, this.isTransparent);
                     setupModelRenderCache = true;
@@ -350,14 +359,19 @@
                 }
             }
 
-            // Need to create the instance?
             let gii: GroupInstanceInfo;
             let newInstance = false;
+
+            // Need to create the instance data parts?
             if (!this._modelRenderInstanceID) {
+                // Yes, flag it for later, more processing will have to be done
                 newInstance = true;
+
+                // Create the instance data parts of the primitive and store them
                 let parts = this.createInstanceDataParts();
                 this._instanceDataParts = parts;
 
+                // Check if the ModelRenderCache for this particular instance is also brand new, initialize it if it's the case
                 if (!this._modelRenderCache._partsDataStride) {
                     let ctiArray = new Array<ClassTreeInfo<InstanceClassInfo, InstancePropInfo>>();
                     let dataStrides = new Array<number>();
@@ -371,7 +385,7 @@
                         // Make sure the instance is visible other the properties won't be set and their size/offset wont be computed
                         let curVisible = this.isVisible;
                         this.isVisible = true;
-                        // We manually trigger refreshInstanceData for the only sake of evaluating each isntance property size and offset in the instance data, this can only be made at runtime. Once it's done we have all the information to create the instance data buffer.
+                        // We manually trigger refreshInstanceData for the only sake of evaluating each instance property size and offset in the instance data, this can only be made at runtime. Once it's done we have all the information to create the instance data buffer.
                         //console.log("Build Prop Layout for " + Tools.getClassName(this._instanceDataParts[0]));
                         let joinCat = cat.join(";");
                         joinedUsedCatList.push(joinCat);
@@ -401,6 +415,9 @@
                     this._modelRenderCache._partIdList = partIdList;
                 }
 
+                // The Rendering resources (Effect, VB, IB, Textures) are stored in the ModelRenderCache
+                // But it's the RenderGroup that will store all the Instanced related data to render all the primitive it owns.
+                // So for a given ModelKey we getOrAdd a GroupInstanceInfo that will store all these data
                 gii = this.renderGroup.groupRenderInfo.getOrAddWithFactory(this.modelKey, k => new GroupInstanceInfo(this.renderGroup, this._modelRenderCache));
 
                 // First time init of the GroupInstanceInfo
@@ -416,26 +433,39 @@
                     }
                 }
 
+                // For each instance data part of the primitive, allocate the instanced element it needs for render
                 for (let i = 0; i < parts.length; i++) {
                     let part = parts[i];
                     part.dataBuffer = gii._instancesPartsData[i];
                     part.allocElements();
                 }
 
+                // Add the instance data parts in the ModelRenderCache they belong, track them by storing their ID in the primitive in case we need to change the model later on, so we'll have to release the allocated instance data parts because they won't fit anymore
                 this._modelRenderInstanceID = this._modelRenderCache.addInstanceDataParts(this._instanceDataParts);
             }
 
+            // If the ModelRenderCache is brand new, now is the time to call the implementation's specific setup method to create the rendering resources
             if (setupModelRenderCache) {
                 this.setupModelRenderCache(this._modelRenderCache);
             }
 
-            if (context.forceRefreshPrimitive || newInstance || (this._instanceDirtyFlags !== 0) || (this._globalTransformProcessStep !== this._globalTransformStep)) {
+            // At this stage we have everything correctly initialized, ModelRenderCache is setup, Model Instance data are good too, they have allocated elements in the Instanced DynamicFloatArray.
+
+            // The last thing to do is check if the instanced related data must be updated because a InstanceLevel property had changed or the primitive visibility changed.
+            if (this._visibilityChanged || context.forceRefreshPrimitive || newInstance || (this._instanceDirtyFlags !== 0) || (this._globalTransformProcessStep !== this._globalTransformStep)) {
+
+                // Fetch the GroupInstanceInfo if we don't already have it
                 if (!gii) {
                     gii = this.renderGroup.groupRenderInfo.get(this.modelKey);
                 }
 
+                // For each Instance Data part, refresh it to update the data in the DynamicFloatArray
                 for (let part of this._instanceDataParts) {
-                    let cat = this.getUsedShaderCategories(part);
+                    // Check if we need to allocate data elements (hidden prim which becomes visible again)
+                    if (this._visibilityChanged && !part.dataElements) {
+                        part.allocElements();
+                    }
+
                     InstanceClassInfo._CurCategories = gii._instancesPartsUsedShaderCategories[gii._partIndexFromId.get(part.id.toString())];
 
                     // Will return false if the instance should not be rendered (not visible or other any reasons)
@@ -449,6 +479,7 @@
                 this._instanceDirtyFlags = 0;
 
                 gii._dirtyInstancesData = true;
+                this._visibilityChanged = false;    // Reset the flag as we've handled the case
             }
         }
 

+ 17 - 12
src/Tools/babylon.dynamicFloatArray.ts

@@ -97,10 +97,15 @@
 
             let firstFreeSlotOffset = sortedFree[0].offset;
             let freeZoneSize = 1;
+            let occupiedZoneSize = this.usedElementCount * s;
 
-            // The sortedFree array is sorted in reverse, first free at the end, last free at the beginning, so we loop from the end to beginning
             let prevOffset = sortedFree[0].offset;
             for (let i = 1; i < sortedFree.length; i++) {
+                // If the first free (which means everything before is occupied) is greater or equal the occupied zone size, it means everything is defragmented, we can quit
+                if (firstFreeSlotOffset >= occupiedZoneSize) {
+                    break;
+                }
+
                 let curFree = sortedFree[i];
                 let curOffset = curFree.offset;
 
@@ -134,7 +139,7 @@
                     this._moveElement(moveEl, firstFreeSlotOffset);
                     let replacedEl = sortedAll[freeI];
 
-                    // set the offset of the element element we replace with a value that will make it discard at the end of the method
+                    // set the offset of the element we replaced with a value that will make it discard at the end of the method
                     replacedEl.offset = curMoveOffset;
 
                     // Swap the element we moved and the one it replaced in the sorted array to reflect the action we've made
@@ -147,11 +152,11 @@
 
                 // Free Zone is smaller or equal so it's no longer a free zone, set the new one to the current location
                 if (freeZoneSize <= usedRange) {
-                    firstFreeSlotOffset = curOffset;
+                    firstFreeSlotOffset = curMoveOffset+s;
                     freeZoneSize = 1;
                 }
 
-                // Free Zone was bigger, the firstFreeSlotOffset is already up to date, but we need to update the its size
+                // Free Zone was bigger, the firstFreeSlotOffset is already up to date, but we need to update its size
                 else {
                     freeZoneSize = ((curOffset - firstFreeSlotOffset) / s) + 1;
                 }
@@ -180,22 +185,22 @@
 
         private _growBuffer() {
             // Allocate the new buffer with 50% more entries, copy the content of the current one
-            let newElCount = this.totalElementCount * 1.5;
+            let newElCount = Math.floor(this.totalElementCount * 1.5);
             let newBuffer = new Float32Array(newElCount * this._stride);
             newBuffer.set(this.buffer);
 
+            let curCount = this.totalElementCount;
             let addedCount = newElCount - this.totalElementCount;
-            this._allEntries.length += addedCount;
-            this._freeEntries.length += addedCount;
 
-            for (let i = this.totalElementCount; i < newElCount; i++) {
-                let el = new DynamicFloatArrayElementInfo();
-                el.offset = i * this._stride;
+            for (let i = 0; i < addedCount; i++) {
+                let element = new DynamicFloatArrayElementInfo();
+                element.offset = (curCount+i) * this.stride;
 
-                this._allEntries[i] = el;
-                this._freeEntries[i] = el;
+                this._allEntries.push(element);
+                this._freeEntries[addedCount - i - 1] = element;
             }
 
+            this._firstFree = curCount * this.stride;
             this.buffer = newBuffer;
         }