Bladeren bron

Canvas2D: major changes, one evol in DynamicFloatArray and in Engine

Engine.setAlphaMode() now takes an optional parameter to not change the DepthWrite setting.

DynamicFloatArray: there's now a sort() method to sort the content of the array based on a value of an Element

Now supporting Opaque/AlphaTest/Transparency rendering of primitives

Many little bugs fixed
nockawa 9 jaren geleden
bovenliggende
commit
37b8ab35e0

+ 4 - 6
src/Canvas2d/babylon.canvas2d.ts

@@ -146,7 +146,7 @@
 
             this.setupGroup2D(this, null, name, Vector2.Zero(), origin, size, isVisible, this._cachingStrategy===Canvas2D.CACHESTRATEGY_ALLGROUPS ? Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE : Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY, marginTop, marginLeft, marginRight, marginBottom, hAlign, vAlign);
 
-            this._hierarchyLevelMaxSiblingCount = 100;
+            this._hierarchyLevelMaxSiblingCount = 10;
             this._hierarchyDepthOffset = 0;
             this._siblingDepthOffset = 1 / this._hierarchyLevelMaxSiblingCount;
             this._scene = scene;
@@ -816,8 +816,7 @@
                 }
             }
 
-            var context = new Render2DContext();
-            context.forceRefreshPrimitive = false;
+            var context = new PreapreRender2DContext();
 
             ++this._globalTransformProcessStep;
             this.updateGlobalTransVis(false);
@@ -839,12 +838,11 @@
                 this._updateOverStatus();   // TODO this._primPointerInfo may not be up to date!
             }
 
-            var context = new Render2DContext();
-            this._groupRender(context);
+            this._groupRender();
 
             // If the canvas is cached at canvas level, we must manually render the sprite that will display its content
             if (this._cachingStrategy === Canvas2D.CACHESTRATEGY_CANVAS && this._cachedCanvasGroup) {
-                this._cachedCanvasGroup._renderCachedCanvas(context);
+                this._cachedCanvasGroup._renderCachedCanvas();
             }
         }
 

+ 90 - 54
src/Canvas2d/babylon.ellipse2d.ts

@@ -1,28 +1,35 @@
 module BABYLON {
     export class Ellipse2DRenderCache extends ModelRenderCache {
-        fillVB: WebGLBuffer;
-        fillIB: WebGLBuffer;
-        fillIndicesCount: number;
-        instancingFillAttributes: InstancingAttributeInfo[];
-        effectFill: Effect;
-
-        borderVB: WebGLBuffer;
-        borderIB: WebGLBuffer;
-        borderIndicesCount: number;
-        instancingBorderAttributes: InstancingAttributeInfo[];
-        effectBorder: Effect;
-
-        constructor(engine: Engine, modelKey: string, isTransparent: boolean) {
-            super(engine, modelKey, isTransparent);
+        effectsReady: boolean                               = false;
+        fillVB: WebGLBuffer                                 = null;
+        fillIB: WebGLBuffer                                 = null;
+        fillIndicesCount: number                            = 0;
+        instancingFillAttributes: InstancingAttributeInfo[] = null;
+        effectFillInstanced: Effect                         = null;
+        effectFill: Effect                                  = null;
+
+        borderVB: WebGLBuffer                                 = null;
+        borderIB: WebGLBuffer                                 = null;
+        borderIndicesCount: number                            = 0;
+        instancingBorderAttributes: InstancingAttributeInfo[] = null;
+        effectBorderInstanced: Effect                         = null;
+        effectBorder: Effect                                  = null;
+
+        constructor(engine: Engine, modelKey: string) {
+            super(engine, modelKey);
         }
 
         render(instanceInfo: GroupInstanceInfo, context: Render2DContext): boolean {
             // Do nothing if the shader is still loading/preparing 
-            if ((this.effectFill && !this.effectFill.isReady()) || (this.effectBorder && !this.effectBorder.isReady())) {
-                return false;
+            if (!this.effectsReady) {
+                if ((this.effectFill && (!this.effectFill.isReady() || (this.effectFillInstanced && !this.effectFillInstanced.isReady()))) ||
+                    (this.effectBorder && (!this.effectBorder.isReady() || (this.effectBorderInstanced && !this.effectBorderInstanced.isReady())))) {
+                    return false;
+                }
+                this.effectsReady = true;
             }
 
-            var engine = instanceInfo._owner.owner.engine;
+            var engine = instanceInfo.owner.owner.engine;
 
             let depthFunction = 0;
             if (this.effectFill && this.effectBorder) {
@@ -30,60 +37,65 @@
                 engine.setDepthFunctionToLessOrEqual();
             }
 
-            var cur: number;
-            if (this.isTransparent) {
-                cur = engine.getAlphaMode();
-                engine.setAlphaMode(Engine.ALPHA_COMBINE);
-            }
+            let curAlphaMode = engine.getAlphaMode();
 
             if (this.effectFill) {
-                let partIndex = instanceInfo._partIndexFromId.get(Shape2D.SHAPE2D_FILLPARTID.toString());
+                let partIndex = instanceInfo.partIndexFromId.get(Shape2D.SHAPE2D_FILLPARTID.toString());
+                let pid = context.groupInfoPartData[partIndex];
+
+                if (context.renderMode !== Render2DContext.RenderModeOpaque) {
+                    engine.setAlphaMode(Engine.ALPHA_COMBINE);
+                }
 
-                engine.enableEffect(this.effectFill);
-                engine.bindBuffers(this.fillVB, this.fillIB, [1], 4, this.effectFill);
-                let count = instanceInfo._instancesPartsData[partIndex].usedElementCount;
-                if (instanceInfo._owner.owner.supportInstancedArray) {
+                let effect = context.useInstancing ? this.effectFillInstanced : this.effectFill;
+
+                engine.enableEffect(effect);
+                engine.bindBuffers(this.fillVB, this.fillIB, [1], 4, effect);
+                if (context.useInstancing) {
                     if (!this.instancingFillAttributes) {
-                        // Compute the offset locations of the attributes in the vertex shader that will be mapped to the instance buffer data
-                        this.instancingFillAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_FILLPARTID, this.effectFill);
+                        this.instancingFillAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_FILLPARTID, effect);
                     }
 
-                    engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], null, this.instancingFillAttributes);
-                    engine.draw(true, 0, this.fillIndicesCount, count);
-                    engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], this.instancingFillAttributes);
+                    engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingFillAttributes);
+                    engine.draw(true, 0, this.fillIndicesCount, pid._partData.usedElementCount);
+                    engine.unBindInstancesBuffer(pid._partBuffer, this.instancingFillAttributes);
                 } else {
-                    for (let i = 0; i < count; i++) {
-                        this.setupUniforms(this.effectFill, partIndex, instanceInfo._instancesPartsData[partIndex], i);
+                    for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) {
+                        this.setupUniforms(effect, partIndex, pid._partData, i);
                         engine.draw(true, 0, this.fillIndicesCount);                        
                     }
                 }
             }
 
             if (this.effectBorder) {
-                let partIndex = instanceInfo._partIndexFromId.get(Shape2D.SHAPE2D_BORDERPARTID.toString());
+                let partIndex = instanceInfo.partIndexFromId.get(Shape2D.SHAPE2D_BORDERPARTID.toString());
+                let pid = context.groupInfoPartData[partIndex];
+
+                if (context.renderMode !== Render2DContext.RenderModeOpaque) {
+                    engine.setAlphaMode(Engine.ALPHA_COMBINE);
+                }
+
+                let effect = context.useInstancing ? this.effectBorderInstanced : this.effectBorder;
 
-                engine.enableEffect(this.effectBorder);
-                engine.bindBuffers(this.borderVB, this.borderIB, [1], 4, this.effectBorder);
-                let count = instanceInfo._instancesPartsData[partIndex].usedElementCount;
-                if (instanceInfo._owner.owner.supportInstancedArray) {
+                engine.enableEffect(effect);
+                engine.bindBuffers(this.borderVB, this.borderIB, [1], 4, effect);
+                if (context.useInstancing) {
                     if (!this.instancingBorderAttributes) {
-                        this.instancingBorderAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_BORDERPARTID, this.effectBorder);
+                        this.instancingBorderAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_BORDERPARTID, effect);
                     }
 
-                    engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], null, this.instancingBorderAttributes);
-                    engine.draw(true, 0, this.borderIndicesCount, count);
-                    engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], this.instancingBorderAttributes);
+                    engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingBorderAttributes);
+                    engine.draw(true, 0, this.borderIndicesCount, pid._partData.usedElementCount);
+                    engine.unBindInstancesBuffer(pid._partBuffer, this.instancingBorderAttributes);
                 } else {
-                    for (let i = 0; i < count; i++) {
-                        this.setupUniforms(this.effectBorder, partIndex, instanceInfo._instancesPartsData[partIndex], i);
+                    for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) {
+                        this.setupUniforms(this.effectBorder, partIndex, pid._partData, i);
                         engine.draw(true, 0, this.borderIndicesCount);
                     }
                 }
             }
 
-            if (this.isTransparent) {
-                engine.setAlphaMode(cur);
-            }
+            engine.setAlphaMode(curAlphaMode);
 
             if (this.effectFill && this.effectBorder) {
                 engine.setDepthFunction(depthFunction);
@@ -111,6 +123,11 @@
                 this.effectFill = null;
             }
 
+            if (this.effectFillInstanced) {
+                this._engine._releaseEffect(this.effectFillInstanced);
+                this.effectFillInstanced = null;
+            }
+
             if (this.borderVB) {
                 this._engine._releaseBuffer(this.borderVB);
                 this.borderVB = null;
@@ -126,6 +143,11 @@
                 this.effectBorder = null;
             }
 
+            if (this.effectBorderInstanced) {
+                this._engine._releaseEffect(this.effectBorderInstanced);
+                this.effectBorderInstanced = null;
+            }
+
             return true;
         }
     }
@@ -213,7 +235,7 @@
                 ellipse.setupEllipse2D(parent.owner, parent, null, Vector2.Zero(), null, new Size(10, 10), 64, Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF"), null, 1, true, null, null, null, null, null, null);
             } else {
                 let fill: IBrush2D;
-                if (!options.fill) {
+                if (options.fill === undefined) {
                     fill = Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
                 } else {
                     fill = options.fill;
@@ -227,8 +249,8 @@
             return ellipse;
         }
 
-        protected createModelRenderCache(modelKey: string, isTransparent: boolean): ModelRenderCache {
-            let renderCache = new Ellipse2DRenderCache(this.owner.engine, modelKey, isTransparent);
+        protected createModelRenderCache(modelKey: string): ModelRenderCache {
+            let renderCache = new Ellipse2DRenderCache(this.owner.engine, modelKey);
             return renderCache;
         }
 
@@ -257,7 +279,14 @@
                 renderCache.fillIB = engine.createIndexBuffer(ib);
                 renderCache.fillIndicesCount = triCount * 3;
 
-                let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_FILLPARTID, ["index"]);
+                // Get the instanced version of the effect, if the engine does not support it, null is return and we'll only draw on by one
+                let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_FILLPARTID, ["index"], true);
+                if (ei) {
+                    renderCache.effectFillInstanced = engine.createEffect({ vertex: "ellipse2d", fragment: "ellipse2d" }, ei.attributes, ei.uniforms, [], ei.defines, null);
+                }
+
+                // Get the non instanced version
+                ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_FILLPARTID, ["index"], false);
                 renderCache.effectFill = engine.createEffect({ vertex: "ellipse2d", fragment: "ellipse2d" }, ei.attributes, ei.uniforms, [], ei.defines, null);
             }
 
@@ -289,8 +318,15 @@
                 renderCache.borderIB = engine.createIndexBuffer(ib);
                 renderCache.borderIndicesCount = (triCount* 3);
 
-                let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_BORDERPARTID, ["index"]);
-                renderCache.effectBorder = engine.createEffect({ vertex: "ellipse2d", fragment: "ellipse2d" }, ei.attributes, ei.uniforms, [], ei.defines, null);
+                // Get the instanced version of the effect, if the engine does not support it, null is return and we'll only draw on by one
+                let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_BORDERPARTID, ["index"], true);
+                if (ei) {
+                    renderCache.effectBorderInstanced = engine.createEffect("ellipse2d", ei.attributes, ei.uniforms, [], ei.defines, null);
+                }
+
+                // Get the non instanced version
+                ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_BORDERPARTID, ["index"], false);
+                renderCache.effectBorder = engine.createEffect("ellipse2d", ei.attributes, ei.uniforms, [], ei.defines, null);
             }
 
             return renderCache;

+ 401 - 102
src/Canvas2d/babylon.group2d.ts

@@ -27,9 +27,6 @@
          */
         constructor() {
             super();
-            this._primDirtyList = new Array<Prim2DBase>();
-            this._childrenRenderableGroups = new Array<Group2D>();
-            this._renderGroupInstancesInfo = new StringDictionary<GroupInstanceInfo>();
         }
 
         /**
@@ -76,15 +73,15 @@
             this._bindCacheTarget();
 
             var uv = vertexData.uvs;
-            let nodeuv = this._cacheNode.UVs;
+            let nodeuv = this._renderableData._cacheNode.UVs;
             for (let i = 0; i < 4; i++) {
                 uv[i * 2 + 0] = nodeuv[i].x;
                 uv[i * 2 + 1] = nodeuv[i].y;
             }
 
-            material.diffuseTexture = this._cacheTexture;
+            material.diffuseTexture = this._renderableData._cacheTexture;
             material.emissiveColor = new Color3(1, 1, 1);
-            this._cacheTexture.hasAlpha = true;
+            this._renderableData._cacheTexture.hasAlpha = true;
             this._unbindCacheTarget();
         }
 
@@ -96,27 +93,9 @@
                 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._renderGroupInstancesInfo) {
-                this._renderGroupInstancesInfo.forEach((k, v) => {
-                    v.dispose();
-                });
-                this._renderGroupInstancesInfo = null;
+            if (this._renderableData) {
+                this._renderableData.dispose();
+                this._renderableData = null;
             }
 
             return true;
@@ -199,13 +178,14 @@
         }
 
         public _addPrimToDirtyList(prim: Prim2DBase) {
-            this._primDirtyList.push(prim);
+            this._renderableData._primDirtyList.push(prim);
         }
 
-        public _renderCachedCanvas(context: Render2DContext) {
+        public _renderCachedCanvas() {
             this.updateGlobalTransVis(true);
+            let context = new PreapreRender2DContext();
             this._prepareGroupRender(context);
-            this._groupRender(context);
+            this._groupRender();
         }
 
         protected levelIntersect(intersectInfo: IntersectInfo2D): boolean {
@@ -229,12 +209,12 @@
         }
 
         // Method called only on renderable groups to prepare the rendering
-        protected _prepareGroupRender(context: Render2DContext) {
+        protected _prepareGroupRender(context: PreapreRender2DContext) {
             let sortedDirtyList: Prim2DBase[] = null;
 
             // Update the Global Transformation and visibility status of the changed primitives
-            if ((this._primDirtyList.length > 0) || context.forceRefreshPrimitive) {
-                sortedDirtyList = this._primDirtyList.sort((a, b) => a.hierarchyDepth - b.hierarchyDepth);
+            if ((this._renderableData._primDirtyList.length > 0) || context.forceRefreshPrimitive) {
+                sortedDirtyList = this._renderableData._primDirtyList.sort((a, b) => a.hierarchyDepth - b.hierarchyDepth);
                 this.updateGlobalTransVisOf(sortedDirtyList, true);
             }
 
@@ -276,7 +256,7 @@
                 this._viewportSize = newSize;
             }
 
-            if ((this._primDirtyList.length > 0) || context.forceRefreshPrimitive) {
+            if ((this._renderableData._primDirtyList.length > 0) || context.forceRefreshPrimitive) {
                 // If the group is cached, set the dirty flag to true because of the incoming changes
                 this._cacheGroupDirty = this._isCachedGroup;
 
@@ -289,7 +269,7 @@
                     // Each primitive that changed at least once was added into the primDirtyList, we have to sort this level using
                     //  the hierarchyDepth in order to prepare primitives from top to bottom
                     if (!sortedDirtyList) {
-                        sortedDirtyList = this._primDirtyList.sort((a, b) => a.hierarchyDepth - b.hierarchyDepth);
+                        sortedDirtyList = this._renderableData._primDirtyList.sort((a, b) => a.hierarchyDepth - b.hierarchyDepth);
                     }
 
                     sortedDirtyList.forEach(p => {
@@ -302,24 +282,24 @@
                     });
 
                     // Everything is updated, clear the dirty list
-                    this._primDirtyList.forEach(p => p._resetPropertiesDirty());
-                    this._primDirtyList.splice(0);
+                    this._renderableData._primDirtyList.forEach(p => p._resetPropertiesDirty());
+                    this._renderableData._primDirtyList.splice(0);
                 }
             }
 
             // A renderable group has a list of direct children that are also renderable groups, we recurse on them to also prepare them
-            this._childrenRenderableGroups.forEach(g => {
+            this._renderableData._childrenRenderableGroups.forEach(g => {
                 g._prepareGroupRender(context);
             });
         }
 
-        protected _groupRender(context: Render2DContext) {
+        protected _groupRender() {
             let engine = this.owner.engine;
             let failedCount = 0;
 
             // First recurse to children render group to render them (in their cache or on screen)
-            for (let childGroup of this._childrenRenderableGroups) {
-                childGroup._groupRender(context);
+            for (let childGroup of this._renderableData._childrenRenderableGroups) {
+                childGroup._groupRender();
             }
 
             // Render the primitives if needed: either if we don't cache the content or if the content is cached but has changed
@@ -330,54 +310,83 @@
                     var curVP = engine.setDirectViewport(this._viewportPosition.x, this._viewportPosition.y, this._viewportSize.width, this._viewportSize.height);
                 }
 
+                // ===================================================================
+                // First pass, update the InstancedArray and render Opaque primitives
+
+                // Disable Alpha Testing, Enable Depth Write
+                engine.setAlphaTesting(false);
+                engine.setDepthWrite(true);
+
                 // For each different model of primitive to render
-                let totalRenderCount = 0;
-                this._renderGroupInstancesInfo.forEach((k, v) => {
+                let context = new Render2DContext(Render2DContext.RenderModeOpaque);
+                this._renderableData._renderGroupInstancesInfo.forEach((k, v) => {
 
-                    // This part will pack the dynamicfloatarray 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 = false;
-
-                                // 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;
+                    // Prepare the context object, update the WebGL Instanced Array buffer if needed
+                    let renderCount = this._prepareContext(engine, context, v);
+
+                    // If null is returned, there's no opaque data to render
+                    if (renderCount === null) {
+                        return;
                     }
 
                     // Submit render only if we have something to render (everything may be hidden and the floatarray empty)
-                    if (!this.owner.supportInstancedArray || totalRenderCount > 0) {
+                    if (!this.owner.supportInstancedArray || renderCount > 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);
+                        let renderFailed = !v.modelRenderCache.render(v, context);
 
                         // Update dirty flag/related
-                        v._dirtyInstancesData = renderFailed;
+                        v.opaqueDirty = renderFailed;
                         failedCount += renderFailed ? 1 : 0;
                     }
                 });
 
+                // =======================================================================
+                // Second pass, update the InstancedArray and render AlphaTest primitives
+
+                // Enable Alpha Testing, Enable Depth Write
+                engine.setAlphaTesting(true);
+                engine.setDepthWrite(true);
+
+                // For each different model of primitive to render
+                context = new Render2DContext(Render2DContext.RenderModeAlphaTest);
+                this._renderableData._renderGroupInstancesInfo.forEach((k, v) => {
+
+                    // Prepare the context object, update the WebGL Instanced Array buffer if needed
+                    let renderCount = this._prepareContext(engine, context, v);
+
+                    // If null is returned, there's no opaque data to render
+                    if (renderCount === null) {
+                        return;
+                    }
+
+                    // Submit render only if we have something to render (everything may be hidden and the floatarray empty)
+                    if (!this.owner.supportInstancedArray || renderCount > 0) {
+                        // render all the instances of this model, if the render method returns true then our instances are no longer dirty
+                        let renderFailed = !v.modelRenderCache.render(v, context);
+
+                        // Update dirty flag/related
+                        v.opaqueDirty = renderFailed;
+                        failedCount += renderFailed ? 1 : 0;
+                    }
+                });
+
+                // =======================================================================
+                // Third pass, transparent primitive rendering
+
+                // Enable Alpha Testing, Disable Depth Write
+                engine.setAlphaTesting(true);
+                engine.setDepthWrite(false);
+
+                // First Check if the transparent List change so we can update the TransparentSegment and PartData (sort if needed)
+                if (this._renderableData._transparentListChanged) {
+                    this._updateTransparentData();
+                }
+
+                // From this point on we have up to date data to render, so let's go
+                failedCount += this._renderTransparentData();
+
+                // =======================================================================
+                //  Unbind target/restore viewport setting, clear dirty flag, and quit
                 // The group's content is no longer dirty
                 this._cacheGroupDirty = failedCount !== 0;
 
@@ -391,12 +400,204 @@
             }
         }
 
+        private _updateTransparentData() {
+            let rd = this._renderableData;
+
+            // If null, there was no change of ZOrder, we have nothing to do
+            if (rd._firstChangedPrim === null) {
+                return;
+            }
+
+            // Sort all the primitive from their depth, max (bottom) to min (top)
+            rd._transparentPrimitives.sort((a, b) => b._primitive.getActualZOffset() - a._primitive.getActualZOffset());
+
+            let checkAndAddPrimInSegment = (seg: TransparentSegment, tpiI: number): boolean => {
+                let tpi = rd._transparentPrimitives[tpiI];
+
+                // Fast rejection: if gii are different
+                if (seg.groupInsanceInfo !== tpi._groupInstanceInfo) {
+                    return false;
+                }
+
+                let tpiZ = tpi._primitive.getActualZOffset();
+
+                // We've made it so far, the tpi can be part of the segment, add it
+                tpi._transparentSegment = seg;
+
+                // Check if we have to update endZ, a smaller value means one above the current one
+                if (tpiZ < seg.endZ) {
+                    seg.endZ = tpiZ;
+                    seg.endDataIndex = tpi._primitive._getLastIndexInDataBuffer() + 1; // Still exclusive
+                }
+
+                return true;
+            }
+
+            rd._transparentSegments.splice(0);
+            let prevSeg = null;
+
+            for (let tpiI = 0; tpiI < rd._transparentPrimitives.length; tpiI++) {
+                let tpi = rd._transparentPrimitives[tpiI];
+
+                // Check if the Data in which the primitive is stored is not sorted properly
+                if (tpi._groupInstanceInfo.transparentOrderDirty) {
+                    tpi._groupInstanceInfo.sortTransparentData();
+                }
+
+                // Reset the segment, we have to create/rebuild it
+                tpi._transparentSegment = null;                
+
+                // If there's a previous valid segment, check if this prim can be part of it
+                if (prevSeg) {
+                    checkAndAddPrimInSegment(prevSeg, tpiI);
+                }
+
+                // If we couldn't insert in the adjacent segments, he have to create one
+                if (!tpi._transparentSegment) {
+                    let ts = new TransparentSegment();
+                    ts.groupInsanceInfo = tpi._groupInstanceInfo;
+                    let prim = tpi._primitive;
+                    ts.startZ = prim.getActualZOffset();
+                    ts.startDataIndex = prim._getFirstIndexInDataBuffer();
+                    ts.endDataIndex = prim._getLastIndexInDataBuffer() + 1; // Make it exclusive, more natural to use in a for loop
+                    ts.endZ = ts.startZ;
+                    tpi._transparentSegment = ts;
+                    rd._transparentSegments.push(ts);
+                }
+                // Update prevSeg
+                prevSeg = tpi._transparentSegment;
+            }
+
+            rd._firstChangedPrim = null;
+            rd._transparentListChanged = false;
+        }
+
+        private _renderTransparentData(): number {
+            let failedCount = 0;
+            let context = new Render2DContext(Render2DContext.RenderModeTransparent);
+            let rd = this._renderableData;
+
+            let length = rd._transparentSegments.length;
+            for (let i = 0; i < length; i++) {
+                let ts = rd._transparentSegments[i];
+
+
+                let gii = ts.groupInsanceInfo;
+                let mrc = gii.modelRenderCache;
+
+                context.useInstancing = false;
+                context.partDataStartIndex = ts.startDataIndex;
+                context.partDataEndIndex = ts.endDataIndex;
+                context.groupInfoPartData = gii.transparentData;
+
+                let renderFailed = !mrc.render(gii, context);
+
+                failedCount += renderFailed ? 1 : 0;
+            }
+
+            return failedCount;
+        }
+
+        private _prepareContext(engine: Engine, context: Render2DContext, gii: GroupInstanceInfo): number {
+            let gipd: GroupInfoPartData[] = null;
+            let setDirty: (dirty: boolean) => void;
+            let getDirty: () => boolean;
+
+            // Render Mode specifics
+            switch (context.renderMode) {
+                case Render2DContext.RenderModeOpaque:
+                {
+                    if (!gii.hasOpaqueData) {
+                        return null;
+                    }
+                    setDirty = (dirty: boolean) => { gii.opaqueDirty = dirty; };
+                    getDirty = () => gii.opaqueDirty;
+                    context.groupInfoPartData = gii.opaqueData;
+                    gipd = gii.opaqueData;
+                    break;
+                }
+                case Render2DContext.RenderModeAlphaTest:
+                {
+                    if (!gii.hasAlphaTestData) {
+                        return null;
+                    }
+                    setDirty = (dirty: boolean) => { gii.alphaTestDirty = dirty; };
+                    getDirty = () => gii.alphaTestDirty;
+                    context.groupInfoPartData = gii.alphaTestData;
+                    gipd = gii.alphaTestData;
+                    break;
+                }
+                default:
+                    throw new Error("_prepareContext is only for opaque or alphaTest");
+            }
+
+
+            let renderCount = 0;
+
+            // This part will pack the dynamicfloatarray and update the instanced array WebGLBufffer
+            // Skip it if instanced arrays are not supported
+            if (this.owner.supportInstancedArray) {
+
+                // Flag for instancing
+                context.useInstancing = true;
+
+                // Make sure all the WebGLBuffers of the Instanced Array are created/up to date for the parts to render.
+                for (let i = 0; i < gipd.length; i++) {
+                    let pid = gipd[i];
+
+                    // If the instances of the model was changed, pack the data
+                    let array = pid._partData;
+                    let instanceData = array.pack();
+                    renderCount += 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 (!pid._partBuffer || (pid._partBufferSize < neededSize)) {
+                        if (pid._partBuffer) {
+                            engine.deleteInstancesBuffer(pid._partBuffer);
+                        }
+                        pid._partBuffer = engine.createInstancesBuffer(neededSize);
+                        pid._partBufferSize = neededSize;
+                        setDirty(false);
+
+                        // Update the WebGL buffer to match the new content of the instances data
+                        engine._gl.bufferSubData(engine._gl.ARRAY_BUFFER, 0, instanceData);
+                    } else if (getDirty()) {
+                        // Update the WebGL buffer to match the new content of the instances data
+                        engine._gl.bindBuffer(engine._gl.ARRAY_BUFFER, pid._partBuffer);
+                        engine._gl.bufferSubData(engine._gl.ARRAY_BUFFER, 0, instanceData);
+
+                    }
+                }
+                setDirty(false);
+            }
+
+
+            // Can't rely on hardware instancing, use the DynamicFloatArray instance, render its whole content
+            else {
+                context.partDataStartIndex = 0;
+
+                // Find the first valid object to get the count
+                let i = 0;
+                while (!context.groupInfoPartData[i]) {
+                    i++;
+                }
+
+                context.partDataEndIndex = context.groupInfoPartData[i]._partData.usedElementCount;
+            }
+
+            return renderCount;
+        }
+
         private _bindCacheTarget() {
             let curWidth: number;
             let curHeight: number;
+            let rd = this._renderableData;
 
-            if (this._cacheNode) {
-                let size = this._cacheNode.contentSize;
+            if (rd._cacheNode) {
+                let size = rd._cacheNode.contentSize;
                 let groupWidth = Math.ceil(this.actualSize.width);
                 let groupHeight = Math.ceil(this.actualSize.height);
 
@@ -404,48 +605,50 @@
                     curWidth = Math.floor(size.width * 1.07);    // Grow 5% more to avoid frequent resizing for few pixels...
                     curHeight = Math.floor(size.height * 1.07);
                     //console.log(`[${this._globalTransformProcessStep}] Resize group ${this.id}, width: ${curWidth}, height: ${curHeight}`);
-                    this._cacheTexture.freeRect(this._cacheNode);
-                    this._cacheNode = null;
+                    rd._cacheTexture.freeRect(rd._cacheNode);
+                    rd._cacheNode = null;
                 }
             }
 
-            if (!this._cacheNode) {
+            if (!rd._cacheNode) {
                 // Check if we have to allocate a rendering zone in the global cache texture
                 var res = this.owner._allocateGroupCache(this, this.renderGroup, curWidth ? new Size(curWidth, curHeight) : null);
-                this._cacheNode = res.node;
-                this._cacheTexture = res.texture;
-                this._cacheRenderSprite = res.sprite;
-                let size = this._cacheNode.contentSize;
+                rd._cacheNode = res.node;
+                rd._cacheTexture = res.texture;
+                rd._cacheRenderSprite = res.sprite;
+                let size = rd._cacheNode.contentSize;
             }
 
-            let n = this._cacheNode;
-            this._cacheTexture.bindTextureForPosSize(n.pos, this.actualSize, true);
+            let n = rd._cacheNode;
+            rd._cacheTexture.bindTextureForPosSize(n.pos, this.actualSize, true);
         }
 
         private _unbindCacheTarget() {
-            if (this._cacheTexture) {
-                this._cacheTexture.unbindTexture();
+            if (this._renderableData._cacheTexture) {
+                this._renderableData._cacheTexture.unbindTexture();
             }
         }
 
         protected handleGroupChanged(prop: Prim2DPropInfo) {
             // This method is only for cachedGroup
-            if (!this.isCachedGroup || !this._cacheRenderSprite) {
+            let rd = this._renderableData;
+
+            if (!this.isCachedGroup || !rd._cacheRenderSprite) {
                 return;
             }
 
             // For now we only support these property changes
             // TODO: add more! :)
             if (prop.id === Prim2DBase.positionProperty.id) {
-                this._cacheRenderSprite.position = this.position.clone();
+                rd._cacheRenderSprite.position = this.position.clone();
             } else if (prop.id === Prim2DBase.rotationProperty.id) {
-                this._cacheRenderSprite.rotation = this.rotation;
+                rd._cacheRenderSprite.rotation = this.rotation;
             } else if (prop.id === Prim2DBase.scaleProperty.id) {
-                this._cacheRenderSprite.scale = this.scale;
+                rd._cacheRenderSprite.scale = this.scale;
             } else if (prop.id === Prim2DBase.originProperty.id) {
-                this._cacheRenderSprite.origin = this.origin.clone();
+                rd._cacheRenderSprite.origin = this.origin.clone();
             } else if (prop.id === Group2D.actualSizeProperty.id) {
-                this._cacheRenderSprite.spriteSize = this.actualSize.clone();
+                rd._cacheRenderSprite.spriteSize = this.actualSize.clone();
                 //console.log(`[${this._globalTransformProcessStep}] Sync Sprite ${this.id}, width: ${this.actualSize.width}, height: ${this.actualSize.height}`);
             }
         }
@@ -501,12 +704,17 @@
                 }
             }
 
+
+            if (this._isRenderableGroup) {
+                this._renderableData = new RenderableGroupData();
+            }
+
             // If the group is tagged as renderable we add it to the renderable tree
             if (this._isCachedGroup) {
                 let cur = this.parent;
                 while (cur) {
                     if (cur instanceof Group2D && cur._isRenderableGroup) {
-                        cur._childrenRenderableGroups.push(this);
+                        cur._renderableData._childrenRenderableGroups.push(this);
                         break;
                     }
                     cur = cur.parent;
@@ -517,18 +725,109 @@
         protected _isRenderableGroup: boolean;
         protected _isCachedGroup: boolean;
         private _cacheGroupDirty: boolean;
-        protected _childrenRenderableGroups: Array<Group2D>;
         private _size: Size;
         private _actualSize: Size;
         private _cacheBehavior: number;
-        private _primDirtyList: Array<Prim2DBase>;
-        private _cacheNode: PackedRect;
-        private _cacheTexture: MapTexture;
-        private _cacheRenderSprite: Sprite2D;
         private _viewportPosition: Vector2;
         private _viewportSize: Size;
 
+        public _renderableData: RenderableGroupData;
+
+    }
+
+    export class RenderableGroupData {
+        constructor() {
+            this._primDirtyList = new Array<Prim2DBase>();
+            this._childrenRenderableGroups = new Array<Group2D>();
+            this._renderGroupInstancesInfo = new StringDictionary<GroupInstanceInfo>();
+            this._transparentPrimitives = new Array<TransparentPrimitiveInfo>();
+            this._transparentSegments = new Array<TransparentSegment>();
+            this._firstChangedPrim = null;
+            this._transparentListChanged = false;
+        }
+
+        dispose() {
+            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._renderGroupInstancesInfo) {
+                this._renderGroupInstancesInfo.forEach((k, v) => {
+                    v.dispose();
+                });
+                this._renderGroupInstancesInfo = null;
+            }
+        }
+
+        addNewTransparentPrimitiveInfo(prim: RenderablePrim2D, gii: GroupInstanceInfo): TransparentPrimitiveInfo {
+            let tpi = new TransparentPrimitiveInfo();
+            tpi._primitive = prim;
+            tpi._groupInstanceInfo = gii;
+            tpi._transparentSegment = null;
+
+            this._transparentPrimitives.push(tpi);
+            this._transparentListChanged = true;
+
+            this.updateSmallestZChangedPrim(tpi);
+
+            return tpi;
+        }
+
+        removeTransparentPrimitiveInfo(tpi: TransparentPrimitiveInfo) {
+            let index = this._transparentPrimitives.indexOf(tpi);
+            if (index !== -1) {
+                this._transparentPrimitives.splice(index, 1);
+                this._transparentListChanged = true;
+
+                this.updateSmallestZChangedPrim(tpi);
+            }
+        }
+
+        transparentPrimitiveZChanged(tpi: TransparentPrimitiveInfo) {
+            this._transparentListChanged = true;
+            this.updateSmallestZChangedPrim(tpi);
+        }
+
+        updateSmallestZChangedPrim(tpi: TransparentPrimitiveInfo) {
+            if (tpi._primitive) {
+                let newZ = tpi._primitive.getActualZOffset();
+                let curZ = this._firstChangedPrim ? this._firstChangedPrim._primitive.getActualZOffset() : Number.MIN_VALUE;
+                if (newZ > curZ) {
+                    this._firstChangedPrim = tpi;
+                }
+            }
+        }
+
+        _primDirtyList: Array<Prim2DBase>;
+        _childrenRenderableGroups: Array<Group2D>;
         _renderGroupInstancesInfo: StringDictionary<GroupInstanceInfo>;
+        
+        _cacheNode: PackedRect;
+        _cacheTexture: MapTexture;
+        _cacheRenderSprite: Sprite2D;
+
+        _transparentListChanged: boolean;
+        _transparentPrimitives: Array<TransparentPrimitiveInfo>;
+        _transparentSegments: Array<TransparentSegment>;
+        _firstChangedPrim: TransparentPrimitiveInfo;
+    }
+
+    export class TransparentPrimitiveInfo {
+        _primitive: RenderablePrim2D;
+        _groupInstanceInfo: GroupInstanceInfo;
+        _transparentSegment: TransparentSegment;
     }
 
 }

+ 90 - 54
src/Canvas2d/babylon.lines2d.ts

@@ -1,28 +1,35 @@
 module BABYLON {
     export class Lines2DRenderCache extends ModelRenderCache {
-        fillVB: WebGLBuffer;
-        fillIB: WebGLBuffer;
-        fillIndicesCount: number;
-        instancingFillAttributes: InstancingAttributeInfo[];
-        effectFill: Effect;
-
-        borderVB: WebGLBuffer;
-        borderIB: WebGLBuffer;
-        borderIndicesCount: number;
-        instancingBorderAttributes: InstancingAttributeInfo[];
-        effectBorder: Effect;
-
-        constructor(engine: Engine, modelKey: string, isTransparent: boolean) {
-            super(engine, modelKey, isTransparent);
+        effectsReady: boolean                               = false;
+        fillVB: WebGLBuffer                                 = null;
+        fillIB: WebGLBuffer                                 = null;
+        fillIndicesCount: number                            = 0;
+        instancingFillAttributes: InstancingAttributeInfo[] = null;
+        effectFill: Effect                                  = null;
+        effectFillInstanced: Effect                         = null;
+
+        borderVB: WebGLBuffer                                 = null;
+        borderIB: WebGLBuffer                                 = null;
+        borderIndicesCount: number                            = 0;
+        instancingBorderAttributes: InstancingAttributeInfo[] = null;
+        effectBorder: Effect                                  = null;
+        effectBorderInstanced: Effect                         = null;
+
+        constructor(engine: Engine, modelKey: string) {
+            super(engine, modelKey);
         }
 
         render(instanceInfo: GroupInstanceInfo, context: Render2DContext): boolean {
             // Do nothing if the shader is still loading/preparing 
-            if ((this.effectFill && !this.effectFill.isReady()) || (this.effectBorder && !this.effectBorder.isReady())) {
-                return false;
+            if (!this.effectsReady) {
+                if ((this.effectFill && (!this.effectFill.isReady() || (this.effectFillInstanced && !this.effectFillInstanced.isReady()))) ||
+                    (this.effectBorder && (!this.effectBorder.isReady() || (this.effectBorderInstanced && !this.effectBorderInstanced.isReady())))) {
+                    return false;
+                }
+                this.effectsReady = true;
             }
 
-            var engine = instanceInfo._owner.owner.engine;
+            var engine = instanceInfo.owner.owner.engine;
 
             let depthFunction = 0;
             if (this.effectFill && this.effectBorder) {
@@ -30,60 +37,65 @@
                 engine.setDepthFunctionToLessOrEqual();
             }
 
-            var cur: number;
-            if (this.isTransparent) {
-                cur = engine.getAlphaMode();
-                engine.setAlphaMode(Engine.ALPHA_COMBINE);
-            }
+            let curAlphaMode = engine.getAlphaMode();
 
             if (this.effectFill) {
-                let partIndex = instanceInfo._partIndexFromId.get(Shape2D.SHAPE2D_FILLPARTID.toString());
+                let partIndex = instanceInfo.partIndexFromId.get(Shape2D.SHAPE2D_FILLPARTID.toString());
+                let pid = context.groupInfoPartData[partIndex];
              
-                engine.enableEffect(this.effectFill);
-                engine.bindBuffers(this.fillVB, this.fillIB, [2], 2*4, this.effectFill);
-                let count = instanceInfo._instancesPartsData[partIndex].usedElementCount;
-                if (instanceInfo._owner.owner.supportInstancedArray) {
+                if (context.renderMode !== Render2DContext.RenderModeOpaque) {
+                    engine.setAlphaMode(Engine.ALPHA_COMBINE);
+                }
+
+                let effect = context.useInstancing ? this.effectFillInstanced : this.effectFill;
+
+                engine.enableEffect(effect);
+                engine.bindBuffers(this.fillVB, this.fillIB, [2], 2*4, effect);
+                if (context.useInstancing) {
                     if (!this.instancingFillAttributes) {
-                        // Compute the offset locations of the attributes in the vertex shader that will be mapped to the instance buffer data
-                        this.instancingFillAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_FILLPARTID, this.effectFill);
+                        this.instancingFillAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_FILLPARTID, effect);
                     }
 
-                    engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], null, this.instancingFillAttributes);
-                    engine.draw(true, 0, this.fillIndicesCount, count);
-                    engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], this.instancingFillAttributes);
+                    engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingFillAttributes);
+                    engine.draw(true, 0, this.fillIndicesCount, pid._partData.usedElementCount);
+                    engine.unBindInstancesBuffer(pid._partBuffer, this.instancingFillAttributes);
                 } else {
-                    for (let i = 0; i < count; i++) {
-                        this.setupUniforms(this.effectFill, partIndex, instanceInfo._instancesPartsData[partIndex], i);
+                    for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) {
+                        this.setupUniforms(effect, partIndex, pid._partData, i);
                         engine.draw(true, 0, this.fillIndicesCount);                        
                     }
                 }
             }
 
             if (this.effectBorder) {
-                let partIndex = instanceInfo._partIndexFromId.get(Shape2D.SHAPE2D_BORDERPARTID.toString());
+                let partIndex = instanceInfo.partIndexFromId.get(Shape2D.SHAPE2D_BORDERPARTID.toString());
+                let pid = context.groupInfoPartData[partIndex];
+
+                if (context.renderMode !== Render2DContext.RenderModeOpaque) {
+                    engine.setAlphaMode(Engine.ALPHA_COMBINE);
+                }
+
+                let effect = context.useInstancing ? this.effectBorderInstanced : this.effectBorder;
 
-                engine.enableEffect(this.effectBorder);
-                engine.bindBuffers(this.borderVB, this.borderIB, [2], 2 * 4, this.effectBorder);
-                let count = instanceInfo._instancesPartsData[partIndex].usedElementCount;
-                if (instanceInfo._owner.owner.supportInstancedArray) {
+                engine.enableEffect(effect);
+                engine.bindBuffers(this.borderVB, this.borderIB, [2], 2 * 4, effect);
+                if (context.useInstancing) {
                     if (!this.instancingBorderAttributes) {
-                        this.instancingBorderAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_BORDERPARTID, this.effectBorder);
+                        this.instancingBorderAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_BORDERPARTID, effect);
                     }
 
-                    engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], null, this.instancingBorderAttributes);
-                    engine.draw(true, 0, this.borderIndicesCount, count);
-                    engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], this.instancingBorderAttributes);
+                    engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingBorderAttributes);
+                    engine.draw(true, 0, this.borderIndicesCount, pid._partData.usedElementCount);
+                    engine.unBindInstancesBuffer(pid._partBuffer, this.instancingBorderAttributes);
                 } else {
-                    for (let i = 0; i < count; i++) {
-                        this.setupUniforms(this.effectBorder, partIndex, instanceInfo._instancesPartsData[partIndex], i);
+                    for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) {
+                        this.setupUniforms(effect, partIndex, pid._partData, i);
                         engine.draw(true, 0, this.borderIndicesCount);
                     }
                 }
             }
 
-            if (this.isTransparent) {
-                engine.setAlphaMode(cur);
-            }
+            engine.setAlphaMode(curAlphaMode);
 
             if (this.effectFill && this.effectBorder) {
                 engine.setDepthFunction(depthFunction);
@@ -111,6 +123,11 @@
                 this.effectFill = null;
             }
 
+            if (this.effectFillInstanced) {
+                this._engine._releaseEffect(this.effectFillInstanced);
+                this.effectFillInstanced = null;
+            }
+
             if (this.borderVB) {
                 this._engine._releaseBuffer(this.borderVB);
                 this.borderVB = null;
@@ -126,6 +143,11 @@
                 this.effectBorder = null;
             }
 
+            if (this.effectBorderInstanced) {
+                this._engine._releaseEffect(this.effectBorderInstanced);
+                this.effectBorderInstanced = null;
+            }
+
             return true;
         }
     }
@@ -306,7 +328,7 @@
                 lines.setupLines2D(parent.owner, parent, null, Vector2.Zero(), null, points, 1, 0, 0, Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF"), null, 1, false, true, null, null, null, null, null, null);
             } else {
                 let fill: IBrush2D;
-                if (!options.fill) {
+                if (options.fill === undefined) {
                     fill = Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
                 } else {
                     fill = options.fill;
@@ -319,8 +341,8 @@
             return lines;
         }
 
-        protected createModelRenderCache(modelKey: string, isTransparent: boolean): ModelRenderCache {
-            let renderCache = new Lines2DRenderCache(this.owner.engine, modelKey, isTransparent);
+        protected createModelRenderCache(modelKey: string): ModelRenderCache {
+            let renderCache = new Lines2DRenderCache(this.owner.engine, modelKey);
             return renderCache;
         }
 
@@ -872,8 +894,15 @@
                 renderCache.fillIB = engine.createIndexBuffer(ib);
                 renderCache.fillIndicesCount = ib.length;
 
-                let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_FILLPARTID, ["position"]);
-                renderCache.effectFill = engine.createEffect({ vertex: "lines2d", fragment: "lines2d" }, ei.attributes, ei.uniforms, [], ei.defines, null);
+                // Get the instanced version of the effect, if the engine does not support it, null is return and we'll only draw on by one
+                let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_FILLPARTID, ["position"], true);
+                if (ei) {
+                    renderCache.effectFillInstanced = engine.createEffect("lines2d", ei.attributes, ei.uniforms, [], ei.defines, null);
+                }
+
+                // Get the non instanced version
+                ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_FILLPARTID, ["position"], false);
+                renderCache.effectFill = engine.createEffect("lines2d", ei.attributes, ei.uniforms, [], ei.defines, null);
             }
 
             // Need to create WebGL resources for border part?
@@ -917,7 +946,14 @@
                 renderCache.borderIB = engine.createIndexBuffer(ib);
                 renderCache.borderIndicesCount = ib.length;
 
-                let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_BORDERPARTID, ["position"]);
+                // Get the instanced version of the effect, if the engine does not support it, null is return and we'll only draw on by one
+                let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_BORDERPARTID, ["position"], true);
+                if (ei) {
+                    renderCache.effectBorderInstanced = engine.createEffect({ vertex: "lines2d", fragment: "lines2d" }, ei.attributes, ei.uniforms, [], ei.defines, null);
+                }
+
+                // Get the non instanced version
+                ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_BORDERPARTID, ["position"], false);
                 renderCache.effectBorder = engine.createEffect({ vertex: "lines2d", fragment: "lines2d" }, ei.attributes, ei.uniforms, [], ei.defines, null);
             }
 

+ 169 - 50
src/Canvas2d/babylon.modelRenderCache.ts

@@ -4,15 +4,18 @@
     }
 
     export class GroupInstanceInfo {
-        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>();
-            this._partIndexFromId = new StringDictionary<number>();
-            this._instancesPartsUsedShaderCategories = new Array<string>();
+        constructor(owner: Group2D, mrc: ModelRenderCache, partCount: number) {
+            this._partCount = partCount;
+            this.owner = owner;
+            this.modelRenderCache = mrc;
+            this.modelRenderCache.addRef();
+            this.partIndexFromId = new StringDictionary<number>();
+            this._usedShaderCategories = new Array<string>(partCount);
+            this._strides = new Array<number>(partCount);
+            this._opaqueData = null;
+            this._alphaTestData = null;
+            this._transparentData = null;
+            this.opaqueDirty = this.alphaTestDirty = this.transparentDirty = this.transparentOrderDirty = false;
         }
 
         public dispose(): boolean {
@@ -20,44 +23,158 @@
                 return false;
             }
 
-            if (this._modelCache) {
-                this._modelCache.dispose();
+            if (this.modelRenderCache) {
+                this.modelRenderCache.dispose();
             }
 
-            let engine = this._owner.owner.engine;
-            if (this._instancesPartsBuffer) {
-                this._instancesPartsBuffer.forEach(b => {
-                    engine._releaseBuffer(b);
-                });
-            }
+            let engine = this.owner.owner.engine;
 
-            this._partIndexFromId = null;
-            this._instancesPartsData = null;
-            this._instancesPartsBufferSize = null;
-            this._instancesPartsUsedShaderCategories = null;
+            if (this.opaqueData) {
+                this.opaqueData.forEach(d => d.dispose(engine));
+                this.opaqueData = null;
+            }
 
+            this.partIndexFromId = null;
+            this._isDisposed = true;
             return true;
         }
 
-        _isDisposed: boolean;
-        _owner: Group2D;
-        _modelCache: ModelRenderCache;
-        _partIndexFromId: StringDictionary<number>;
-        _instancesPartsData: DynamicFloatArray[];
-        _dirtyInstancesData: boolean;
-        _instancesPartsBuffer: WebGLBuffer[];
-        _instancesPartsBufferSize: number[];
-        _instancesPartsUsedShaderCategories: string[];
+        private _isDisposed: boolean;
+        owner: Group2D;
+
+        modelRenderCache: ModelRenderCache;
+        partIndexFromId: StringDictionary<number>;
+
+        get hasOpaqueData(): boolean {
+            return this._opaqueData != null;
+        }
+
+        get hasAlphaTestData(): boolean {
+            return this._alphaTestData != null;
+        }
+
+        get hasTransparentData(): boolean {
+            return this._transparentData != null;
+        }
+
+        opaqueDirty: boolean;
+        get opaqueData(): GroupInfoPartData[] {
+            if (!this._opaqueData) {
+                this._opaqueData = new Array<GroupInfoPartData>(this._partCount);
+                for (let i = 0; i < this._partCount; i++) {
+                    this._opaqueData[i] = new GroupInfoPartData(this._strides[i]);
+                }
+            }
+            return this._opaqueData;
+        }
+
+        alphaTestDirty: boolean;
+        get alphaTestData(): GroupInfoPartData[] {
+            if (!this._alphaTestData) {
+                this._alphaTestData = new Array<GroupInfoPartData>(this._partCount);
+                for (let i = 0; i < this._partCount; i++) {
+                    this._alphaTestData[i] = new GroupInfoPartData(this._strides[i]);
+                }
+            }
+            return this._alphaTestData;
+        }
+
+        transparentOrderDirty: boolean;
+        transparentDirty: boolean;
+        get transparentData(): TransparentGroupInfoPartData[] {
+            if (!this._transparentData) {
+                this._transparentData = new Array<TransparentGroupInfoPartData>(this._partCount);
+                for (let i = 0; i < this._partCount; i++) {
+                    let zoff = this.modelRenderCache._partData[i]._zBiasOffset;
+                    this._transparentData[i] = new TransparentGroupInfoPartData(this._strides[i], zoff);
+                }
+            }
+            return this._transparentData;
+        }
+
+        sortTransparentData() {
+            if (!this.transparentOrderDirty) {
+                return;
+            }
+
+            for (let i = 0; i < this._transparentData.length; i++) {
+                let td = this._transparentData[i];
+                td._partData.sort();
+
+            }
+
+            this.transparentOrderDirty = false;
+        }
+
+        get usedShaderCategories(): string[] {
+            return this._usedShaderCategories;
+        }
+
+        get strides(): number[] {
+            return this._strides;
+        }
+
+        private _partCount: number;
+        private _strides: number[];
+        private _usedShaderCategories: string[];
+        private _opaqueData: GroupInfoPartData[];
+        private _alphaTestData: GroupInfoPartData[];
+        private _transparentData: TransparentGroupInfoPartData[];
+    }
+
+    export class TransparentSegment {
+        groupInsanceInfo: GroupInstanceInfo;
+        startZ: number;
+        endZ: number;
+        startDataIndex: number;
+        endDataIndex: number;
+    }
+
+    export class GroupInfoPartData {
+        _partData: DynamicFloatArray = null;
+        _partBuffer: WebGLBuffer     = null;
+        _partBufferSize: number      = 0;
+
+        constructor(stride: number) {
+            this._partData = new DynamicFloatArray(stride/4, 50);
+            this._isDisposed = false;
+        }
+
+        public dispose(engine: Engine): boolean {
+            if (this._isDisposed) {
+                return false;
+            }
+
+            if (this._partBuffer) {
+                engine._releaseBuffer(this._partBuffer);
+                this._partBuffer = null;
+            }
+
+            this._partData = null;
+
+            this._isDisposed = true;
+        }
+
+        private _isDisposed: boolean;        
+    }
+
+    export class TransparentGroupInfoPartData extends GroupInfoPartData {
+        constructor(stride: number, zoff: number) {
+            super(stride);
+            this._partData.compareValueOffset = zoff;
+            this._partData.sortingAscending = false;
+        }
+        
     }
 
     export class ModelRenderCache {
-        constructor(engine: Engine, modelKey: string, isTransparent: boolean) {
+        constructor(engine: Engine, modelKey: string) {
             this._engine = engine;
             this._modelKey = modelKey;
-            this._isTransparent = isTransparent;
             this._nextKey = 1;
             this._refCounter = 1;
             this._instancesData = new StringDictionary<InstanceDataBase[]>();
+            this._partData = null;
         }
 
         public dispose(): boolean {
@@ -113,8 +230,8 @@
         }
 
         protected getPartIndexFromId(partId: number) {
-            for (var i = 0; i < this._partIdList.length; i++) {
-                if (this._partIdList[i] === partId) {
+            for (var i = 0; i < this._partData.length; i++) {
+                if (this._partData[i]._partId === partId) {
                     return i;
                 }
             }
@@ -128,7 +245,7 @@
             }
 
             var ci = this._partsClassInfo[i];
-            var categories = this._partsUsedCategories[i];
+            var categories = this._partData[i]._partUsedCategories;
             let res = ci.classContent.getInstancingAttributeInfos(effect, categories);
 
             return res;
@@ -153,22 +270,23 @@
         private static v4 = Vector4.Zero();
 
         protected setupUniforms(effect: Effect, partIndex: number, data: DynamicFloatArray, elementCount: number) {
-            let offset = (this._partsDataStride[partIndex]/4) * elementCount;
+            let pd = this._partData[partIndex];
+            let offset = (pd._partDataStride/4) * elementCount;
             let pci = this._partsClassInfo[partIndex];
 
             let self = this;
             pci.fullContent.forEach((k, v) => {
-                if (!v.category || self._partsUsedCategories[partIndex].indexOf(v.category)!==1) {
+                if (!v.category || pd._partUsedCategories.indexOf(v.category) !== -1) {
                     switch (v.dataType) {
                         case ShaderDataType.float:
                         {
-                            let attribOffset = v.instanceOffset.get(self._partsJoinedUsedCategories[partIndex]);
+                            let attribOffset = v.instanceOffset.get(pd._partJoinedUsedCategories);
                             effect.setFloat(v.attributeName, data.buffer[offset + attribOffset]);
                             break;
                         }
                         case ShaderDataType.Vector2:
                         {
-                            let attribOffset = v.instanceOffset.get(self._partsJoinedUsedCategories[partIndex]);
+                            let attribOffset = v.instanceOffset.get(pd._partJoinedUsedCategories);
                             ModelRenderCache.v2.x = data.buffer[offset + attribOffset + 0];
                             ModelRenderCache.v2.y = data.buffer[offset + attribOffset + 1];
                             effect.setVector2(v.attributeName, ModelRenderCache.v2);
@@ -177,7 +295,7 @@
                         case ShaderDataType.Color3:
                         case ShaderDataType.Vector3:
                         {
-                            let attribOffset = v.instanceOffset.get(self._partsJoinedUsedCategories[partIndex]);
+                            let attribOffset = v.instanceOffset.get(pd._partJoinedUsedCategories);
                             ModelRenderCache.v3.x = data.buffer[offset + attribOffset + 0];
                             ModelRenderCache.v3.y = data.buffer[offset + attribOffset + 1];
                             ModelRenderCache.v3.z = data.buffer[offset + attribOffset + 2];
@@ -187,7 +305,7 @@
                         case ShaderDataType.Color4:
                         case ShaderDataType.Vector4:
                         {
-                            let attribOffset = v.instanceOffset.get(self._partsJoinedUsedCategories[partIndex]);
+                            let attribOffset = v.instanceOffset.get(pd._partJoinedUsedCategories);
                             ModelRenderCache.v4.x = data.buffer[offset + attribOffset + 0];
                             ModelRenderCache.v4.y = data.buffer[offset + attribOffset + 1];
                             ModelRenderCache.v4.z = data.buffer[offset + attribOffset + 2];
@@ -203,20 +321,21 @@
 
         protected _engine: Engine;
         private _modelKey: string;
-        private _isTransparent: boolean;
-
-        public get isTransparent() {
-            return this._isTransparent;
-        }
 
         _instancesData: StringDictionary<InstanceDataBase[]>;
 
         private _nextKey: number;
         private _refCounter: number;
-        _partIdList: number[];
-        _partsDataStride: number[];
-        _partsUsedCategories: Array<string[]>;
-        _partsJoinedUsedCategories: string[];
+
+        _partData: ModelRenderCachePartData[];
         _partsClassInfo: ClassTreeInfo<InstanceClassInfo, InstancePropInfo>[];
     }
+
+    export class ModelRenderCachePartData {
+        _partId: number;
+        _zBiasOffset: number;
+        _partDataStride: number;
+        _partUsedCategories: string[];
+        _partJoinedUsedCategories: string;
+    }
 }

+ 89 - 5
src/Canvas2d/babylon.prim2dBase.ts

@@ -1,9 +1,87 @@
 module BABYLON {
 
-    export class Render2DContext {
+    export class PreapreRender2DContext {
+        constructor() {
+            this.forceRefreshPrimitive = false;
+        }
+
+        /**
+         * True if the primitive must be refreshed no matter what
+         * This mode is needed because sometimes the primitive doesn't change by itself, but external changes make a refresh of its InstanceData necessary
+         */
         forceRefreshPrimitive: boolean;
     }
 
+    export class Render2DContext {
+
+        constructor(renderMode: number) {
+            this._renderMode = renderMode;
+            this.useInstancing = false;
+            this.groupInfoPartData = null;
+            this.partDataStartIndex = this.partDataEndIndex = null;
+        }
+        /**
+         * Define which render Mode should be used to render the primitive: one of Render2DContext.RenderModeXxxx property
+         */
+        get renderMode(): number {
+            return this._renderMode;
+        }
+
+        /**
+         * If true hardware instancing is supported and must be used for the rendering. The groupInfoPartData._partBuffer must be used.
+         * If false rendering on a per primitive basis must be made. The following properties must be used
+         *  - groupInfoPartData._partData: contains the primitive instances data to render
+         *  - partDataStartIndex: the index into instanceArrayData of the first instance to render.
+         *  - partDataCount: the number of primitive to render
+         */
+        useInstancing: boolean;
+
+        /**
+         * Contains the data related to the primitives instances to render
+         */
+        groupInfoPartData: GroupInfoPartData[];
+
+        /**
+         * The index into groupInfoPartData._partData of the first primitive to render. This is an index, not an offset: it represent the nth primitive which is the first to render.
+         */
+        partDataStartIndex: number;
+
+        /**
+         * The exclusive end index, you have to render the primitive instances until you reach this one, but don't render this one!
+         */
+        partDataEndIndex: number;
+
+        /**
+         * The set of primitives to render is opaque.
+         * This is the first rendering pass. All Opaque primitives are rendered. Depth Compare and Write are both enabled.
+         */
+        public static get RenderModeOpaque(): number {
+            return Render2DContext._renderModeOpaque;
+        }
+
+        /**
+         * The set of primitives to render is using Alpha Test (aka masking).
+         * Alpha Blend is enabled, the AlphaMode must be manually set, the render occurs after the RenderModeOpaque and is depth independent (i.e. primitives are not sorted by depth). Depth Compare and Write are both enabled.
+         */
+        public static get RenderModeAlphaTest(): number {
+            return Render2DContext._renderModeAlphaTest;
+        }
+
+        /**
+         * The set of primitives to render is transparent.
+         * Alpha Blend is enabled, the AlphaMode must be manually set, the render occurs after the RenderModeAlphaTest and is depth dependent (i.e. primitives are stored by depth and rendered back to front). Depth Compare is on, but Depth write is Off.
+         */
+        public static get RenderModeTransparent(): number {
+            return Render2DContext._renderModeTransparent;
+        }
+
+        private static _renderModeOpaque:      number = 1;
+        private static _renderModeAlphaTest:   number = 2;
+        private static _renderModeTransparent: number = 3;
+
+        private _renderMode: number;
+    }
+
     /**
      * This class store information for the pointerEventObservable Observable.
      * The Observable is divided into many sub events (using the Mask feature of the Observable pattern): PointerOver, PointerEnter, PointerDown, PointerMouseWheel, PointerMove, PointerUp, PointerDown, PointerLeave, PointerGotCapture and PointerLostCapture.
@@ -396,6 +474,7 @@
             this._boundingInfo = new BoundingInfo2D();
             this._owner = owner;
             this._parent = parent;
+            this._id = id;
             if (parent != null) {
                 this._hierarchyDepth = parent._hierarchyDepth + 1;
                 this._renderGroup = <Group2D>this.parent.traverseUp(p => p instanceof Group2D && p.isRenderableGroup);
@@ -405,7 +484,6 @@
                 this._renderGroup = null;
             }
 
-            this._id = id;
             this.propertyChanged = new Observable<PropertyChangedInfo>();
             this._children = new Array<Prim2DBase>();
             this._globalTransformProcessStep = 0;
@@ -627,6 +705,7 @@
 
         public set zOrder(value: number) {
             this._zOrder = value;
+            this.onZOrderChanged();
         }
 
         @dynamicLevelProperty(8, pi => Prim2DBase.marginProperty = pi)
@@ -746,6 +825,10 @@
             return this._pointerEventObservable;
         }
 
+        protected onZOrderChanged() {
+            
+        }
+
         protected levelIntersect(intersectInfo: IntersectInfo2D): boolean {
 
             return false;
@@ -863,6 +946,7 @@
 
         private addChild(child: Prim2DBase) {
             child._hierarchyDepthOffset = this._hierarchyDepthOffset + ((this._children.length + 1) * this._siblingDepthOffset);
+            console.log(`Node: ${child.id} has depth: ${child._hierarchyDepthOffset}`);
             child._siblingDepthOffset = this._siblingDepthOffset / this.owner.hierarchyLevelMaxSiblingCount;
             this._children.push(child);
         }
@@ -914,15 +998,15 @@
             return this._visibilityChanged || this._modelDirty || (this._instanceDirtyFlags !== 0) || (this._globalTransformProcessStep !== this._globalTransformStep);
         }
 
-        public _prepareRender(context: Render2DContext) {
+        public _prepareRender(context: PreapreRender2DContext) {
             this._prepareRenderPre(context);
             this._prepareRenderPost(context);
         }
 
-        public _prepareRenderPre(context: Render2DContext) {
+        public _prepareRenderPre(context: PreapreRender2DContext) {
         }
 
-        public _prepareRenderPost(context: Render2DContext) {
+        public _prepareRenderPost(context: PreapreRender2DContext) {
             // Don't recurse if it's a renderable group, the content will be processed by the group itself
             if (this instanceof Group2D) {
                 var self: any = this;

+ 94 - 60
src/Canvas2d/babylon.rectangle2d.ts

@@ -1,28 +1,35 @@
 module BABYLON {
     export class Rectangle2DRenderCache extends ModelRenderCache {
-        fillVB: WebGLBuffer;
-        fillIB: WebGLBuffer;
-        fillIndicesCount: number;
-        instancingFillAttributes: InstancingAttributeInfo[];
-        effectFill: Effect;
-
-        borderVB: WebGLBuffer;
-        borderIB: WebGLBuffer;
-        borderIndicesCount: number;
-        instancingBorderAttributes: InstancingAttributeInfo[];
-        effectBorder: Effect;
-
-        constructor(engine: Engine, modelKey: string, isTransparent: boolean) {
-            super(engine, modelKey, isTransparent);
+        effectsReady: boolean                               = false;
+        fillVB: WebGLBuffer                                 = null;
+        fillIB: WebGLBuffer                                 = null;
+        fillIndicesCount: number                            = 0;
+        instancingFillAttributes: InstancingAttributeInfo[] = null;
+        effectFill: Effect                                  = null;
+        effectFillInstanced: Effect                         = null;
+
+        borderVB: WebGLBuffer                                 = null;
+        borderIB: WebGLBuffer                                 = null;
+        borderIndicesCount: number                            = 0;
+        instancingBorderAttributes: InstancingAttributeInfo[] = null;
+        effectBorder: Effect                                  = null;
+        effectBorderInstanced: Effect                         = null;
+
+        constructor(engine: Engine, modelKey: string) {
+            super(engine, modelKey);
         }
 
         render(instanceInfo: GroupInstanceInfo, context: Render2DContext): boolean {
-            // Do nothing if the shader is still loading/preparing
-            if ((this.effectFill && !this.effectFill.isReady()) || (this.effectBorder && !this.effectBorder.isReady())) {
-                return false;
+            // Do nothing if the shader is still loading/preparing 
+            if (!this.effectsReady) {
+                if ((this.effectFill && (!this.effectFill.isReady() || (this.effectFillInstanced && !this.effectFillInstanced.isReady()))) ||
+                    (this.effectBorder && (!this.effectBorder.isReady() || (this.effectBorderInstanced && !this.effectBorderInstanced.isReady())))) {
+                    return false;
+                }
+                this.effectsReady = true;
             }
 
-            var engine = instanceInfo._owner.owner.engine;
+            var engine = instanceInfo.owner.owner.engine;
 
             let depthFunction = 0;
             if (this.effectFill && this.effectBorder) {
@@ -30,60 +37,66 @@
                 engine.setDepthFunctionToLessOrEqual();
             }
 
-            var cur: number;
-            if (this.isTransparent) {
-                cur = engine.getAlphaMode();
-                engine.setAlphaMode(Engine.ALPHA_COMBINE);
-            }
+            var curAlphaMode = engine.getAlphaMode();
 
             if (this.effectFill) {
-                let partIndex = instanceInfo._partIndexFromId.get(Shape2D.SHAPE2D_FILLPARTID.toString());
 
-                engine.enableEffect(this.effectFill);
-                engine.bindBuffers(this.fillVB, this.fillIB, [1], 4, this.effectFill);
-                let count = instanceInfo._instancesPartsData[partIndex].usedElementCount;
-                if (instanceInfo._owner.owner.supportInstancedArray) {
+                let partIndex = instanceInfo.partIndexFromId.get(Shape2D.SHAPE2D_FILLPARTID.toString());
+                let pid = context.groupInfoPartData[partIndex];
+
+                if (context.renderMode !== Render2DContext.RenderModeOpaque) {
+                    engine.setAlphaMode(Engine.ALPHA_COMBINE);
+                }
+
+                let effect = context.useInstancing ? this.effectFillInstanced : this.effectFill;
+
+                engine.enableEffect(effect);
+                engine.bindBuffers(this.fillVB, this.fillIB, [1], 4, effect);
+                if (context.useInstancing) {
                     if (!this.instancingFillAttributes) {
-                        // Compute the offset locations of the attributes in the vertex shader that will be mapped to the instance buffer data
-                        this.instancingFillAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_FILLPARTID, this.effectFill);
+                        this.instancingFillAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_FILLPARTID, effect);
                     }
 
-                    engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], null, this.instancingFillAttributes);
-                    engine.draw(true, 0, this.fillIndicesCount, count);
-                    engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], this.instancingFillAttributes);
+                    engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingFillAttributes);
+                    engine.draw(true, 0, this.fillIndicesCount, pid._partData.usedElementCount);
+                    engine.unBindInstancesBuffer(pid._partBuffer, this.instancingFillAttributes);
                 } else {
-                    for (let i = 0; i < count; i++) {
-                        this.setupUniforms(this.effectFill, partIndex, instanceInfo._instancesPartsData[partIndex], i);
+                    for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) {
+                        this.setupUniforms(effect, partIndex, pid._partData, i);
                         engine.draw(true, 0, this.fillIndicesCount);                        
                     }
                 }
             }
 
             if (this.effectBorder) {
-                let partIndex = instanceInfo._partIndexFromId.get(Shape2D.SHAPE2D_BORDERPARTID.toString());
+                let partIndex = instanceInfo.partIndexFromId.get(Shape2D.SHAPE2D_BORDERPARTID.toString());
+                let pid = context.groupInfoPartData[partIndex];
+
+                if (context.renderMode !== Render2DContext.RenderModeOpaque) {
+                    engine.setAlphaMode(Engine.ALPHA_COMBINE);
+                }
+
+                let effect = context.useInstancing ? this.effectBorderInstanced : this.effectBorder;
 
-                engine.enableEffect(this.effectBorder);
-                engine.bindBuffers(this.borderVB, this.borderIB, [1], 4, this.effectBorder);
-                let count = instanceInfo._instancesPartsData[partIndex].usedElementCount;
-                if (instanceInfo._owner.owner.supportInstancedArray) {
+                engine.enableEffect(effect);
+                engine.bindBuffers(this.borderVB, this.borderIB, [1], 4, effect);
+                if (context.useInstancing) {
                     if (!this.instancingBorderAttributes) {
-                        this.instancingBorderAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_BORDERPARTID, this.effectBorder);
+                        this.instancingBorderAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_BORDERPARTID, effect);
                     }
 
-                    engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], null, this.instancingBorderAttributes);
-                    engine.draw(true, 0, this.borderIndicesCount, count);
-                    engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], this.instancingBorderAttributes);
+                    engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingBorderAttributes);
+                    engine.draw(true, 0, this.borderIndicesCount, pid._partData.usedElementCount);
+                    engine.unBindInstancesBuffer(pid._partBuffer, this.instancingBorderAttributes);
                 } else {
-                    for (let i = 0; i < count; i++) {
-                        this.setupUniforms(this.effectBorder, partIndex, instanceInfo._instancesPartsData[partIndex], i);
+                    for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) {
+                        this.setupUniforms(effect, partIndex, pid._partData, i);
                         engine.draw(true, 0, this.borderIndicesCount);
                     }
                 }
             }
 
-            if (this.isTransparent) {
-                engine.setAlphaMode(cur);
-            }
+            engine.setAlphaMode(curAlphaMode);
 
             if (this.effectFill && this.effectBorder) {
                 engine.setDepthFunction(depthFunction);
@@ -111,6 +124,11 @@
                 this.effectFill = null;
             }
 
+            if (this.effectFillInstanced) {
+                this._engine._releaseEffect(this.effectFillInstanced);
+                this.effectFillInstanced = null;
+            }
+
             if (this.borderVB) {
                 this._engine._releaseBuffer(this.borderVB);
                 this.borderVB = null;
@@ -126,6 +144,11 @@
                 this.effectBorder = null;
             }
 
+            if (this.effectBorderInstanced) {
+                this._engine._releaseEffect(this.effectBorderInstanced);
+                this.effectBorderInstanced = null;
+            }
+
             return true;
         }
     }
@@ -229,16 +252,17 @@
             } else {
                 let pos = options.position || new Vector2(options.x || 0, options.y || 0);
                 let size = options.size || (new Size(options.width || 10, options.height || 10));
+                let fill = options.fill===undefined ? Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF") : options.fill;
 
-                rect.setupRectangle2D(parent.owner, parent, options.id || null, pos, options.origin || null, size, options.roundRadius || 0, options.fill || Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF"), options.border || null, options.borderThickness || 1, options.isVisible || true, options.marginTop || null, options.marginLeft || null, options.marginRight || null, options.marginBottom || null, options.vAlignment || null, options.hAlignment || null);
+                rect.setupRectangle2D(parent.owner, parent, options.id || null, pos, options.origin || null, size, options.roundRadius || 0, fill, options.border || null, options.borderThickness || 1, options.isVisible || true, options.marginTop || null, options.marginLeft || null, options.marginRight || null, options.marginBottom || null, options.vAlignment || null, options.hAlignment || null);
             }
             return rect;
         }
 
         public static roundSubdivisions = 16;
 
-        protected createModelRenderCache(modelKey: string, isTransparent: boolean): ModelRenderCache {
-            let renderCache = new Rectangle2DRenderCache(this.owner.engine, modelKey, isTransparent);
+        protected createModelRenderCache(modelKey: string): ModelRenderCache {
+            let renderCache = new Rectangle2DRenderCache(this.owner.engine, modelKey);
             return renderCache;
         }
 
@@ -267,10 +291,15 @@
                 renderCache.fillIB = engine.createIndexBuffer(ib);
                 renderCache.fillIndicesCount = triCount * 3;
 
-                let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_FILLPARTID, ["index"]);
-                renderCache.effectFill = engine.createEffect({ vertex: "rect2d", fragment: "rect2d" }, ei.attributes, ei.uniforms, [], ei.defines, null, e => {
-//                    renderCache.setupUniformsLocation(e, ei.uniforms, Shape2D.SHAPE2D_FILLPARTID);
-                });
+                // Get the instanced version of the effect, if the engine does not support it, null is return and we'll only draw on by one
+                let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_FILLPARTID, ["index"], true);
+                if (ei) {
+                    renderCache.effectFillInstanced = engine.createEffect("rect2d", ei.attributes, ei.uniforms, [], ei.defines, null);
+                }
+
+                // Get the non instanced version
+                ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_FILLPARTID, ["index"], false);
+                renderCache.effectFill = engine.createEffect("rect2d", ei.attributes, ei.uniforms, [], ei.defines, null);
             }
 
             // Need to create WebGL resource for border part?
@@ -301,10 +330,15 @@
                 renderCache.borderIB = engine.createIndexBuffer(ib);
                 renderCache.borderIndicesCount = triCount * 3;
 
-                let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_BORDERPARTID, ["index"]);
-                renderCache.effectBorder = engine.createEffect({ vertex: "rect2d", fragment: "rect2d" }, ei.attributes, ei.uniforms, [], ei.defines, null, e => {
-//                    renderCache.setupUniformsLocation(e, ei.uniforms, Shape2D.SHAPE2D_BORDERPARTID);
-                });
+                // Get the instanced version of the effect, if the engine does not support it, null is return and we'll only draw on by one
+                let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_BORDERPARTID, ["index"], true);
+                if (ei) {
+                    renderCache.effectBorderInstanced = engine.createEffect("rect2d", ei.attributes, ei.uniforms, [], ei.defines, null);
+                }
+
+                // Get the non instanced version
+                ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_BORDERPARTID, ["index"], false);
+                renderCache.effectBorder = engine.createEffect("rect2d", ei.attributes, ei.uniforms, [], ei.defines, null);
             }
 
             return renderCache;

+ 266 - 116
src/Canvas2d/babylon.renderablePrim2d.ts

@@ -332,9 +332,19 @@
     export class RenderablePrim2D extends Prim2DBase {
         static RENDERABLEPRIM2D_PROPCOUNT: number = Prim2DBase.PRIM2DBASE_PROPCOUNT + 5;
 
+        public static isAlphaTestProperty: Prim2DPropInfo;
         public static isTransparentProperty: Prim2DPropInfo;
 
-        @modelLevelProperty(Prim2DBase.PRIM2DBASE_PROPCOUNT + 1, pi => RenderablePrim2D.isTransparentProperty = pi)
+        @dynamicLevelProperty(Prim2DBase.PRIM2DBASE_PROPCOUNT + 0, pi => RenderablePrim2D.isAlphaTestProperty = pi)
+        public get isAlphaTest(): boolean {
+            return this._isAlphaTest;
+        }
+
+        public set isAlphaTest(value: boolean) {
+            this._isAlphaTest = value;
+        }
+
+        @dynamicLevelProperty(Prim2DBase.PRIM2DBASE_PROPCOUNT + 1, pi => RenderablePrim2D.isTransparentProperty = pi)
         public get isTransparent(): boolean {
             return this._isTransparent;
         }
@@ -346,6 +356,8 @@
         setupRenderablePrim2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, isVisible: boolean, marginTop: number, marginLeft: number, marginRight: number, marginBottom: number, hAlign: number, vAlign: number) {
             this.setupPrim2DBase(owner, parent, id, position, origin, isVisible, marginTop, marginLeft, marginRight, marginBottom, hAlign, vAlign);
             this._isTransparent = false;
+            this._isAlphaTest = false;
+            this._transparentPrimitiveInfo = null;
         }
 
         public dispose(): boolean {
@@ -373,7 +385,7 @@
             return true;
         }
 
-        public _prepareRenderPre(context: Render2DContext) {
+        public _prepareRenderPre(context: PreapreRender2DContext) {
             super._prepareRenderPre(context);
 
             // If the model changed and we have already an instance, we must remove this instance from the obsolete model
@@ -385,146 +397,268 @@
             // 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;
-                    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();
-                }
+                setupModelRenderCache = this._createModelRenderCache();
             }
 
-            let gii: GroupInstanceInfo;
+            let gii: GroupInstanceInfo = null;
             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;
+                gii = this._createModelDataParts();
+            }
 
-                // 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>();
-                    let usedCatList = new Array<string[]>();
-                    let partIdList = new Array<number>();
-                    let joinedUsedCatList = new Array<string>();
-
-                    for (let dataPart of parts) {
-                        var cat = this.getUsedShaderCategories(dataPart);
-                        var cti = dataPart.getClassTreeInfo();
-                        // 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 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);
-                        InstanceClassInfo._CurCategories = joinCat;
-                        let obj = this.beforeRefreshForLayoutConstruction(dataPart);
-                        this.refreshInstanceDataPart(dataPart);
-                        this.afterRefreshForLayoutConstruction(dataPart, obj);
-                        this.isVisible = curVisible;
-
-                        var size = 0;
-                        cti.fullContent.forEach((k, v) => {
-                            if (!v.category || cat.indexOf(v.category) !== -1) {
-                                if (!v.size) {
-                                    console.log(`ERROR: Couldn't detect the size of the Property ${v.attributeName} from type ${Tools.getClassName(cti.type)}. Property is ignored.`);
-                                } else {
-                                    size += v.size;
-                                }
-                            }
-                        });
-                        dataStrides.push(size);
-                        usedCatList.push(cat);
-                        ctiArray.push(cti);
-                        partIdList.push(dataPart.id);
-                    }
-                    this._modelRenderCache._partsDataStride = dataStrides;
-                    this._modelRenderCache._partsUsedCategories = usedCatList;
-                    this._modelRenderCache._partsJoinedUsedCategories = joinedUsedCatList;
-                    this._modelRenderCache._partsClassInfo = ctiArray;
-                    this._modelRenderCache._partIdList = partIdList;
+            // 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);
+            }
+
+            // 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)) {
+                this._updateInstanceDataParts(gii);
+            }
+        }
+
+        private _createModelRenderCache(): boolean {
+            let setupModelRenderCache = false;
+
+            if (this._modelRenderCache) {
+                this._modelRenderCache.dispose();
+            }
+            this._modelRenderCache = this.owner._engineData.GetOrAddModelCache(this.modelKey, (key: string) => {
+                let mrc = this.createModelRenderCache(key);
+                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();
+            }
+
+            return setupModelRenderCache;
+        }
+
+        private _createModelDataParts(): GroupInstanceInfo {
+            // 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._partData) {
+                this._setupModelRenderCache(parts);
+            }
+
+            // 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
+            let gii = this.renderGroup._renderableData._renderGroupInstancesInfo.getOrAddWithFactory(this.modelKey, k => {
+
+                let res = new GroupInstanceInfo(this.renderGroup, this._modelRenderCache, this._modelRenderCache._partData.length);
+
+                for (let j = 0; j < this._modelRenderCache._partData.length; j++) {
+                    let part = this._instanceDataParts[j];
+                    res.partIndexFromId.add(part.id.toString(), j);
+                    res.usedShaderCategories[j] = ";" + this.getUsedShaderCategories(part).join(";") + ";";
+                    res.strides[j] = this._modelRenderCache._partData[j]._partDataStride;
                 }
 
-                // 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._renderGroupInstancesInfo.getOrAddWithFactory(this.modelKey, k => new GroupInstanceInfo(this.renderGroup, this._modelRenderCache));
+                return res;
+            });
+
+            // Get the GroupInfoDataPart corresponding to the render category of the part
+            let gipd: GroupInfoPartData[] = null;
+            if (this.isTransparent) {
+                gipd = gii.transparentData;
+            } else if (this.isAlphaTest) {
+                gipd = gii.alphaTestData;
+            } else {
+                gipd = gii.opaqueData;
+            }
+
+            // 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 = gipd[i]._partData;
+                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);
+
+            return gii;
+        }
+
+        private _setupModelRenderCache(parts: InstanceDataBase[]) {
+            let ctiArray = new Array<ClassTreeInfo<InstanceClassInfo, InstancePropInfo>>();
+            this._modelRenderCache._partData = new Array<ModelRenderCachePartData>();
+            for (let dataPart of parts) {
+                let pd = new ModelRenderCachePartData();
+                this._modelRenderCache._partData.push(pd)
+                var cat = this.getUsedShaderCategories(dataPart);
+                var cti = dataPart.getClassTreeInfo();
+                // 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 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(";") + ";";
+                pd._partJoinedUsedCategories = joinCat;
+                InstanceClassInfo._CurCategories = joinCat;
+                let obj = this.beforeRefreshForLayoutConstruction(dataPart);
+                this.refreshInstanceDataPart(dataPart);
+                this.afterRefreshForLayoutConstruction(dataPart, obj);
+                this.isVisible = curVisible;
+
+                var size = 0;
+                cti.fullContent.forEach((k, v) => {
+                    if (!v.category || cat.indexOf(v.category) !== -1) {
+                        if (v.attributeName === "zBias") {
+                            pd._zBiasOffset = v.instanceOffset.get(joinCat);
+                        }
+                        if (!v.size) {
+                            console.log(`ERROR: Couldn't detect the size of the Property ${v.attributeName} from type ${Tools.getClassName(cti.type)}. Property is ignored.`);
+                        } else {
+                            size += v.size;
+                        }
+                    }
+                });
+                pd._partDataStride = size;
+                pd._partUsedCategories = cat;
+                pd._partId = dataPart.id;
+                ctiArray.push(cti);
+            }
+            this._modelRenderCache._partsClassInfo = ctiArray;
+        }
+
+        protected onZOrderChanged() {
+            if (this.isTransparent && this._transparentPrimitiveInfo) {
+                this.renderGroup._renderableData.transparentPrimitiveZChanged(this._transparentPrimitiveInfo);
+                let gii = this.renderGroup._renderableData._renderGroupInstancesInfo.get(this.modelKey);
 
-                // First time init of the GroupInstanceInfo
-                if (gii._instancesPartsData.length === 0) {
-                    for (let j = 0; j < this._modelRenderCache._partsDataStride.length; j++) {
-                        let stride = this._modelRenderCache._partsDataStride[j];
-                        gii._instancesPartsData.push(new DynamicFloatArray(stride / 4, 50));
-                        gii._partIndexFromId.add(this._modelRenderCache._partIdList[j].toString(), j);
+                // Flag the transparentData dirty has will have to sort it again
+                gii.transparentOrderDirty = true;
+            }
+        }
 
-                        for (let part of this._instanceDataParts) {
-                            gii._instancesPartsUsedShaderCategories[gii._partIndexFromId.get(part.id.toString())] = ";" + this.getUsedShaderCategories(part).join(";") + ";";
+        private _updateInstanceDataParts(gii: GroupInstanceInfo) {
+            // Fetch the GroupInstanceInfo if we don't already have it
+            if (!gii) {
+                gii = this.renderGroup._renderableData._renderGroupInstancesInfo.get(this.modelKey);
+            }
+
+            // Handle changes related to ZOffset
+            if (this.isTransparent) {
+                // Handle visibility change, which is also triggered when the primitive just got created
+                if (this._visibilityChanged) {
+                    if (this.isVisible) {
+                        if (!this._transparentPrimitiveInfo) {
+                            // Add the primitive to the list of transparent ones in the group that render is
+                            this._transparentPrimitiveInfo = this.renderGroup._renderableData.addNewTransparentPrimitiveInfo(this, gii);
+                        }
+                    } else {
+                        if (this._transparentPrimitiveInfo) {
+                            this.renderGroup._renderableData.removeTransparentPrimitiveInfo(this._transparentPrimitiveInfo);
                         }
                     }
+                    gii.transparentOrderDirty = true;
                 }
+            }
 
-                // 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];
+            // For each Instance Data part, refresh it to update the data in the DynamicFloatArray
+            for (let part of this._instanceDataParts) {
+                // Check if we need to allocate data elements (hidden prim which becomes visible again)
+                if (this._visibilityChanged && !part.dataElements) {
                     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);
+                InstanceClassInfo._CurCategories = gii.usedShaderCategories[gii.partIndexFromId.get(part.id.toString())];
+
+                // Will return false if the instance should not be rendered (not visible or other any reasons)
+                if (!this.refreshInstanceDataPart(part)) {
+                    // Free the data element
+                    if (part.dataElements) {
+                        part.freeElements();
+                    }
+                }
             }
+            this._instanceDirtyFlags = 0;
 
-            // 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);
+            // Make the appropriate data dirty
+            if (this.isTransparent) {
+                gii.transparentDirty = true;
+            } else if (this.isAlphaTest) {
+                gii.alphaTestDirty = true;
+            } else {
+                gii.opaqueDirty = true;
             }
 
-            // 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.
+            this._visibilityChanged = false;    // Reset the flag as we've handled the case            
+        }
 
-            // 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)) {
+        public _getFirstIndexInDataBuffer(): number {
+            for (let part of this._instanceDataParts) {
+                if (part) {
+                    return part.dataElements[0].offset / part.dataBuffer.stride;
+                }
+            }
+            return null;
+        }
 
-                // Fetch the GroupInstanceInfo if we don't already have it
-                if (!gii) {
-                    gii = this.renderGroup._renderGroupInstancesInfo.get(this.modelKey);
+        public _getLastIndexInDataBuffer(): number {
+            for (let part of this._instanceDataParts) {
+                if (part) {
+                    return part.dataElements[part.dataElements.length-1].offset / part.dataBuffer.stride;
                 }
+            }
+            return null;
+        }
 
-                // For each Instance Data part, refresh it to update the data in the DynamicFloatArray
-                for (let part of this._instanceDataParts) {
-                    // Check if we need to allocate data elements (hidden prim which becomes visible again)
-                    if (this._visibilityChanged && !part.dataElements) {
-                        part.allocElements();
+        // This internal method is mainly used for transparency processing
+        public _getNextPrimZOrder(): number {
+            let length = this._instanceDataParts.length;
+            for (let i = 0; i < length; i++) {
+                let part = this._instanceDataParts[i];
+                if (part) {
+                    let stride = part.dataBuffer.stride;
+                    let lastElementOffset = part.dataElements[part.dataElements.length - 1].offset;
+
+                    // check if it's the last in the DFA
+                    if (part.dataBuffer.totalElementCount * stride <= lastElementOffset) {
+                        return null;
                     }
 
-                    InstanceClassInfo._CurCategories = gii._instancesPartsUsedShaderCategories[gii._partIndexFromId.get(part.id.toString())];
+                    // Return the Z of the next primitive that lies in the DFA
+                    return part.dataBuffer[lastElementOffset + stride + this.modelRenderCache._partData[i]._zBiasOffset];
+                }
+            }
+            return null;
+        }
+
+        // This internal method is mainly used for transparency processing
+        public _getPrevPrimZOrder(): number {
+            let length = this._instanceDataParts.length;
+            for (let i = 0; i < length; i++) {
+                let part = this._instanceDataParts[i];
+                if (part) {
+                    let stride = part.dataBuffer.stride;
+                    let firstElementOffset = part.dataElements[0].offset;
 
-                    // Will return false if the instance should not be rendered (not visible or other any reasons)
-                    if (!this.refreshInstanceDataPart(part)) {
-                        // Free the data element
-                        if (part.dataElements) {
-                            part.freeElements();
-                        }
+                    // check if it's the first in the DFA
+                    if (firstElementOffset === 0) {
+                        return null;
                     }
-                }
-                this._instanceDirtyFlags = 0;
 
-                gii._dirtyInstancesData = true;
-                this._visibilityChanged = false;    // Reset the flag as we've handled the case
+                    // Return the Z of the previous primitive that lies in the DFA
+                    return part.dataBuffer[firstElementOffset - stride + this.modelRenderCache._partData[i]._zBiasOffset];
+                }
             }
+            return null;
         }
 
         /**
@@ -546,13 +680,28 @@
             return res;
         }
 
-        protected getDataPartEffectInfo(dataPartId: number, vertexBufferAttributes: string[]): { attributes: string[], uniforms: string[], defines: string } {
+        /**
+         * Get the info for a given effect based on the dataPart metadata
+         * @param dataPartId partId in part list to get the info
+         * @param vertexBufferAttributes vertex buffer attributes to manually add
+         * @param useInstanced specified if Instanced Array should be used, if null the engine caps will be used (so true if WebGL supports it, false otherwise), but you have the possibility to override the engine capability. However, if you manually set true but the engine does not support Instanced Array, this method will return null
+         */
+        protected getDataPartEffectInfo(dataPartId: number, vertexBufferAttributes: string[], useInstanced: boolean = null): { attributes: string[], uniforms: string[], defines: string } {
             let dataPart = Tools.first(this._instanceDataParts, i => i.id === dataPartId);
             if (!dataPart) {
                 return null;
             }
 
             let instancedArray = this.owner.supportInstancedArray;
+            if (useInstanced != null) {
+                // Check if the caller ask for Instanced Array and the engine does not support it, return null if it's the case
+                if (useInstanced && instancedArray === false) {
+                    return null;
+                }
+
+                // Use the caller's setting
+                instancedArray = useInstanced;
+            }
 
             let cti = dataPart.getClassTreeInfo();
             let categories = this.getUsedShaderCategories(dataPart);
@@ -570,7 +719,7 @@
             return this._modelRenderCache;
         }
 
-        protected createModelRenderCache(modelKey: string, isTransparent: boolean): ModelRenderCache {
+        protected createModelRenderCache(modelKey: string): ModelRenderCache {
             return null;
         }
 
@@ -628,15 +777,14 @@
 
             // Have to convert the coordinates to clip space which is ranged between [-1;1] on X and Y axis, with 0,0 being the left/bottom corner
             // Current coordinates are expressed in renderGroup coordinates ([0, renderGroup.actualSize.width|height]) with 0,0 being at the left/top corner
-            // RenderGroup Width and Height are multiplied by zBias because the VertexShader will multiply X and Y by W, which is 1/zBias. As we divide our coordinate by these Width/Height, we will also divide by the zBias to compensate the operation made by the VertexShader.
             // So for X: 
             //  - tx.x = value * 2 / width: is to switch from [0, renderGroup.width] to [0, 2]
-            //  - tx.w = (value * 2 / width) - 1: w stores the translation in renderGroup coordinates so (value * 2 / width) to switch to a clip space translation value. - 1 is to offset the overall [0;2] to [-1;1]. Don't forget it's -(1/zBias) and not -1 because everything need to be scaled by 1/zBias.
-            let w = size.width * zBias;
-            let h = size.height * zBias;
+            //  - tx.w = (value * 2 / width) - 1: w stores the translation in renderGroup coordinates so (value * 2 / width) to switch to a clip space translation value. - 1 is to offset the overall [0;2] to [-1;1].
+            let w = size.width;
+            let h = size.height;
             let invZBias = 1 / zBias;
-            let tx = new Vector4(t.m[0] * 2 / w, t.m[4] * 2 / w, 0/*t.m[8]*/, ((t.m[12] + offX) * 2 / w) - (invZBias));
-            let ty = new Vector4(t.m[1] * 2 / h, t.m[5] * 2 / h, 0/*t.m[9]*/, ((t.m[13] + offY) * 2 / h) - (invZBias));
+            let tx = new Vector4(t.m[0] * 2 / w, t.m[4] * 2 / w, 0/*t.m[8]*/, ((t.m[12] + offX) * 2 / w) - 1);
+            let ty = new Vector4(t.m[1] * 2 / h, t.m[5] * 2 / h, 0/*t.m[9]*/, ((t.m[13] + offY) * 2 / h) - 1);
             part.transformX = tx;
             part.transformY = ty;
             part.origin = this.origin;
@@ -647,8 +795,10 @@
 
         private _modelRenderCache: ModelRenderCache;
         private _modelRenderInstanceID: string;
+        private _transparentPrimitiveInfo: TransparentPrimitiveInfo;
 
         protected _instanceDataParts: InstanceDataBase[];
+        protected _isAlphaTest: boolean;
         protected _isTransparent: boolean;
     }
 

+ 4 - 0
src/Canvas2d/babylon.smartPropertyPrim.ts

@@ -297,6 +297,10 @@
         private static propChangedInfo = new PropertyChangedInfo();
 
         public markAsDirty(propertyName: string) {
+            if (this.isDisposed) {
+                return;
+            }
+
             let i = propertyName.indexOf(".");
             if (i !== -1) {
                 propertyName = propertyName.substr(0, i);

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

@@ -1,38 +1,48 @@
 module BABYLON {
     export class Sprite2DRenderCache extends ModelRenderCache {
-        vb: WebGLBuffer;
-        ib: WebGLBuffer;
-        instancingAttributes: InstancingAttributeInfo[];
-
-        texture: Texture;
-        effect: Effect;
+        effectsReady: boolean                           = false;
+        vb: WebGLBuffer                                 = null;
+        ib: WebGLBuffer                                 = null;
+        instancingAttributes: InstancingAttributeInfo[] = null;
+        texture: Texture                                = null;
+        effect: Effect                                  = null;
+        effectInstanced: Effect                         = null;
 
         render(instanceInfo: GroupInstanceInfo, context: Render2DContext): boolean {
-            // Do nothing if the shader is still loading/preparing
-            if (!this.effect.isReady() || !this.texture.isReady()) {
-                return false;
+            // Do nothing if the shader is still loading/preparing 
+            if (!this.effectsReady) {
+                if ((!this.effect.isReady() || (this.effectInstanced && !this.effectInstanced.isReady()))) {
+                    return false;
+                }
+                this.effectsReady = true;
             }
 
             // Compute the offset locations of the attributes in the vertex shader that will be mapped to the instance buffer data
-            var engine = instanceInfo._owner.owner.engine;
+            var engine = instanceInfo.owner.owner.engine;
 
-            engine.enableEffect(this.effect);
-            this.effect.setTexture("diffuseSampler", this.texture);
-            engine.bindBuffers(this.vb, this.ib, [1], 4, this.effect);
+            let effect = context.useInstancing ? this.effectInstanced : this.effect;
+
+            engine.enableEffect(effect);
+            effect.setTexture("diffuseSampler", this.texture);
+            engine.bindBuffers(this.vb, this.ib, [1], 4, effect);
 
             var cur = engine.getAlphaMode();
-            engine.setAlphaMode(Engine.ALPHA_COMBINE);
-            let count = instanceInfo._instancesPartsData[0].usedElementCount;
-            if (instanceInfo._owner.owner.supportInstancedArray) {
+
+            if (context.renderMode !== Render2DContext.RenderModeOpaque) {
+                engine.setAlphaMode(Engine.ALPHA_COMBINE);
+            }
+
+            let pid = context.groupInfoPartData[0];
+            if (context.useInstancing) {
                 if (!this.instancingAttributes) {
-                    this.instancingAttributes = this.loadInstancingAttributes(Sprite2D.SPRITE2D_MAINPARTID, this.effect);
+                    this.instancingAttributes = this.loadInstancingAttributes(Sprite2D.SPRITE2D_MAINPARTID, effect);
                 }
-                engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[0], null, this.instancingAttributes);
-                engine.draw(true, 0, 6, count);
-                engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[0], this.instancingAttributes);
+                engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingAttributes);
+                engine.draw(true, 0, 6, pid._partData.usedElementCount);
+                engine.unBindInstancesBuffer(pid._partBuffer, this.instancingAttributes);
             } else {
-                for (let i = 0; i < count; i++) {
-                    this.setupUniforms(this.effect, 0, instanceInfo._instancesPartsData[0], i);
+                for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) {
+                    this.setupUniforms(effect, 0, pid._partData, i);
                     engine.draw(true, 0, 6);
                 }
             }
@@ -67,6 +77,11 @@
                 this.effect = null;
             }
 
+            if (this.effectInstanced) {
+                this._engine._releaseEffect(this.effectInstanced);
+                this.effectInstanced = null;
+            }
+
             return true;
         }
     }
@@ -234,8 +249,8 @@
             return sprite;
         }
 
-        protected createModelRenderCache(modelKey: string, isTransparent: boolean): ModelRenderCache {
-            let renderCache = new Sprite2DRenderCache(this.owner.engine, modelKey, isTransparent);
+        protected createModelRenderCache(modelKey: string): ModelRenderCache {
+            let renderCache = new Sprite2DRenderCache(this.owner.engine, modelKey);
             return renderCache;
         }
 
@@ -261,10 +276,14 @@
 
             renderCache.texture = this.texture;
 
-            var ei = this.getDataPartEffectInfo(Sprite2D.SPRITE2D_MAINPARTID, ["index"]);
-            renderCache.effect = engine.createEffect({ vertex: "sprite2d", fragment: "sprite2d" }, ei.attributes, ei.uniforms, ["diffuseSampler"], ei.defines, null, e => {
-//                renderCache.setupUniformsLocation(e, ei.uniforms, Sprite2D.SPRITE2D_MAINPARTID);
-            });
+            // Get the instanced version of the effect, if the engine does not support it, null is return and we'll only draw on by one
+            let ei = this.getDataPartEffectInfo(Sprite2D.SPRITE2D_MAINPARTID, ["index"], true);
+            if (ei) {
+                renderCache.effectInstanced = engine.createEffect("sprite2d", ei.attributes, ei.uniforms, ["diffuseSampler"], ei.defines, null);
+            }
+
+            ei = this.getDataPartEffectInfo(Sprite2D.SPRITE2D_MAINPARTID, ["index"], false);
+            renderCache.effect = engine.createEffect("sprite2d", ei.attributes, ei.uniforms, ["diffuseSampler"], ei.defines, null);
 
             return renderCache;
         }

+ 51 - 32
src/Canvas2d/babylon.text2d.ts

@@ -1,44 +1,53 @@
 module BABYLON {
     export class Text2DRenderCache extends ModelRenderCache {
-        vb: WebGLBuffer;
-        ib: WebGLBuffer;
-        instancingAttributes: InstancingAttributeInfo[];
-        fontTexture: FontTexture;
-        effect: Effect;
+        effectsReady: boolean                           = false;
+        vb: WebGLBuffer                                 = null;
+        ib: WebGLBuffer                                 = null;
+        instancingAttributes: InstancingAttributeInfo[] = null;
+        fontTexture: FontTexture                        = null;
+        effect: Effect                                  = null;
+        effectInstanced: Effect                         = null;
 
         render(instanceInfo: GroupInstanceInfo, context: Render2DContext): boolean {
-            // Do nothing if the shader is still loading/preparing
-            if (!this.effect.isReady() || !this.fontTexture.isReady()) {
-                return false;
+            // Do nothing if the shader is still loading/preparing 
+            if (!this.effectsReady) {
+                if ((!this.effect.isReady() || (this.effectInstanced && !this.effectInstanced.isReady()))) {
+                    return false;
+                }
+                this.effectsReady = true;
             }
 
-            // Compute the offset locations of the attributes in the vertexshader that will be mapped to the instance buffer data
-            if (!this.instancingAttributes) {
-                this.instancingAttributes = this.loadInstancingAttributes(Text2D.TEXT2D_MAINPARTID, this.effect);
-            }
-            var engine = instanceInfo._owner.owner.engine;
+            var engine = instanceInfo.owner.owner.engine;
 
             this.fontTexture.update();
 
-            engine.enableEffect(this.effect);
-            this.effect.setTexture("diffuseSampler", this.fontTexture);
-            engine.bindBuffers(this.vb, this.ib, [1], 4, this.effect);
-
-            var cur = engine.getAlphaMode();
-            engine.setAlphaMode(Engine.ALPHA_ADD);
-            let count = instanceInfo._instancesPartsData[0].usedElementCount;
-            if (instanceInfo._owner.owner.supportInstancedArray) {
-                engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[0], null, this.instancingAttributes);
-                engine.draw(true, 0, 6, count);
-                engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[0], this.instancingAttributes);
+            let effect = context.useInstancing ? this.effectInstanced : this.effect;
+
+            engine.enableEffect(effect);
+            effect.setTexture("diffuseSampler", this.fontTexture);
+            engine.bindBuffers(this.vb, this.ib, [1], 4, effect);
+
+            var curAlphaMode = engine.getAlphaMode();
+
+            engine.setAlphaMode(Engine.ALPHA_COMBINE, true);
+
+            let pid = context.groupInfoPartData[0];
+            if (context.useInstancing) {
+                if (!this.instancingAttributes) {
+                    this.instancingAttributes = this.loadInstancingAttributes(Text2D.TEXT2D_MAINPARTID, effect);
+                }
+
+                engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingAttributes);
+                engine.draw(true, 0, 6, pid._partData.usedElementCount);
+                engine.unBindInstancesBuffer(pid._partBuffer, this.instancingAttributes);
             } else {
-                for (let i = 0; i < count; i++) {
-                    this.setupUniforms(this.effect, 0, instanceInfo._instancesPartsData[0], i);
+                for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) {
+                    this.setupUniforms(effect, 0, pid._partData, i);
                     engine.draw(true, 0, 6);
                 }
             }
 
-            engine.setAlphaMode(cur);
+            engine.setAlphaMode(curAlphaMode);
 
             return true;
         }
@@ -68,6 +77,11 @@
                 this.effect = null;
             }
 
+            if (this.effectInstanced) {
+                this._engine._releaseEffect(this.effectInstanced);
+                this.effectInstanced = null;
+            }
+
             return true;
         }
 
@@ -197,7 +211,7 @@
             this.text = text;
             this.areaSize = areaSize;
             this._tabulationSize = tabulationSize;
-            this._isTransparent = true;
+            this.isAlphaTest = true;
         }
 
         /**
@@ -235,8 +249,8 @@
             return true;
         }
 
-        protected createModelRenderCache(modelKey: string, isTransparent: boolean): ModelRenderCache {
-            let renderCache = new Text2DRenderCache(this.owner.engine, modelKey, isTransparent);
+        protected createModelRenderCache(modelKey: string): ModelRenderCache {
+            let renderCache = new Text2DRenderCache(this.owner.engine, modelKey);
             return renderCache;
         }
 
@@ -262,8 +276,13 @@
 
             renderCache.ib = engine.createIndexBuffer(ib);
 
-            // Effects
-            let ei = this.getDataPartEffectInfo(Text2D.TEXT2D_MAINPARTID, ["index"]);
+            // Get the instanced version of the effect, if the engine does not support it, null is return and we'll only draw on by one
+            let ei = this.getDataPartEffectInfo(Text2D.TEXT2D_MAINPARTID, ["index"], true);
+            if (ei) {
+                renderCache.effectInstanced = engine.createEffect("text2d", ei.attributes, ei.uniforms, ["diffuseSampler"], ei.defines, null);
+            }
+
+            ei = this.getDataPartEffectInfo(Text2D.TEXT2D_MAINPARTID, ["index"], false);
             renderCache.effect = engine.createEffect("text2d", ei.attributes, ei.uniforms, ["diffuseSampler"], ei.defines, null);
 
             return renderCache;

+ 1 - 1
src/Shaders/ellipse2d.vertex.fx

@@ -103,6 +103,6 @@ void main(void) {
 	pos.xy = (pos2.xy - origin) * properties.xy;
 	pos.z = 1.0;
 	pos.w = 1.0;
-	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, zBias.y);
+	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, 1);
 
 }

+ 1 - 1
src/Shaders/lines2d.vertex.fx

@@ -63,6 +63,6 @@ void main(void) {
 	pos.xy = position.xy - ((origin.xy-vec2(0.5,0.5)) * (boundingMax - boundingMin));
 	pos.z = 1.0;
 	pos.w = 1.0;
-	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, zBias.y);
+	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, 1);
 
 }

+ 1 - 1
src/Shaders/rect2d.vertex.fx

@@ -201,6 +201,6 @@ void main(void) {
 	pos.xy = (pos2.xy - origin) * properties.xy;
 	pos.z = 1.0;
 	pos.w = 1.0;
-	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, zBias.y);
+	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, 1);
 
 }

+ 1 - 1
src/Shaders/sprite2d.fragment.fx

@@ -3,7 +3,7 @@ uniform sampler2D diffuseSampler;
 
 void main(void) {
 	vec4 color = texture2D(diffuseSampler, vUV);
-	if (color.w == 0.0) {
+	if (color.a == 0.05) {
 		discard;
 	}
 	gl_FragColor = color;

+ 1 - 1
src/Shaders/sprite2d.vertex.fx

@@ -63,5 +63,5 @@ void main(void) {
 	pos.xy = (pos2.xy * sizeUV * textureSize) - origin;
 	pos.z = 1.0;
 	pos.w = 1.0;
-	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, zBias.y);
+	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, 1);
 }	

+ 2 - 0
src/Shaders/text2d.fragment.fx

@@ -6,5 +6,7 @@ uniform sampler2D diffuseSampler;
 
 void main(void) {
 	vec4 color = texture2D(diffuseSampler, vUV);
+	if (color.a < 0.05)
+		discard;
 	gl_FragColor = color*vColor;
 }

+ 1 - 1
src/Shaders/text2d.vertex.fx

@@ -55,5 +55,5 @@ void main(void) {
 	pos.xy = (pos2.xy * sizeUV * textureSize) - origin;
 	pos.z = 1.0;
 	pos.w = 1.0;
-	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, zBias.y);
+	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, 1);
 }

+ 129 - 0
src/Tools/babylon.dynamicFloatArray.ts

@@ -80,6 +80,7 @@
             }
 
             // If the buffer is already packed the last used will always be lower than the first free
+            // The opposite may not be true, we can have a lastUsed greater than firstFree but the array still packed, because when an element is freed, lastUsed is not updated (for speed reason) so we may have a lastUsed of a freed element. But that's ok, well soon realize this case.
             if (this._lastUsed < this._firstFree) {
                 let elementsBuffer = this.buffer.subarray(0, this._lastUsed + this._stride);
                 return elementsBuffer;
@@ -242,10 +243,138 @@
             return this._stride;
         }
 
+        compareValueOffset: number = null;
+        sortingAscending: boolean = true;
+
+        public sort(): boolean {
+            if (!this.compareValueOffset) {
+                throw new Error("The DynamicFloatArray.sort() method needs a valid 'compareValueOffset' property");
+            }
+
+            let count = this.usedElementCount;
+
+            // Do we have to (re)create the sort table?
+            if (!this._sortTable || this._sortTable.length < count) {
+                // Small heuristic... We don't want to allocate totalElementCount right away because it may have 50 for 3 used elements, but on the other side we don't want to allocate just 3 when we just need 2, so double this value to give us some air to breath...
+                let newCount = Math.min(this.totalElementCount, count * 2);
+
+                this._sortTable = new Array<SortInfo>(newCount);
+                this._sortedTable = new Array<SortInfo>(newCount);
+            }
+
+            // Because, you know...
+            this.pack();
+
+            //let stride = this.stride;
+            //for (let i = 0; i < count; i++) {
+            //    let si = this._sortTable[i];
+            //    if (!si) {
+            //        si = new SortInfo();
+            //        this._sortTable[i] = si;
+            //    }
+            //    si.entry = this._allEntries[i];
+            //    si.compareData = this.buffer[si.entry.offset + this.compareValueOffset];
+            //    si.swapedOffset = null;
+
+            //    this._sortedTable[i] = si;
+            //}
+
+            let curOffset = 0;
+            let stride = this.stride;
+            for (let i = 0; i < count; i++ , curOffset += stride) {
+                let si = this._sortTable[i];
+                if (!si) {
+                    si = new SortInfo();
+                    this._sortTable[i] = si;
+                }
+                si.compareData = this.buffer[curOffset + this.compareValueOffset];
+                si.offset = curOffset;
+                si.swapedOffset = null;
+
+                this._sortedTable[i] = si;
+            }
+
+            // Let's sort the sorted table, we want to keep a track of the original one (that's why we have two buffers)
+            if (this.sortingAscending) {
+                this._sortedTable.sort((a, b) => a.compareData - b.compareData);
+            } else {
+                this._sortedTable.sort((a, b) => b.compareData - a.compareData);
+            }
+
+            let swapElements = (src: number, dst: number) => {
+                for (let i = 0; i < stride; i++) {
+                    let tps = this.buffer[dst + i];
+                    this.buffer[dst + i] = this.buffer[src + i];
+                    this.buffer[src + i] = tps;
+                }
+            }
+
+            // The fun part begin, sortedTable give us the ordered layout to obtain, to get that we have to move elements, but when we move an element: 
+            //  it replaces an existing one.I don't want to allocate a new Float32Array and do a raw copy, because it's awful (GC - wise), 
+            //  and I still want something with a good algorithm complexity.
+            // So here's the deal: we are going to swap elements, but we have to track the change of location of the element being replaced, 
+            //  we need sortTable for that, it contains the original layout of SortInfo object, not the sorted one.
+            // The best way is to use an extra field in SortInfo, because potentially every element can be replaced.
+            // When we'll look for and element, we'll check if its swapedOffset is set, if so we reiterate the operation with the one there 
+            //  until we find a SortInfo object without a swapedOffset which means we got the right location
+            // Yes, we may have to do multiple iterations to find the right location, but hey, it won't be huge: <3 in most cases, and it's better 
+            //  than a double allocation of the whole float32Array or a O(n²/2) typical algorithm.
+
+            for (let i = 0; i < count; i++) {
+                // Get the element to move
+                let sourceSI = this._sortedTable[i];
+                let destSI = this._sortTable[i];
+
+                let sourceOff = sourceSI.offset;
+
+                // If the source changed location, find the new one
+                if (sourceSI.swapedOffset) {
+                    // Follow the swapedOffset until there's none, it will mean that curSI contains the new location in its offset member
+                    let curSI = sourceSI;
+                    while (curSI.swapedOffset) {
+
+                        curSI = this._sortTable[curSI.swapedOffset / stride];
+                    }
+
+                    // Finally get the right location
+                    sourceOff = curSI.offset;
+                }
+
+                // Tag the element being replaced with its new location
+                destSI.swapedOffset = sourceOff;
+
+                // Swap elements (only if needed)
+                if (sourceOff !== destSI.offset) {
+                    swapElements(sourceOff, destSI.offset);
+                }
+
+                // Update the offset in the corresponding DFAE
+                //sourceSI.entry.offset = destSI.entry.offset;
+                this._allEntries[sourceSI.offset / stride].offset = destSI.offset;
+            }
+
+            this._allEntries.sort((a, b) => a.offset - b.offset);
+            return true;
+        }
+
         private _allEntries: Array<DynamicFloatArrayElementInfo>;
         private _freeEntries: Array<DynamicFloatArrayElementInfo>;
         private _stride: number;
         private _lastUsed: number;
         private _firstFree: number;
+
+        private _sortTable: SortInfo[];
+        private _sortedTable: SortInfo[];
+    }
+
+    class SortInfo {
+        constructor() {
+            this.compareData = this.offset = this.swapedOffset = null;
+        }
+
+        compareData: number;
+        //entry: DynamicFloatArrayElementInfo;
+        offset: number;
+        swapedOffset: number;
     }
 }

+ 6 - 8
src/babylon.engine.ts

@@ -1376,7 +1376,7 @@
             this._gl.colorMask(enable, enable, enable, enable);
         }
 
-        public setAlphaMode(mode: number): void {
+        public setAlphaMode(mode: number, noDepthWriteChange: boolean=false): void {
             if (this._alphaMode === mode) {
                 return;
             }
@@ -1387,37 +1387,33 @@
                     this._alphaState.alphaBlend = false;
                     break;
                 case Engine.ALPHA_COMBINE:
-                    this.setDepthWrite(false);
                     this._alphaState.setAlphaBlendFunctionParameters(this._gl.SRC_ALPHA, this._gl.ONE_MINUS_SRC_ALPHA, this._gl.ONE, this._gl.ONE);
                     this._alphaState.alphaBlend = true;
                     break;
                 case Engine.ALPHA_ONEONE:
-                    this.setDepthWrite(false);
                     this._alphaState.setAlphaBlendFunctionParameters(this._gl.ONE, this._gl.ONE, this._gl.ZERO, this._gl.ONE);
                     this._alphaState.alphaBlend = true;
                     break;
                 case Engine.ALPHA_ADD:
-                    this.setDepthWrite(false);
                     this._alphaState.setAlphaBlendFunctionParameters(this._gl.SRC_ALPHA, this._gl.ONE, this._gl.ZERO, this._gl.ONE);
                     this._alphaState.alphaBlend = true;
                     break;
                 case Engine.ALPHA_SUBTRACT:
-                    this.setDepthWrite(false);
                     this._alphaState.setAlphaBlendFunctionParameters(this._gl.ZERO, this._gl.ONE_MINUS_SRC_COLOR, this._gl.ONE, this._gl.ONE);
                     this._alphaState.alphaBlend = true;
                     break;
                 case Engine.ALPHA_MULTIPLY:
-                    this.setDepthWrite(false);
                     this._alphaState.setAlphaBlendFunctionParameters(this._gl.DST_COLOR, this._gl.ZERO, this._gl.ONE, this._gl.ONE);
                     this._alphaState.alphaBlend = true;
                     break;
                 case Engine.ALPHA_MAXIMIZED:
-                    this.setDepthWrite(false);
                     this._alphaState.setAlphaBlendFunctionParameters(this._gl.SRC_ALPHA, this._gl.ONE_MINUS_SRC_COLOR, this._gl.ONE, this._gl.ONE);
                     this._alphaState.alphaBlend = true;
                     break;
             }
-
+            if (!noDepthWriteChange) {
+                this.setDepthWrite(false);
+            }
             this._alphaMode = mode;
         }
 
@@ -1832,6 +1828,8 @@
             if (generateDepthBuffer) {
                 texture._depthBuffer = depthBuffer;
             }
+            texture._baseWidth = width;
+            texture._baseHeight = height;
             texture._width = width;
             texture._height = height;
             texture.isReady = true;