瀏覽代碼

Merge pull request #1166 from nockawa/transbranch

Canvas 2D: transparency
David Catuhe 9 年之前
父節點
當前提交
4c9449c192

+ 28 - 17
src/Canvas2d/babylon.canvas2d.ts

@@ -55,16 +55,28 @@
          * @param scene the Scene that owns the Canvas
          * Options:
          *  - id: a text identifier, for information purpose only
-         *  - pos: the position of the canvas, relative from the bottom/left of the scene's viewport
-         *  - size: the Size of the canvas. If null two behaviors depend on the cachingStrategy: if it's CACHESTRATEGY_CACHECANVAS then it will always auto-fit the rendering device, in all the other modes it will fit the content of the Canvas
-         *  - cachingStrategy: either CACHESTRATEGY_TOPLEVELGROUPS, CACHESTRATEGY_ALLGROUPS, CACHESTRATEGY_CANVAS, CACHESTRATEGY_DONTCACHE. Please refer to their respective documentation for more information. Default is Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS
+         *  - pos: the position of the canvas, relative from the bottom/left of the scene's viewport. Alternatively you can set the x and y properties directly. Default value is [0, 0]
+         *  - size: the Size of the canvas. Alternatively the width and height properties can be set. If null two behaviors depend on the cachingStrategy: if it's CACHESTRATEGY_CACHECANVAS then it will always auto-fit the rendering device, in all the other modes it will fit the content of the Canvas
+         *  - cachingStrategy: either CACHESTRATEGY_TOPLEVELGROUPS, CACHESTRATEGY_ALLGROUPS, CACHESTRATEGY_CANVAS, CACHESTRATEGY_DONTCACHE. Please refer to their respective documentation for more information. Default is Canvas2D.CACHESTRATEGY_DONTCACHE
          *  - enableInteraction: if true the pointer events will be listened and rerouted to the appropriate primitives of the Canvas2D through the Prim2DBase.onPointerEventObservable observable property.
+         *  - isVisible: true if the canvas must be visible, false for hidden. Default is true.
+         *  - marginTop/Left/Right/Bottom: define the margin for the corresponding edge, if all of them are null, margin is not used in layout computing. Default Value is null for each.
+         *  - hAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
+         *  - vAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
          */
-        static CreateScreenSpace(scene: Scene, options: { id?: string, pos?: Vector2, origin?: Vector2, size?: Size, cachingStrategy?: number, enableInteraction?: boolean }): Canvas2D {
+        static CreateScreenSpace(scene: Scene, options: { id?: string, x?: number, y?: number, position?: Vector2, origin?: Vector2, width?: number, height?: number, size?: Size, cachingStrategy?: number, enableInteraction?: boolean, isVisible?: boolean, marginTop?: number, marginLeft?: number, marginRight?: number, marginBottom?: number, hAlignment?: number, vAlignment?: number }): Canvas2D {
             let c = new Canvas2D();
-            c.setupCanvas(scene, options && options.id || null, options && options.size || null, true, options && options.cachingStrategy || Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS, options && options.enableInteraction || true);
-            c.position = options && options.pos || Vector2.Zero();
-            c.origin = options && options.origin || Vector2.Zero();
+
+            if (!options) {
+                c.setupCanvas(scene, null, null, true, Canvas2D.CACHESTRATEGY_DONTCACHE, true, Vector2.Zero(), true, null, null, null, null, null, null);
+                c.position = Vector2.Zero();
+            } else { 
+                let pos = options.position || new Vector2(options.x || 0, options.y || 0);
+                let size = (!options.size && !options.width && !options.height) ? null : (options.size || (new Size(options.width || 0, options.height || 0)));
+
+                c.setupCanvas(scene, options.id || null, size, true, options.cachingStrategy || Canvas2D.CACHESTRATEGY_DONTCACHE, options.enableInteraction || true, options.origin || Vector2.Zero(), options.isVisible || true, options.marginTop, options.marginLeft, options.marginRight, options.marginBottom, options.hAlignment || Prim2DBase.HAlignLeft, options.vAlignment || Prim2DBase.VAlignTop);
+                c.position = pos;
+            }
 
             return c;
         }
@@ -83,8 +95,9 @@
          * TIPS: if you want a renderScaleFactor independent reference of frame, create a child Group2D in the Canvas with position 0,0 and size set to null, then set its scale property to the same amount than the renderScaleFactor, put all your primitive inside using coordinates regarding the size property you pick for the Canvas and you'll be fine.
          * - sideOrientation: Unexpected behavior occur if the value is different from Mesh.DEFAULTSIDE right now, so please use this one, which is the default.
          * - cachingStrategy Must be CACHESTRATEGY_CANVAS for now, which is the default.
+         * - isVisible: true if the canvas must be visible, false for hidden. Default is true.
          */
-        static CreateWorldSpace(scene: Scene, size: Size, options: { id?: string, position?: Vector3, rotation?: Quaternion, renderScaleFactor?: number, sideOrientation?: number, cachingStrategy?: number, enableInteraction?: boolean}): Canvas2D {
+        static CreateWorldSpace(scene: Scene, size: Size, options: { id?: string, position?: Vector3, rotation?: Quaternion, renderScaleFactor?: number, sideOrientation?: number, cachingStrategy?: number, enableInteraction?: boolean, isVisible?: boolean}): Canvas2D {
 
             let cs = options && options.cachingStrategy || Canvas2D.CACHESTRATEGY_CANVAS;
 
@@ -99,7 +112,7 @@
             let id = options && options.id || null;
             let rsf = options && options.renderScaleFactor || 1;
             let c = new Canvas2D();
-            c.setupCanvas(scene, id, new Size(size.width * rsf, size.height * rsf), false, cs, options && options.enableInteraction || true);
+            c.setupCanvas(scene, id, new Size(size.width * rsf, size.height * rsf), false, cs, options && options.enableInteraction || true, Vector2.Zero(), options && options.isVisible || true, null, null, null, null, null, null);
 
             let plane = new WorldSpaceCanvas2D(id, scene, c);
             let vertexData = VertexData.CreatePlane({ width: size.width / 2, height: size.height / 2, sideOrientation: options && options.sideOrientation || Mesh.DEFAULTSIDE });
@@ -119,7 +132,7 @@
             return c;
         }
 
-        protected setupCanvas(scene: Scene, name: string, size: Size, isScreenSpace: boolean, cachingstrategy: number, enableInteraction: boolean) {
+        protected setupCanvas(scene: Scene, name: string, size: Size, isScreenSpace: boolean, cachingstrategy: number, enableInteraction: boolean, origin: Vector2, isVisible: boolean, marginTop: number, marginLeft: number, marginRight: number, marginBottom: number, hAlign: number, vAlign: number) {
             let engine = scene.getEngine();
             this._fitRenderingDevice = !size;
             if (!size) {
@@ -131,9 +144,9 @@
             this._capturedPointers = new StringDictionary<Prim2DBase>();
             this._pickStartingPosition = Vector2.Zero();
 
-            this.setupGroup2D(this, null, name, Vector2.Zero(), null, size, this._cachingStrategy===Canvas2D.CACHESTRATEGY_ALLGROUPS ? Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE : Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY);
+            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;
@@ -813,8 +826,7 @@
                 }
             }
 
-            var context = new Render2DContext();
-            context.forceRefreshPrimitive = false;
+            var context = new PreapreRender2DContext();
 
             ++this._globalTransformProcessStep;
             this.updateGlobalTransVis(false);
@@ -836,12 +848,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();
             }
         }
 

+ 110 - 64
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);
+                }
+
+                let effect = context.useInstancing ? this.effectFillInstanced : this.effectFill;
 
-                engine.enableEffect(this.effectFill);
-                engine.bindBuffersDirectly(this.fillVB, this.fillIB, [1], 4, this.effectFill);
-                let count = instanceInfo._instancesPartsData[partIndex].usedElementCount;
-                if (instanceInfo._owner.owner.supportInstancedArray) {
+                engine.enableEffect(effect);
+                engine.bindBuffersDirectly(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.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingFillAttributes);
+                    engine.draw(true, 0, this.fillIndicesCount, pid._partData.usedElementCount);
                     engine.unbindInstanceAttributes();
                 } 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];
 
-                engine.enableEffect(this.effectBorder);
-                engine.bindBuffersDirectly(this.borderVB, this.borderIB, [1], 4, this.effectBorder);
-                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.effectBorderInstanced : this.effectBorder;
+
+                engine.enableEffect(effect);
+                engine.bindBuffersDirectly(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.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingBorderAttributes);
+                    engine.draw(true, 0, this.borderIndicesCount, pid._partData.usedElementCount);
                     engine.unbindInstanceAttributes();
                 } 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;
         }
     }
@@ -181,8 +203,8 @@
             BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo, this.origin);
         }
 
-        protected setupEllipse2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, size: Size, subdivisions: number=64, fill?: IBrush2D, border?: IBrush2D, borderThickness: number = 1) {
-            this.setupShape2D(owner, parent, id, position, origin, true, fill, border, borderThickness);
+        protected setupEllipse2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, size: Size, subdivisions: number, fill: IBrush2D, border: IBrush2D, borderThickness: number, isVisible: boolean, marginTop: number, marginLeft: number, marginRight: number, marginBottom: number, vAlignment: number, hAlignment: number) {
+            this.setupShape2D(owner, parent, id, position, origin, isVisible, fill, border, borderThickness, marginTop, marginLeft, marginRight, marginBottom, hAlignment, vAlignment);
             this.size = size;
             this.subdivisions = subdivisions;
         }
@@ -192,33 +214,43 @@
          * @param parent the parent primitive, must be a valid primitive (or the Canvas)
          * options:
          *  - id: a text identifier, for information purpose
-         *  - x: the X position relative to its parent, default is 0
-         *  - y: the Y position relative to its parent, default is 0
+         *  - position: the X & Y positions relative to its parent. Alternatively the x and y properties can be set. Default is [0;0]
          *  - origin: define the normalized origin point location, default [0.5;0.5]
-         *  - width: the width of the ellipse, default is 10
-         *  - height: the height of the ellipse, default is 10
+         *  - size: the size of the group. Alternatively the width and height properties can be set. Default will be [10;10].
          *  - subdivision: the number of subdivision to create the ellipse perimeter, default is 64.
          *  - fill: the brush used to draw the fill content of the ellipse, you can set null to draw nothing (but you will have to set a border brush), default is a SolidColorBrush of plain white.
          *  - border: the brush used to draw the border of the ellipse, you can set null to draw nothing (but you will have to set a fill brush), default is null.
          *  - borderThickness: the thickness of the drawn border, default is 1.
+         *  - isVisible: true if the primitive must be visible, false for hidden. Default is true.
+         *  - marginTop/Left/Right/Bottom: define the margin for the corresponding edge, if all of them are null, margin is not used in layout computing. Default Value is null for each.
+         *  - hAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
+         *  - vAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
          */
-        public static Create(parent: Prim2DBase, options: { id?: string, x?: number, y?: number, origin?: Vector2, width?: number, height?: number, subdivisions?: number, fill?: IBrush2D, border?: IBrush2D, borderThickness?: number }): Ellipse2D {
+        public static Create(parent: Prim2DBase, options: { id?: string, position?: Vector2, x?: number, y?: number, origin?: Vector2, size?: Size, width?: number, height?: number, subdivisions?: number, fill?: IBrush2D, border?: IBrush2D, borderThickness?: number, isVisible?: boolean, marginTop?: number, marginLeft?: number, marginRight?: number, marginBottom?: number, vAlignment?: number, hAlignment?: number}): Ellipse2D {
             Prim2DBase.CheckParent(parent);
 
-            let fill: IBrush2D;
-            if (options && options.fill !== undefined) {
-                fill = options.fill;
+            let ellipse = new Ellipse2D();
+
+            if (!options) {
+                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 {
-                fill = Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
+                let fill: IBrush2D;
+                if (options.fill === undefined) {
+                    fill = Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
+                } else {
+                    fill = options.fill;
+                }
+                let pos = options.position || new Vector2(options.x || 0, options.y || 0);
+                let size = options.size || (new Size(options.width || 10, options.height || 10));
+
+                ellipse.setupEllipse2D(parent.owner, parent, options.id || null, pos, options.origin || null, size, options.subdivisions || 64, 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);
             }
 
-            let ellipse = new Ellipse2D();
-            ellipse.setupEllipse2D(parent.owner, parent, options && options.id || null, new Vector2(options && options.x || 0, options && options.y || 0), options && options.origin || null, new Size(options && options.width || 10, options && options.height || 10), options && options.subdivisions || 64, fill, options && options.border || null, options && options.borderThickness || 1);
             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;
         }
 
@@ -247,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);
             }
 
@@ -279,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;

+ 422 - 109
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>();
         }
 
         /**
@@ -37,22 +34,36 @@
          * @param parent the parent primitive, must be a valid primitive (or the Canvas)
          * options:
          *  - id a text identifier, for information purpose
-         *  - position: the X & Y positions relative to its parent, default is [0;0]
+         *  - position: the X & Y positions relative to its parent. Alternatively the x and y properties can be set. Default is [0;0]
          *  - origin: define the normalized origin point location, default [0.5;0.5]
-         *  - size: the size of the group, if null the size will be computed from its content, default is null.
+         *  - size: the size of the group. Alternatively the width and height properties can be set. If null the size will be computed from its content, default is null.
          *  - cacheBehavior: Define how the group should behave regarding the Canvas's cache strategy, default is Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY
+         *  - isVisible: true if the group must be visible, false for hidden. Default is true.
+         *  - marginTop/Left/Right/Bottom: define the margin for the corresponding edge, if all of them are null, margin is not used in layout computing. Default Value is null for each.
+         *  - hAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
+         *  - vAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
          */
-        static CreateGroup2D(parent: Prim2DBase, options: { id?: string, position?: Vector2; origin?: Vector2, size?: Size, cacheBehavior?: number}): Group2D {
+        static CreateGroup2D(parent: Prim2DBase, options: { id?: string, position?: Vector2, x?: number, y?: number, origin?: Vector2, size?: Size, width?: number, height?: number, cacheBehavior?: number, isVisible?: boolean, marginTop?: number, marginLeft?: number, marginRight?: number, marginBottom?: number, vAlignment?: number, hAlignment?: number}): Group2D {
             Prim2DBase.CheckParent(parent);
+
+
             var g = new Group2D();
-            g.setupGroup2D(parent.owner, parent, options && options.id || null, options && options.position || Vector2.Zero(), options && options.origin || null, options && options.size || null, options && options.cacheBehavior || Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY);
 
+            if (!options) {
+                g.setupGroup2D(parent.owner, parent, null, Vector2.Zero(), null, null, true, Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY, null, null, null, null, null, null);
+            } else {
+                let pos = options.position || new Vector2(options.x || 0, options.y || 0);
+                let size = (!options.size && !options.width && !options.height) ? null : (options.size || (new Size(options.width || 0, options.height || 0)));
+                
+                g.setupGroup2D(parent.owner, parent, options.id || null, pos, options.origin || null, size, options.isVisible || true, options.cacheBehavior || Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY, options.marginTop, options.marginLeft, options.marginRight, options.marginBottom, options.hAlignment || Prim2DBase.HAlignLeft, options.vAlignment || Prim2DBase.VAlignTop);
+            }
+       
             return g;
         }
 
         static _createCachedCanvasGroup(owner: Canvas2D): Group2D {
             var g = new Group2D();
-            g.setupGroup2D(owner, null, "__cachedCanvasGroup__", Vector2.Zero(), null);
+            g.setupGroup2D(owner, null, "__cachedCanvasGroup__", Vector2.Zero(), Vector2.Zero(), null, true, Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY, null, null, null, null, null, null);
             g.origin = Vector2.Zero();
             return g;
             
@@ -62,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();
         }
 
@@ -82,35 +93,17 @@
                 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;
         }
 
-        protected setupGroup2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, size?: Size, cacheBehavior: number = Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY) {
+        protected setupGroup2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, size: Size, isVisible: boolean, cacheBehavior: number, marginTop: number, marginLeft: number, marginRight: number, marginBottom: number, hAlign: number, vAlign: number) {
             this._cacheBehavior = cacheBehavior;
-            this.setupPrim2DBase(owner, parent, id, position, origin);
+            this.setupPrim2DBase(owner, parent, id, position, origin, isVisible, marginTop, marginLeft, marginRight, marginBottom , hAlign, vAlign);
             this.size = size;
             this._viewportPosition = Vector2.Zero();
         }
@@ -185,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 {
@@ -215,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);
             }
 
@@ -262,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;
 
@@ -275,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 => {
@@ -288,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
@@ -316,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 || 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;
+                    }
+                });
+
+                // =======================================================================
+                // 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 || 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;
                     }
                 });
 
+                // =======================================================================
+                // 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;
 
@@ -377,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); // Create + bind
+                        pid._partBufferSize = neededSize;
+                        setDirty(false);
+
+                        // Update the WebGL buffer to match the new content of the instances data
+                        engine.updateArrayBuffer(instanceData);
+                    } else if (getDirty()) {
+                        // Update the WebGL buffer to match the new content of the instances data
+                        engine.bindArrayBuffer(pid._partBuffer);
+                        engine.updateArrayBuffer(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);
 
@@ -390,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}`);
             }
         }
@@ -487,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;
@@ -503,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;
     }
 
 }

+ 108 - 62
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.bindBuffersDirectly(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.bindBuffersDirectly(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.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingFillAttributes);
+                    engine.draw(true, 0, this.fillIndicesCount, pid._partData.usedElementCount);
                     engine.unbindInstanceAttributes();
                 } 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];
 
-                engine.enableEffect(this.effectBorder);
-                engine.bindBuffersDirectly(this.borderVB, this.borderIB, [2], 2 * 4, this.effectBorder);
-                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.effectBorderInstanced : this.effectBorder;
+
+                engine.enableEffect(effect);
+                engine.bindBuffersDirectly(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.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingBorderAttributes);
+                    engine.draw(true, 0, this.borderIndicesCount, pid._partData.usedElementCount);
                     engine.unbindInstanceAttributes();
                 } 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;
         }
     }
@@ -265,8 +287,8 @@
             BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo, this.origin);
         }
 
-        protected setupLines2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, points: Vector2[], fillThickness: number, startCap: number, endCap: number, fill: IBrush2D, border: IBrush2D, borderThickness: number, closed: boolean) {
-            this.setupShape2D(owner, parent, id, position, origin, true, fill, border, borderThickness);
+        protected setupLines2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, points: Vector2[], fillThickness: number, startCap: number, endCap: number, fill: IBrush2D, border: IBrush2D, borderThickness: number, closed: boolean, isVisible: boolean, marginTop: number, marginLeft: number, marginRight: number, marginBottom: number, vAlignment: number, hAlignment: number) {
+            this.setupShape2D(owner, parent, id, position, origin, isVisible, fill, border, borderThickness, marginTop, marginLeft, marginRight, marginBottom, hAlignment, vAlignment);
             this.fillThickness = fillThickness;
             this.startCap = startCap;
             this.endCap = endCap;
@@ -283,8 +305,7 @@
          * @param points an array that describe the points to use to draw the line, must contain at least two entries.
          * options:
          *  - id a text identifier, for information purpose
-         *  - x: the X position relative to its parent, default is 0
-         *  - y: the Y position relative to its parent, default is 0
+         *  - position: the X & Y positions relative to its parent. Alternatively the x and y properties can be set. Default is [0;0]
          *  - origin: define the normalized origin point location, default [0.5;0.5]
          *  - fillThickness: the thickness of the fill part of the line, can be null to draw nothing (but a border brush must be given), default is 1.
          *  - closed: if false the lines are said to be opened, the first point and the latest DON'T connect. if true the lines are said to be closed, the first and last point will be connected by a line. For instance you can define the 4 points of a rectangle, if you set closed to true a 4 edges rectangle will be drawn. If you set false, only three edges will be drawn, the edge formed by the first and last point won't exist. Default is false.
@@ -293,24 +314,35 @@
          *  - fill: the brush used to draw the fill content of the lines, you can set null to draw nothing (but you will have to set a border brush), default is a SolidColorBrush of plain white.
          *  - border: the brush used to draw the border of the lines, you can set null to draw nothing (but you will have to set a fill brush), default is null.
          *  - borderThickness: the thickness of the drawn border, default is 1.
+         *  - isVisible: true if the primitive must be visible, false for hidden. Default is true.
+         *  - marginTop/Left/Right/Bottom: define the margin for the corresponding edge, if all of them are null, margin is not used in layout computing. Default Value is null for each.
+         *  - hAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
+         *  - vAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
          */
-        public static Create(parent: Prim2DBase, points: Vector2[], options: { id?: string, x?: number, y?: number, origin?: Vector2, fillThickness?: number, closed?: boolean, startCap?: number, endCap?: number, fill?: IBrush2D, border?: IBrush2D, borderThickness?: number }): Lines2D {
+        public static Create(parent: Prim2DBase, points: Vector2[], options: { id?: string, position?: Vector2, x?: number, y?: number, origin?: Vector2, fillThickness?: number, closed?: boolean, startCap?: number, endCap?: number, fill?: IBrush2D, border?: IBrush2D, borderThickness?: number, isVisible?: boolean, marginTop?: number, marginLeft?: number, marginRight?: number, marginBottom?: number, vAlignment?: number, hAlignment?: number }): Lines2D {
             Prim2DBase.CheckParent(parent);
 
-            let fill: IBrush2D;
-            if (options && options.fill !== undefined) {
-                fill = options.fill;
+            let lines = new Lines2D();
+
+            if (!options) {
+                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 {
-                fill = Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
+                let fill: IBrush2D;
+                if (options.fill === undefined) {
+                    fill = Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
+                } else {
+                    fill = options.fill;
+                }
+                let pos = options.position || new Vector2(options.x || 0, options.y || 0);
+
+                lines.setupLines2D(parent.owner, parent, options.id || null, pos, options.origin || null, points, options.fillThickness || 1, options.startCap || 0, options.endCap || 0, fill, options.border || null, options.borderThickness || 1, options.closed || false, options.isVisible || true, options.marginTop || null, options.marginLeft || null, options.marginRight || null, options.marginBottom || null, options.vAlignment || null, options.hAlignment || null);                
             }
 
-            let lines = new Lines2D();
-            lines.setupLines2D(parent.owner, parent, options && options.id || null, new Vector2(options && options.x || 0, options && options.y || 0), options && options.origin || null, points, options && options.fillThickness || 1, options && options.startCap || 0, options && options.endCap || 0, fill, options && options.border || null, options && options.borderThickness || 0, options && options.closed || false);
             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;
         }
 
@@ -862,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?
@@ -907,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;
+    }
 }

+ 253 - 9
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.
@@ -222,6 +300,75 @@
         }
     }
 
+    export class PrimitiveMargin {
+        constructor(owner: Prim2DBase) {
+            this._owner = owner;
+            this._left = this._top = this._bottom = this.right = 0;
+        }
+
+        public get top(): number {
+            return this._top;
+        }
+
+        public set top(value: number) {
+            if (value === this._top) {
+                return;
+            }
+
+            this._top = value;
+            this._owner._marginChanged();
+        }
+
+        public get left(): number {
+            return this._left;
+        }
+
+        public set left(value: number) {
+            if (value === this._left) {
+                return;
+            }
+
+            this._left = value;
+            this._owner._marginChanged();
+        }
+
+        public get right(): number {
+            return this._right;
+        }
+
+        public set right(value: number) {
+            if (value === this._right) {
+                return;
+            }
+
+            this._right = value;
+            this._owner._marginChanged();
+        }
+
+        public get bottom(): number {
+            return this._bottom;
+        }
+
+        public set bottom(value: number) {
+            if (value === this._bottom) {
+                return;
+            }
+
+            this._bottom = value;
+            this._owner._marginChanged();
+        }
+
+        private _owner: Prim2DBase;
+        private _top: number;
+        private _left: number;
+        private _right: number;
+        private _bottom: number;
+
+        static Zero(owner: Prim2DBase): PrimitiveMargin {
+            return new PrimitiveMargin(owner);
+        }
+    }
+
     /**
      * Main class used for the Primitive Intersection API
      */
@@ -294,13 +441,31 @@
      * Base class for a Primitive of the Canvas2D feature
      */
     export class Prim2DBase extends SmartPropertyPrim {
-        static PRIM2DBASE_PROPCOUNT: number = 10;
-
-        protected setupPrim2DBase(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, isVisible: boolean = true) {
+        static PRIM2DBASE_PROPCOUNT: number = 12;
+
+        public static get HAlignLeft():    number { return Prim2DBase._hAlignLeft;   }
+        public static get HAlignCenter():  number { return Prim2DBase._hAlignCenter; }
+        public static get HAlignRight():   number { return Prim2DBase._hAlignRight;  }
+        public static get HAlignStretch(): number { return Prim2DBase._hAlignStretch;}
+        public static get VAlignTop():     number { return Prim2DBase._vAlignTop;    }
+        public static get VAlignCenter():  number { return Prim2DBase._vAlignCenter; }
+        public static get VAlignBottom():  number { return Prim2DBase._vAlignBottom; }
+        public static get VAlignStretch(): number { return Prim2DBase._vAlignStretch;}
+
+        protected setupPrim2DBase(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, isVisible: boolean, marginTop?: number, marginLeft?: number, marginRight?: number, marginBottom?: number, vAlignment?: number, hAlignment?: number) {
             if (!(this instanceof Group2D) && !(this instanceof Sprite2D && id !== null && id.indexOf("__cachedSpriteOfGroup__") === 0) && (owner.cachingStrategy === Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) && (parent === owner)) {
                 throw new Error("Can't create a primitive with the canvas as direct parent when the caching strategy is TOPLEVELGROUPS. You need to create a Group below the canvas and use it as the parent for the primitive");
             }
 
+            let m: PrimitiveMargin = null;
+            if (marginTop || marginLeft || marginRight || marginBottom) {
+                m = new PrimitiveMargin(this);
+                m.top    = marginTop    || 0;
+                m.left   = marginLeft   || 0;
+                m.right  = marginRight  || 0;
+                m.bottom = marginBottom || 0;
+            }
+
             this.setupSmartPropertyPrim();
             this._pointerEventObservable = new Observable<PrimitivePointerInfo>();
             this._isPickable = true;
@@ -309,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);
@@ -318,7 +484,6 @@
                 this._renderGroup = null;
             }
 
-            this._id = id;
             this.propertyChanged = new Observable<PropertyChangedInfo>();
             this._children = new Array<Prim2DBase>();
             this._globalTransformProcessStep = 0;
@@ -334,9 +499,11 @@
             this.scale = 1;
             this.levelVisible = isVisible;
             this.origin = origin || new Vector2(0.5, 0.5);
+            this.margin = m;
+            this.hAlignment = hAlignment;
+            this.vAlignment = vAlignment;
         }
 
-
         public get actionManager(): ActionManager {
             if (!this._actionManager) {
                 this._actionManager = new ActionManager(this.owner.scene);
@@ -423,6 +590,21 @@
          */
         public static zOrderProperty: Prim2DPropInfo;
 
+        /**
+         * Metadata of the margin property
+         */
+        public static marginProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the vAlignment property
+         */
+        public static vAlignmentProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the hAlignment property
+         */
+        public static hAlignmentProperty: Prim2DPropInfo;
+
         @instanceLevelProperty(1, pi => Prim2DBase.positionProperty = pi, false, true)
         /**
          * Position of the primitive, relative to its parent.
@@ -523,6 +705,47 @@
 
         public set zOrder(value: number) {
             this._zOrder = value;
+            this.onZOrderChanged();
+        }
+
+        @dynamicLevelProperty(8, pi => Prim2DBase.marginProperty = pi)
+        /**
+         * You can get/set a margin on the primitive through this property
+         * @returns the margin object, if there was none, a default one is created and returned
+         */
+        public get margin(): PrimitiveMargin {
+            if (!this._margin) {
+                this._margin = new PrimitiveMargin(this);
+            }
+            return this._margin;
+        }
+
+        public set margin(value: PrimitiveMargin) {
+            this._margin = value;
+        }
+
+        @dynamicLevelProperty(9, pi => Prim2DBase.hAlignmentProperty = pi)
+        /**
+         * You can get/set the horizontal alignment through this property
+         */
+        public get hAlignment(): number {
+            return this._hAlignment;
+        }
+
+        public set hAlignment(value: number) {
+            this._hAlignment = value;
+        }
+
+        @dynamicLevelProperty(10, pi => Prim2DBase.vAlignmentProperty = pi)
+        /**
+         * You can get/set the vertical alignment through this property
+         */
+        public get vAlignment(): number {
+            return this._vAlignment;
+        }
+
+        public set vAlignment(value: number) {
+            this._vAlignment = value;
         }
 
         /**
@@ -602,6 +825,10 @@
             return this._pointerEventObservable;
         }
 
+        protected onZOrderChanged() {
+            
+        }
+
         protected levelIntersect(intersectInfo: IntersectInfo2D): boolean {
 
             return false;
@@ -719,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);
         }
@@ -762,19 +990,23 @@
             }
         }
 
+        public _marginChanged() {
+            
+        }
+
         public _needPrepare(): boolean {
             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;
@@ -876,6 +1108,15 @@
             }
         }
 
+        private static _hAlignLeft    = 1;
+        private static _hAlignCenter  = 2;
+        private static _hAlignRight   = 3;
+        private static _hAlignStretch = 4;
+        private static _vAlignTop     = 1;
+        private static _vAlignCenter  = 2;
+        private static _vAlignBottom  = 3;
+        private static _vAlignStretch = 4;
+
         private _owner: Canvas2D;
         private _parent: Prim2DBase;
         private _actionManager: ActionManager;
@@ -885,6 +1126,9 @@
         protected _hierarchyDepthOffset: number;
         protected _siblingDepthOffset: number;
         private _zOrder: number;
+        private _margin: PrimitiveMargin;
+        private _hAlignment: number;
+        private _vAlignment: number;
         private _levelVisible: boolean;
         public _pointerEventObservable: Observable<PrimitivePointerInfo>;
         public _boundingInfoDirty: boolean;

+ 106 - 70
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.bindBuffersDirectly(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.bindBuffersDirectly(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.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingFillAttributes);
+                    engine.draw(true, 0, this.fillIndicesCount, pid._partData.usedElementCount);
                     engine.unbindInstanceAttributes();
                 } 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.bindBuffersDirectly(this.borderVB, this.borderIB, [1], 4, this.effectBorder);
-                let count = instanceInfo._instancesPartsData[partIndex].usedElementCount;
-                if (instanceInfo._owner.owner.supportInstancedArray) {
+                engine.enableEffect(effect);
+                engine.bindBuffersDirectly(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.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingBorderAttributes);
+                    engine.draw(true, 0, this.borderIndicesCount, pid._partData.usedElementCount);
                     engine.unbindInstanceAttributes();
                 } 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;
         }
     }
@@ -195,8 +218,8 @@
             BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo, this.origin);
         }
 
-        protected setupRectangle2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, size: Size, roundRadius = 0, fill?: IBrush2D, border?: IBrush2D, borderThickness: number = 1) {
-            this.setupShape2D(owner, parent, id, position, origin, true, fill, border, borderThickness);
+        protected setupRectangle2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, size: Size, roundRadius, fill: IBrush2D, border: IBrush2D, borderThickness: number, isVisible: boolean, marginTop: number, marginLeft: number, marginRight: number, marginBottom: number, vAlignment: number, hAlignment: number) {
+            this.setupShape2D(owner, parent, id, position, origin, isVisible, fill, border, borderThickness, marginTop, marginLeft, marginRight, marginBottom, hAlignment, vAlignment);
             this.size = size;
             this.notRounded = !roundRadius;
             this.roundRadius = roundRadius;
@@ -207,36 +230,39 @@
          * @param parent the parent primitive, must be a valid primitive (or the Canvas)
          * options:
          *  - id a text identifier, for information purpose
-         *  - x: the X position relative to its parent, default is 0
-         *  - y: the Y position relative to its parent, default is 0
+         *  - position: the X & Y positions relative to its parent. Alternatively the x and y properties can be set. Default is [0;0]
          *  - origin: define the normalized origin point location, default [0.5;0.5]
-         *  - width: the width of the rectangle, default is 10
-         *  - height: the height of the rectangle, default is 10
+         *  - size: the size of the group. Alternatively the width and height properties can be set. Default will be [10;10].
          *  - roundRadius: if the rectangle has rounded corner, set their radius, default is 0 (to get a sharp rectangle).
          *  - fill: the brush used to draw the fill content of the ellipse, you can set null to draw nothing (but you will have to set a border brush), default is a SolidColorBrush of plain white.
          *  - border: the brush used to draw the border of the ellipse, you can set null to draw nothing (but you will have to set a fill brush), default is null.
          *  - borderThickness: the thickness of the drawn border, default is 1.
+         *  - isVisible: true if the primitive must be visible, false for hidden. Default is true.
+         *  - marginTop/Left/Right/Bottom: define the margin for the corresponding edge, if all of them are null, margin is not used in layout computing. Default Value is null for each.
+         *  - hAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
+         *  - vAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
          */
-        public static Create(parent: Prim2DBase, options: { id?: string, x?: number, y?: number, origin?: Vector2, width?: number, height?: number, roundRadius?: number, fill?: IBrush2D, border?: IBrush2D, borderThickness?: number}): Rectangle2D {
+        public static Create(parent: Prim2DBase, options: { id?: string, position?: Vector2, x?: number, y?: number, origin?: Vector2, size?: Size, width?: number, height?: number, roundRadius?: number, fill?: IBrush2D, border?: IBrush2D, borderThickness?: number, isVisible?: boolean, marginTop?: number, marginLeft?: number, marginRight?: number, marginBottom?: number, vAlignment?: number, hAlignment?: number}): Rectangle2D {
             Prim2DBase.CheckParent(parent);
 
             let rect = new Rectangle2D();
-            rect.setupRectangle2D(parent.owner, parent, options && options.id || null, new Vector2(options && options.x || 0, options && options.y || 0), options && options.origin || null, new Size(options && options.width || 10, options && options.height || 10), options && options.roundRadius || 0);
 
-            if (options && options.fill !== undefined) {
-                rect.fill = options.fill;
+            if (!options) {
+                rect.setupRectangle2D(parent.owner, parent, null, Vector2.Zero(), null, new Size(10, 10), 0, Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF"), null, 1, true, null, null, null, null, null, null);
             } else {
-                rect.fill = Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");                
+                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, 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);
             }
-            rect.border = options && options.border || null;
-            rect.borderThickness = options && options.borderThickness || 1;
             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;
         }
 
@@ -265,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?
@@ -299,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;

+ 268 - 118
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;
         }
@@ -343,9 +353,11 @@
             this._isTransparent = value;
         }
 
-        setupRenderablePrim2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, isVisible: boolean) {
-            this.setupPrim2DBase(owner, parent, id, position, origin);
+        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;
     }
 

+ 3 - 2
src/Canvas2d/babylon.shape2d.ts

@@ -44,8 +44,9 @@
             this._borderThickness = value;
         }
 
-        setupShape2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, isVisible: boolean, fill: IBrush2D, border: IBrush2D, borderThickness: number = 1.0) {
-            this.setupRenderablePrim2D(owner, parent, id, position, origin, isVisible);
+        setupShape2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, isVisible: boolean, fill: IBrush2D, border: IBrush2D, borderThickness: number, marginTop: number, marginLeft: number, marginRight: number, marginBottom: number, vAlignment: number, hAlignment: number) {
+
+            this.setupRenderablePrim2D(owner, parent, id, position, origin, isVisible, marginTop, marginLeft, marginRight, marginBottom, hAlignment || Prim2DBase.HAlignLeft, vAlignment || Prim2DBase.VAlignTop);
             this.border = border;
             this.fill = fill;
             this.borderThickness = borderThickness;

+ 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);

+ 62 - 34
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;
+
+            let effect = context.useInstancing ? this.effectInstanced : this.effect;
 
-            engine.enableEffect(this.effect);
-            this.effect.setTexture("diffuseSampler", this.texture);
-            engine.bindBuffersDirectly(this.vb, this.ib, [1], 4, this.effect);
+            engine.enableEffect(effect);
+            effect.setTexture("diffuseSampler", this.texture);
+            engine.bindBuffersDirectly(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.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingAttributes);
+                engine.draw(true, 0, 6, pid._partData.usedElementCount);
                 engine.unbindInstanceAttributes();
             } 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;
         }
     }
@@ -179,8 +194,8 @@
             return true;
         }
 
-        protected setupSprite2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, texture: Texture, spriteSize: Size, spriteLocation: Vector2, invertY: boolean) {
-            this.setupRenderablePrim2D(owner, parent, id, position, origin, true);
+        protected setupSprite2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, texture: Texture, spriteSize: Size, spriteLocation: Vector2, invertY: boolean, isVisible: boolean, marginTop: number, marginLeft: number, marginRight: number, marginBottom: number, vAlignment: number, hAlignment: number) {
+            this.setupRenderablePrim2D(owner, parent, id, position, origin, isVisible, marginTop, marginLeft, marginRight, marginBottom, hAlignment, vAlignment);
             this.texture = texture;
             this.texture.wrapU = Texture.CLAMP_ADDRESSMODE;
             this.texture.wrapV = Texture.CLAMP_ADDRESSMODE;
@@ -202,31 +217,40 @@
          * @param texture the texture that stores the sprite to render
          * options:
          *  - id a text identifier, for information purpose
-         *  - x: the X position relative to its parent, default is 0
-         *  - y: the Y position relative to its parent, default is 0
+         *  - position: the X & Y positions relative to its parent. Alternatively the x and y properties can be set. Default is [0;0]
          *  - origin: define the normalized origin point location, default [0.5;0.5]
          *  - spriteSize: the size of the sprite, if null the size of the given texture will be used, default is null.
          *  - spriteLocation: the location in the texture of the top/left corner of the Sprite to display, default is null (0,0)
          *  - invertY: if true the texture Y will be inverted, default is false.
+         *  - isVisible: true if the sprite must be visible, false for hidden. Default is true.
+         *  - marginTop/Left/Right/Bottom: define the margin for the corresponding edge, if all of them are null, margin is not used in layout computing. Default Value is null for each.
+         *  - hAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
+         *  - vAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
          */
-        public static Create(parent: Prim2DBase, texture: Texture, options: { id?: string, x?: number, y?: number, origin?: Vector2, spriteSize?: Size, spriteLocation?: Vector2, invertY?: boolean}): Sprite2D {
+        public static Create(parent: Prim2DBase, texture: Texture, options: { id?: string, position?: Vector2, x?: number, y?: number, origin?: Vector2, spriteSize?: Size, spriteLocation?: Vector2, invertY?: boolean, isVisible?: boolean, marginTop?: number, marginLeft?: number, marginRight?: number, marginBottom?: number, vAlignment?: number, hAlignment?: number}): Sprite2D {
             Prim2DBase.CheckParent(parent);
 
             let sprite = new Sprite2D();
-            sprite.setupSprite2D(parent.owner, parent, options && options.id || null, new Vector2(options && options.x || 0, options && options.y || 0), options && options.origin || null, texture, options && options.spriteSize || null, options && options.spriteLocation || null, options && options.invertY || false);
+            if (!options) {
+                sprite.setupSprite2D(parent.owner, parent, null, Vector2.Zero(), null, texture, null, null, false, true, null, null, null, null, null, null);
+            } else {
+                let pos = options.position || new Vector2(options.x || 0, options.y || 0);
+                sprite.setupSprite2D(parent.owner, parent, options.id || null, pos, options.origin || null, texture, options.spriteSize || null, options.spriteLocation || null, options.invertY || false, options.isVisible || true, options.marginTop || null, options.marginLeft || null, options.marginRight || null, options.marginBottom || null, options.vAlignment || null, options.hAlignment || null);
+            }
+
             return sprite;
         }
 
         static _createCachedCanvasSprite(owner: Canvas2D, texture: MapTexture, size: Size, pos: Vector2): Sprite2D {
 
             let sprite = new Sprite2D();
-            sprite.setupSprite2D(owner, null, "__cachedCanvasSprite__", new Vector2(0, 0), null, texture, size, pos, false);
+            sprite.setupSprite2D(owner, null, "__cachedCanvasSprite__", new Vector2(0, 0), null, texture, size, pos, false, true, null, null, null, null, null, null);
 
             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;
         }
 
@@ -252,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;
         }

+ 63 - 67
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.bindBuffersDirectly(this.vb, this.ib, [1], 4, this.effect);
+            let effect = context.useInstancing ? this.effectInstanced : this.effect;
+
+            engine.enableEffect(effect);
+            effect.setTexture("diffuseSampler", this.fontTexture);
+            engine.bindBuffersDirectly(this.vb, this.ib, [1], 4, effect);
+
+            var curAlphaMode = engine.getAlphaMode();
+
+            engine.setAlphaMode(Engine.ALPHA_COMBINE, true);
 
-            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);
+            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.unbindInstanceAttributes();
             } 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;
         }
 
@@ -107,15 +121,6 @@
         public static defaultFontColorProperty: Prim2DPropInfo;
         public static textProperty: Prim2DPropInfo;
         public static areaSizeProperty: Prim2DPropInfo;
-        public static vAlignProperty: Prim2DPropInfo;
-        public static hAlignProperty: Prim2DPropInfo;
-
-        public static TEXT2D_VALIGN_TOP = 1;
-        public static TEXT2D_VALIGN_CENTER = 2;
-        public static TEXT2D_VALIGN_BOTTOM = 3;
-        public static TEXT2D_HALIGN_LEFT = 1;
-        public static TEXT2D_HALIGN_CENTER = 2;
-        public static TEXT2D_HALIGN_RIGHT = 3;
 
         @modelLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 1, pi => Text2D.fontProperty = pi, false, true)
         public get fontName(): string {
@@ -158,24 +163,6 @@
             this._areaSize = value;
         }
 
-        @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 5, pi => Text2D.vAlignProperty = pi)
-        public get vAlign(): number {
-            return this._vAlign;
-        }
-
-        public set vAlign(value: number) {
-            this._vAlign = value;
-        }
-
-        @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 6, pi => Text2D.hAlignProperty = pi)
-        public get hAlign(): number {
-            return this._hAlign;
-        }
-
-        public set hAlign(value: number) {
-            this._hAlign = value;
-        }
-
         public get actualSize(): Size {
             if (this.areaSize) {
                 return this.areaSize;
@@ -216,17 +203,15 @@
             BoundingInfo2D.CreateFromSizeToRef(this.actualSize, this._levelBoundingInfo, this.origin);
         }
 
-        protected setupText2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, fontName: string, text: string, areaSize: Size, defaultFontColor: Color4, vAlign, hAlign, tabulationSize: number) {
-            this.setupRenderablePrim2D(owner, parent, id, position, origin, true);
+        protected setupText2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, fontName: string, text: string, areaSize: Size, defaultFontColor: Color4, tabulationSize: number, isVisible: boolean, marginTop: number, marginLeft: number, marginRight: number, marginBottom: number, vAlignment: number, hAlignment: number) {
+            this.setupRenderablePrim2D(owner, parent, id, position, origin, isVisible, marginTop, marginLeft, marginRight, marginBottom, hAlignment, vAlignment);
 
             this.fontName = fontName;
             this.defaultFontColor = defaultFontColor;
             this.text = text;
             this.areaSize = areaSize;
-            this.vAlign = vAlign;
-            this.hAlign = hAlign;
             this._tabulationSize = tabulationSize;
-            this._isTransparent = true;
+            this.isAlphaTest = true;
         }
 
         /**
@@ -235,21 +220,27 @@
          * @param text the text to display
          * Options:
          *  - id a text identifier, for information purpose
-         *  - x: the X position relative to its parent, default is 0
-         *  - y: the Y position relative to its parent, default is 0
+         *  - position: the X & Y positions relative to its parent. Alternatively the x and y properties can be set. Default is [0;0]
          *  - origin: define the normalized origin point location, default [0.5;0.5]
          *  - fontName: the name/size/style of the font to use, following the CSS notation. Default is "12pt Arial".
          *  - defaultColor: the color by default to apply on each letter of the text to display, default is plain white.
          *  - areaSize: the size of the area in which to display the text, default is auto-fit from text content.
-         *  - vAlign: vertical alignment (areaSize must be specified), default is Text2D.TEXT2D_VALIGN_CENTER
-         *  - hAlign: horizontal alignment (areaSize must be specified), default is Text2D.TEXT2D_HALIGN_CENTER
          *  - tabulationSize: number of space character to insert when a tabulation is encountered, default is 4
+         *  - isVisible: true if the text must be visible, false for hidden. Default is true.
+         *  - marginTop/Left/Right/Bottom: define the margin for the corresponding edge, if all of them are null, margin is not used in layout computing. Default Value is null for each.
+         *  - hAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
+         *  - vAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
          */
-        public static Create(parent: Prim2DBase, text: string, options?: { id?: string, x?: number, y?: number, origin?:Vector2, fontName?: string, defaultFontColor?: Color4, areaSize?: Size, vAlign?: number, hAlign?: number, tabulationSize?: number}): Text2D {
+        public static Create(parent: Prim2DBase, text: string, options?: { id?: string, position?: Vector2, x?: number, y?: number, origin?: Vector2, fontName?: string, defaultFontColor?: Color4, areaSize?: Size, tabulationSize?: number, isVisible?: boolean, marginTop?: number, marginLeft?: number, marginRight?: number, marginBottom?: number, hAlignment?: number, vAlignment?: number}): Text2D {
             Prim2DBase.CheckParent(parent);
 
             let text2d = new Text2D();
-            text2d.setupText2D(parent.owner, parent, options && options.id || null, new Vector2(options && options.x || 0, options && options.y || 0), options && options.origin || null, options && options.fontName || "12pt Arial", text, options && options.areaSize, options && options.defaultFontColor || new Color4(1, 1, 1, 1), options && options.vAlign || Text2D.TEXT2D_VALIGN_CENTER, options && options.hAlign || Text2D.TEXT2D_HALIGN_CENTER, options && options.tabulationSize || 4);
+            if (!options) {
+                text2d.setupText2D(parent.owner, parent, null, Vector2.Zero(), null, "12pt Arial", text, null, new Color4(1,1,1,1), 4, true, null, null, null, null, null, null);
+            } else {
+                let pos = options.position || new Vector2(options.x || 0, options.y || 0);
+                text2d.setupText2D(parent.owner, parent, options.id || null, pos, options.origin || null, options.fontName || "12pt Arial", text, options.areaSize, options.defaultFontColor || new Color4(1, 1, 1, 1), options.tabulationSize || 4, options.isVisible || true, options.marginTop || null, options.marginLeft || null, options.marginRight || null, options.marginBottom || null, options.vAlignment || null, options.hAlignment || null);
+            }
             return text2d;
         }
 
@@ -258,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;
         }
 
@@ -285,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;
     }
 }

+ 4 - 8
src/babylon.engine.ts

@@ -1452,7 +1452,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;
             }
@@ -1463,37 +1463,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;
         }