Browse Source

Merge pull request #1235 from nockawa/TextTrans

PR #3 opacity + transparent segment with instanced array + text2d blend
David Catuhe 9 years ago
parent
commit
3617c3a903

+ 22 - 5
dist/preview release/what's new.md

@@ -5,7 +5,7 @@
 ### Updates
 - Added support for texture arrays ([deltakosh](https://github.com/deltakosh)) 
 - Added `camera.isInFrustum` and `camera.isCompletelyInFrustum`. Can be used with meshes, submeshes and boundingInfo ([deltakosh](https://github.com/deltakosh)) 
-- Several memory allocation reduction ([benaadams](https://github.com/benaadams)) 
+- Several memory allocation reduction ([benaadams](https://github.com/benaadams))
 - Several GPU state change reduction ([benaadams](https://github.com/benaadams)) 
 - MapTexture: add `supersample` mode to double font quality. ([nockawa](https://github.com/nockawa))
 - New `invertUV` parameter an all ribbon based shapes : ribbon, tube, lathe, basic and custom extrusion ([jerome](https://github.com/jbousquie))
@@ -13,6 +13,11 @@
 - PerfCounter class added to monitor time/counter and expose min/max/average/lastSecondAverage/current metrics. Updated engine/scene current counter to use this class, exposing new properties as well to access the PerfCounter object ([nockawa](https://github.com/nockawa))
 - Better keyboard event handling which is now done at canvas level and not at window level ([deltakosh](https://github.com/deltakosh)) 
 - New `scene.hoverCursor` property to define a custom cursor when moving mouse over meshes ([deltakosh](https://github.com/deltakosh)) 
+- Canvas2D: ([nockawa](https://github.com/nockawa)) 
+ - Performance metrics added
+ - Text2D super sampling to enhance quality in World Space Canvas
+ - World Space Canvas is now rendering in an adaptive way for its resolution to fit the on screen projected one to achieve a good rendering quality
+ - Transparent Primitives are now drawn with Instanced Array when supported
 
 ### Exporters
     
@@ -24,9 +29,21 @@
 - Fixed cross vector calculation in `_computeHeightQuads()` that affected  all the `GroundMesh.getHeightAtCoordinates()` and `GroundMesh.getNormalAtCoordinates()` methods ([jerome](https://github.com/jbousquie))
 - Fixed `Mesh.CreateDashedLines()` missing `instance` parameter on update ([jerome](https://github.com/jbousquie))
 - Fixed model shape initial red vertex color set to zero not formerly being taken in account in the `SolidParticleSystem` ([jerome](https://github.com/jbousquie))
-- Canvas2D:
- - `Sprite2D`: texture size is now set by default as expected
- - `Sprite2D`: can have no `id` set
- - `ZOrder` fixed in Primitives created inline
+- Canvas2D: ([nockawa](https://github.com/nockawa))
+ - `WorldSpaceCanvas2D`:
+	- Intersection/interaction now works on non squared canvas
+ - Primitive:
+	- `ZOrder` fixed in Primitives created inline
+	- Z-Order is now correctly distributed along the whole canvas object graph
+ - `Sprite2D`: 
+	- texture size is now set by default as expected
+	- can have no `id` set
+ - `Text2D`: 
+	- Fix bad rendering quality on Chrome
+	- Rendering above transparent surface is now blending correctly
+
 ### Breaking changes
+ - Canvas2D: ([nockawa](https://github.com/nockawa))
+  - `WorldSpaceCanvas2D`:
+	- WorldSpaceRenderScale is no longer supported (deprecated because of adaptive feature added).
 

+ 15 - 5
src/Canvas2d/babylon.canvas2d.ts

@@ -751,6 +751,11 @@
                 return false;
             }
 
+            if (this._profilingCanvas) {
+                this._profilingCanvas.dispose();
+                this._profilingCanvas = null;
+            }
+
             if (this.interactionEnabled) {
                 this._setupInteraction(false);
             }
@@ -931,11 +936,15 @@
         }
 
         public createCanvasProfileInfoCanvas(): Canvas2D {
+            if (this._profilingCanvas) {
+                return this._profilingCanvas;
+            }
+
             let canvas = new ScreenSpaceCanvas2D(this.scene, {
                 id: "ProfileInfoCanvas", cachingStrategy: Canvas2D.CACHESTRATEGY_DONTCACHE, children:
                 [
                     new Rectangle2D({
-                        id: "ProfileBorder", border: "#FFFFFFFF", borderThickness: 2, roundRadius: 5, marginAlignment: "h: left, v: top", margin: "10", padding: "10", children:
+                        id: "ProfileBorder", border: "#FFFFFFFF", borderThickness: 2, roundRadius: 5, fill: "#C04040C0", marginAlignment: "h: left, v: top", margin: "10", padding: "10", children:
                         [
                             new Text2D("Stats", { id: "ProfileInfoText", marginAlignment: "h: left, v: top", fontName: "10pt Lucida Console" })
                         ]
@@ -945,7 +954,7 @@
             });
 
             this._profileInfoText = <Text2D>canvas.findById("ProfileInfoText");
-
+            this._profilingCanvas = canvas;
             return canvas;
         }
 
@@ -993,9 +1002,9 @@
             let format = (v: number) => (Math.round(v*100)/100).toString();
 
             let p = `Draw Calls:\n` +
-                    ` - Opaque:      ${this.drawCallsOpaqueCounter.current}, (avg:${format(this.drawCallsOpaqueCounter.lastSecAverage)}, t:${format(this.drawCallsOpaqueCounter.total)})\n` +
-                    ` - AlphaTest:   ${this.drawCallsAlphaTestCounter.current}, (avg:${format(this.drawCallsAlphaTestCounter.lastSecAverage)}, t:${format(this.drawCallsAlphaTestCounter.total)})\n` +
-                    ` - Transparent: ${this.drawCallsTransparentCounter.current}, (avg:${format(this.drawCallsTransparentCounter.lastSecAverage)}, t:${format(this.drawCallsTransparentCounter.total)})\n` +
+                    ` - Opaque:      ${format(this.drawCallsOpaqueCounter.current)}, (avg:${format(this.drawCallsOpaqueCounter.lastSecAverage)}, t:${format(this.drawCallsOpaqueCounter.total)})\n` +
+                    ` - AlphaTest:   ${format(this.drawCallsAlphaTestCounter.current)}, (avg:${format(this.drawCallsAlphaTestCounter.lastSecAverage)}, t:${format(this.drawCallsAlphaTestCounter.total)})\n` +
+                    ` - Transparent: ${format(this.drawCallsTransparentCounter.current)}, (avg:${format(this.drawCallsTransparentCounter.lastSecAverage)}, t:${format(this.drawCallsTransparentCounter.total)})\n` +
                     `Group Render: ${this.groupRenderCounter.current}, (avg:${format(this.groupRenderCounter.lastSecAverage)}, t:${format(this.groupRenderCounter.total)})\n` + 
                     `Update Transparent Data: ${this.updateTransparentDataCounter.current}, (avg:${format(this.updateTransparentDataCounter.lastSecAverage)}, t:${format(this.updateTransparentDataCounter.total)})\n` + 
                     `Cached Group Render: ${this.cachedGroupRenderCounter.current}, (avg:${format(this.cachedGroupRenderCounter.lastSecAverage)}, t:${format(this.cachedGroupRenderCounter.total)})\n` + 
@@ -1102,6 +1111,7 @@
         private _updateLocalTransformCounter : PerfCounter;
         private _boundingInfoRecomputeCounter: PerfCounter;
 
+        private _profilingCanvas: Canvas2D;
         private _profileInfoText: Text2D;
 
         protected onPrimBecomesDirty() {

+ 10 - 4
src/Canvas2d/babylon.ellipse2d.ts

@@ -57,9 +57,11 @@
                         this.instancingFillAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_FILLPARTID, effect);
                     }
 
+                    let glBuffer = context.instancedBuffers ? context.instancedBuffers[partIndex] : pid._partBuffer;
+                    let count = context.instancedBuffers ? context.instancesCount : pid._partData.usedElementCount;
                     canvas._addDrawCallCount(1, context.renderMode);
-                    engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingFillAttributes);
-                    engine.draw(true, 0, this.fillIndicesCount, pid._partData.usedElementCount);
+                    engine.updateAndBindInstancesBuffer(glBuffer, null, this.instancingFillAttributes);
+                    engine.draw(true, 0, this.fillIndicesCount, count);
                     engine.unbindInstanceAttributes();
                 } else {
                     canvas._addDrawCallCount(context.partDataEndIndex - context.partDataStartIndex, context.renderMode);
@@ -87,9 +89,11 @@
                         this.instancingBorderAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_BORDERPARTID, effect);
                     }
 
+                    let glBuffer = context.instancedBuffers ? context.instancedBuffers[partIndex] : pid._partBuffer;
+                    let count = context.instancedBuffers ? context.instancesCount : pid._partData.usedElementCount;
                     canvas._addDrawCallCount(1, context.renderMode);
-                    engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingBorderAttributes);
-                    engine.draw(true, 0, this.borderIndicesCount, pid._partData.usedElementCount);
+                    engine.updateAndBindInstancesBuffer(glBuffer, null, this.instancingBorderAttributes);
+                    engine.draw(true, 0, this.borderIndicesCount, count);
                     engine.unbindInstanceAttributes();
                 } else {
                     canvas._addDrawCallCount(context.partDataEndIndex - context.partDataStartIndex, context.renderMode);
@@ -225,6 +229,7 @@
          * - position: the X & Y positions relative to its parent. Alternatively the x and y properties can be set. Default is [0;0]
          * - rotation: the initial rotation (in radian) of the primitive. default is 0
          * - scale: the initial scale of the primitive. default is 1
+         * - opacity: set the overall opacity of the primitive, 1 to be opaque (default), less than 1 to be transparent.
          * - origin: define the normalized origin point location, default [0.5;0.5]
          * - 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.
@@ -257,6 +262,7 @@
             y                 ?: number,
             rotation          ?: number,
             scale             ?: number,
+            opacity           ?: number,
             origin            ?: Vector2,
             size              ?: Size,
             width             ?: number,

+ 77 - 42
src/Canvas2d/babylon.group2d.ts

@@ -35,6 +35,7 @@
          * - position: the X & Y positions relative to its parent. Alternatively the x and y properties can be set. Default is [0;0]
          * - rotation: the initial rotation (in radian) of the primitive. default is 0
          * - scale: the initial scale of the primitive. default is 1
+         * - opacity: set the overall opacity of the primitive, 1 to be opaque (default), less than 1 to be transparent.
          * - origin: define the normalized origin point location, default [0.5;0.5]
          * - 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
@@ -64,6 +65,7 @@
             x                 ?: number,
             y                 ?: number,
             trackNode         ?: Node,
+            opacity           ?: number,
             origin            ?: Vector2,
             size              ?: Size,
             width             ?: number,
@@ -190,7 +192,7 @@
             }
 
             if (this._renderableData) {
-                this._renderableData.dispose();
+                this._renderableData.dispose(this.owner.engine);
                 this._renderableData = null;
             }
 
@@ -525,11 +527,6 @@
 
             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.actualZOffset - a._primitive.actualZOffset);
 
@@ -541,21 +538,21 @@
                     return false;
                 }
 
-                let tpiZ = tpi._primitive.actualZOffset;
+                //let tpiZ = tpi._primitive.actualZOffset;
 
                 // 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
-                }
+                seg.endDataIndex = tpi._primitive._getPrimitiveLastIndex();
 
                 return true;
             }
 
+            // Free the existing TransparentSegments
+            for (let ts of rd._transparentSegments) {
+                ts.dispose(this.owner.engine);
+            }
             rd._transparentSegments.splice(0);
+
             let prevSeg = null;
 
             for (let tpiI = 0; tpiI < rd._transparentPrimitives.length; tpiI++) {
@@ -580,8 +577,7 @@
                     ts.groupInsanceInfo = tpi._groupInstanceInfo;
                     let prim = tpi._primitive;
                     ts.startZ = prim.actualZOffset;
-                    ts.startDataIndex = prim._getFirstIndexInDataBuffer();
-                    ts.endDataIndex = prim._getLastIndexInDataBuffer() + 1; // Make it exclusive, more natural to use in a for loop
+                    prim._updateTransparentSegmentIndices(ts);
                     ts.endZ = ts.startZ;
                     tpi._transparentSegment = ts;
                     rd._transparentSegments.push(ts);
@@ -590,7 +586,7 @@
                 prevSeg = tpi._transparentSegment;
             }
 
-            rd._firstChangedPrim = null;
+            //rd._firstChangedPrim = null;
             rd._transparentListChanged = false;
         }
 
@@ -599,22 +595,69 @@
             let context = new Render2DContext(Render2DContext.RenderModeTransparent);
             let rd = this._renderableData;
 
+            let useInstanced = this.owner.supportInstancedArray;
+
             let length = rd._transparentSegments.length;
             for (let i = 0; i < length; i++) {
-                let ts = rd._transparentSegments[i];
-
+                context.instancedBuffers = null;
 
+                let ts = rd._transparentSegments[i];
                 let gii = ts.groupInsanceInfo;
                 let mrc = gii.modelRenderCache;
+                let engine = this.owner.engine;
+                let count = ts.endDataIndex - ts.startDataIndex;
+
+                // Use Instanced Array if it's supported and if there's at least 5 prims to draw.
+                // We don't want to create an Instanced Buffer for less that 5 prims
+                if (useInstanced && count >= 5) {
+
+                    if (!ts.partBuffers) {
+                        let buffers = new Array<WebGLBuffer>();
+
+                        for (let j = 0; j < gii.transparentData.length; j++) {
+                            let gitd = gii.transparentData[j];
+                            let dfa = gitd._partData;
+                            let data = dfa.pack();
+                            let stride = dfa.stride;
+                            let neededSize = count * stride * 4;
+
+                            let buffer = engine.createInstancesBuffer(neededSize); // Create + bind
+                            let segData = data.subarray(ts.startDataIndex * stride, ts.endDataIndex * stride);
+                            engine.updateArrayBuffer(segData);
+                            buffers.push(buffer);
+                        }
+
+                        ts.partBuffers = buffers;
+                    } else if (gii.transparentDirty) {
+                        for (let j = 0; j < gii.transparentData.length; j++) {
+                            let gitd = gii.transparentData[j];
+                            let dfa = gitd._partData;
+                            let data = dfa.pack();
+                            let stride = dfa.stride;
+
+                            let buffer = ts.partBuffers[j];
+                            let segData = data.subarray(ts.startDataIndex * stride, ts.endDataIndex * stride);
+                            engine.bindArrayBuffer(buffer);
+                            engine.updateArrayBuffer(segData);
+                        }
+                    }
 
-                context.useInstancing = false;
-                context.partDataStartIndex = ts.startDataIndex;
-                context.partDataEndIndex = ts.endDataIndex;
-                context.groupInfoPartData = gii.transparentData;
+                    context.useInstancing = true;
+                    context.instancesCount = count;
+                    context.instancedBuffers = ts.partBuffers;
+                    context.groupInfoPartData = gii.transparentData;
 
-                let renderFailed = !mrc.render(gii, context);
+                    let renderFailed = !mrc.render(gii, context);
+                    failedCount += renderFailed ? 1 : 0;
+                } else {
+                    context.useInstancing = false;
+                    context.partDataStartIndex = ts.startDataIndex;
+                    context.partDataEndIndex = ts.endDataIndex;
+                    context.groupInfoPartData = gii.transparentData;
 
-                failedCount += renderFailed ? 1 : 0;
+                    let renderFailed = !mrc.render(gii, context);
+                    failedCount += renderFailed ? 1 : 0;
+                }
             }
 
             return failedCount;
@@ -894,7 +937,6 @@
             this._renderGroupInstancesInfo = new StringDictionary<GroupInstanceInfo>();
             this._transparentPrimitives = new Array<TransparentPrimitiveInfo>();
             this._transparentSegments = new Array<TransparentSegment>();
-            this._firstChangedPrim = null;
             this._transparentListChanged = false;
             this._cacheNode = null;
             this._cacheTexture = null;
@@ -907,7 +949,7 @@
             this._anisotropicLevel = 1;
         }
 
-        dispose() {
+        dispose(engine: Engine) {
             if (this._cacheRenderSprite) {
                 this._cacheRenderSprite.dispose();
                 this._cacheRenderSprite = null;
@@ -935,6 +977,14 @@
                 this._cacheNodeUVsChangedObservable.clear();
                 this._cacheNodeUVsChangedObservable = null;
             }
+
+            if (this._transparentSegments) {
+                for (let ts of this._transparentSegments) {
+                    ts.dispose(engine);
+                }
+                this._transparentSegments.splice(0);
+                this._transparentSegments = null;
+            }
         }
 
         addNewTransparentPrimitiveInfo(prim: RenderablePrim2D, gii: GroupInstanceInfo): TransparentPrimitiveInfo {
@@ -946,8 +996,6 @@
             this._transparentPrimitives.push(tpi);
             this._transparentListChanged = true;
 
-            this.updateSmallestZChangedPrim(tpi);
-
             return tpi;
         }
 
@@ -956,24 +1004,12 @@
             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.actualZOffset;
-                let curZ = this._firstChangedPrim ? this._firstChangedPrim._primitive.actualZOffset : Number.MIN_VALUE;
-                if (newZ > curZ) {
-                    this._firstChangedPrim = tpi;
-                }
-            }
+            //this.updateSmallestZChangedPrim(tpi);
         }
 
         _primDirtyList: Array<Prim2DBase>;
@@ -992,7 +1028,6 @@
         _transparentListChanged: boolean;
         _transparentPrimitives: Array<TransparentPrimitiveInfo>;
         _transparentSegments: Array<TransparentSegment>;
-        _firstChangedPrim: TransparentPrimitiveInfo;
         _renderingScale: number;
 
     }

+ 10 - 4
src/Canvas2d/babylon.lines2d.ts

@@ -56,9 +56,11 @@
                         this.instancingFillAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_FILLPARTID, effect);
                     }
 
+                    let glBuffer = context.instancedBuffers ? context.instancedBuffers[partIndex] : pid._partBuffer;
+                    let count = context.instancedBuffers ? context.instancesCount : pid._partData.usedElementCount;
                     canvas._addDrawCallCount(1, context.renderMode);
-                    engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingFillAttributes);
-                    engine.draw(true, 0, this.fillIndicesCount, pid._partData.usedElementCount);
+                    engine.updateAndBindInstancesBuffer(glBuffer, null, this.instancingFillAttributes);
+                    engine.draw(true, 0, this.fillIndicesCount, count);
                     engine.unbindInstanceAttributes();
                 } else {
                     canvas._addDrawCallCount(context.partDataEndIndex - context.partDataStartIndex, context.renderMode);
@@ -86,9 +88,11 @@
                         this.instancingBorderAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_BORDERPARTID, effect);
                     }
 
+                    let glBuffer = context.instancedBuffers ? context.instancedBuffers[partIndex] : pid._partBuffer;
+                    let count = context.instancedBuffers ? context.instancesCount : pid._partData.usedElementCount;
                     canvas._addDrawCallCount(1, context.renderMode);
-                    engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingBorderAttributes);
-                    engine.draw(true, 0, this.borderIndicesCount, pid._partData.usedElementCount);
+                    engine.updateAndBindInstancesBuffer(glBuffer, null, this.instancingBorderAttributes);
+                    engine.draw(true, 0, this.borderIndicesCount, count);
                     engine.unbindInstanceAttributes();
                 } else {
                     canvas._addDrawCallCount(context.partDataEndIndex - context.partDataStartIndex, context.renderMode);
@@ -391,6 +395,7 @@
          * - position: the X & Y positions relative to its parent. Alternatively the x and y properties can be set. Default is [0;0]
          * - rotation: the initial rotation (in radian) of the primitive. default is 0
          * - scale: the initial scale of the primitive. default is 1
+         * - opacity: set the overall opacity of the primitive, 1 to be opaque (default), less than 1 to be transparent.
          * - 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.
@@ -425,6 +430,7 @@
             y                 ?: number,
             rotation          ?: number,
             scale             ?: number,
+            opacity           ?: number,
             origin            ?: Vector2,
             fillThickness     ?: number,
             closed            ?: boolean,

+ 18 - 0
src/Canvas2d/babylon.modelRenderCache.ts

@@ -123,11 +123,29 @@
     }
 
     export class TransparentSegment {
+        constructor() {
+            this.groupInsanceInfo = null;
+            this.startZ = 0;
+            this.endZ = 0;
+            this.startDataIndex = 0;
+            this.endDataIndex = 0;
+            this.partBuffers = null;
+        }
+
+        dispose(engine: Engine) {
+            if (this.partBuffers) {
+                this.partBuffers.forEach(b => engine._releaseBuffer(b));
+                this.partBuffers.splice(0);
+                this.partBuffers = null;
+            }
+        }
+
         groupInsanceInfo: GroupInstanceInfo;
         startZ: number;
         endZ: number;
         startDataIndex: number;
         endDataIndex: number;
+        partBuffers: WebGLBuffer[];
     }
 
     export class GroupInfoPartData {

+ 91 - 13
src/Canvas2d/babylon.prim2dBase.ts

@@ -19,6 +19,7 @@
             this.useInstancing = false;
             this.groupInfoPartData = null;
             this.partDataStartIndex = this.partDataEndIndex = null;
+            this.instancedBuffers = null;
         }
         /**
          * Define which render Mode should be used to render the primitive: one of Render2DContext.RenderModeXxxx property
@@ -37,6 +38,16 @@
         useInstancing: boolean;
 
         /**
+         * If specified, must take precedence from the groupInfoPartData. partIndex is the same as groupInfoPardData
+         */
+        instancedBuffers: WebGLBuffer[];
+
+        /**
+         * To use when instancedBuffers is specified, gives the count of instances to draw
+         */
+        instancesCount: number;
+
+        /**
          * Contains the data related to the primitives instances to render
          */
         groupInfoPartData: GroupInfoPartData[];
@@ -1280,7 +1291,7 @@
      */
     export class Prim2DBase extends SmartPropertyPrim {
         static PRIM2DBASE_PROPCOUNT: number = 15;
-        private static _bigInt = Math.pow(2, 30);
+        public  static _bigInt = Math.pow(2, 30);
 
         constructor(settings: {
             parent?: Prim2DBase,
@@ -1291,6 +1302,7 @@
             y?: number,
             rotation?: number,
             scale?: number,
+            opacity?: number,
             origin?: Vector2,
             layoutEngine?: LayoutEngineBase | string,
             isVisible?: boolean,
@@ -1373,7 +1385,13 @@
             this._zOrder = 0;
             this._zMax = 0;
             this._firstZDirtyIndex = Prim2DBase._bigInt;
-            this._setFlags(SmartPropertyPrim.flagIsPickable | SmartPropertyPrim.flagBoundingInfoDirty);
+            this._setFlags(SmartPropertyPrim.flagIsPickable | SmartPropertyPrim.flagBoundingInfoDirty | SmartPropertyPrim.flagActualOpacityDirty);
+
+            if (settings.opacity != null) {
+                this._opacity = settings.opacity;
+            } else {
+                this._opacity = 1;
+            }
 
             if (settings.childrenFlatZOrder) {
                 this._setFlags(SmartPropertyPrim.flagChildrenFlatZOrder);
@@ -1598,6 +1616,12 @@
          */
         public static marginAlignmentProperty: Prim2DPropInfo;
 
+        /**
+         * Metadata of the opacity property
+         */
+        public static opacityProperty: Prim2DPropInfo;
+
+
         @instanceLevelProperty(1, pi => Prim2DBase.actualPositionProperty = pi, false, false, true)
         /**
          * Return the position where the primitive is rendered in the Canvas, this position may be different than the one returned by the position property due to layout/alignment/margin/padding computing
@@ -1642,7 +1666,7 @@
          * Use this property to set a new Vector2 object, otherwise to change only the x/y use Prim2DBase.x or y properties.
          * Setting this property may have no effect is specific alignment are in effect.
          */
-        @dynamicLevelProperty(1, pi => Prim2DBase.positionProperty = pi, false, false, true)
+        @dynamicLevelProperty(2, pi => Prim2DBase.positionProperty = pi, false, false, true)
         public get position(): Vector2 {
             return this._position || Prim2DBase._nullPosition;
         }
@@ -1652,6 +1676,7 @@
                 return;
             }
             this._position = value;
+            this.markAsDirty("actualPosition");
         }
 
         /**
@@ -1679,6 +1704,7 @@
 
             this._position.x = value;
             this.markAsDirty("position");
+            this.markAsDirty("actualPosition");
         }
 
         /**
@@ -1706,6 +1732,7 @@
 
             this._position.y = value;
             this.markAsDirty("position");
+            this.markAsDirty("actualPosition");
         }
 
         private static boundinbBoxReentrency = false;
@@ -1716,7 +1743,7 @@
          * BEWARE: if you change only size.width or height it won't trigger a property change and you won't have the expected behavior.
          * Use this property to set a new Size object, otherwise to change only the width/height use Prim2DBase.width or height properties.
          */
-        @dynamicLevelProperty(2, pi => Prim2DBase.sizeProperty = pi, false, true)
+        @dynamicLevelProperty(3, pi => Prim2DBase.sizeProperty = pi, false, true)
         public get size(): Size {
 
             if (!this._size || this._size.width == null || this._size.height == null) {
@@ -1796,7 +1823,7 @@
             this._positioningDirty();
         }
 
-        @instanceLevelProperty(3, pi => Prim2DBase.rotationProperty = pi, false, true)
+        @instanceLevelProperty(4, pi => Prim2DBase.rotationProperty = pi, false, true)
         /**
          * Rotation of the primitive, in radian, along the Z axis
          */
@@ -1808,7 +1835,7 @@
             this._rotation = value;
         }
 
-        @instanceLevelProperty(4, pi => Prim2DBase.scaleProperty = pi, false, true)
+        @instanceLevelProperty(5, pi => Prim2DBase.scaleProperty = pi, false, true)
         /**
          * Uniform scale applied on the primitive
          */
@@ -1896,7 +1923,7 @@
          * 0,1 means the center is top/left
          * @returns The normalized center.
          */
-        @dynamicLevelProperty(5, pi => Prim2DBase.originProperty = pi, false, true)
+        @dynamicLevelProperty(6, pi => Prim2DBase.originProperty = pi, false, true)
         public get origin(): Vector2 {
             return this._origin;
         }
@@ -1905,7 +1932,7 @@
             this._origin = value;
         }
 
-        @dynamicLevelProperty(6, pi => Prim2DBase.levelVisibleProperty = pi)
+        @dynamicLevelProperty(7, pi => Prim2DBase.levelVisibleProperty = pi)
         /**
          * Let the user defines if the Primitive is hidden or not at its level. As Primitives inherit the hidden status from their parent, only the isVisible property give properly the real visible state.
          * Default is true, setting to false will hide this primitive and its children.
@@ -1918,7 +1945,7 @@
             this._changeFlags(SmartPropertyPrim.flagLevelVisible, value);
         }
 
-        @instanceLevelProperty(7, pi => Prim2DBase.isVisibleProperty = pi)
+        @instanceLevelProperty(8, pi => Prim2DBase.isVisibleProperty = pi)
         /**
          * Use ONLY THE GETTER to determine if the primitive is visible or not.
          * The Setter is for internal purpose only!
@@ -1931,7 +1958,7 @@
             this._changeFlags(SmartPropertyPrim.flagIsVisible, value);
         }
 
-        @instanceLevelProperty(8, pi => Prim2DBase.zOrderProperty = pi)
+        @instanceLevelProperty(9, pi => Prim2DBase.zOrderProperty = pi)
         /**
          * You can override the default Z Order through this property, but most of the time the default behavior is acceptable
          */
@@ -1955,7 +1982,7 @@
             return this._manualZOrder != null;
         }
 
-        @dynamicLevelProperty(9, pi => Prim2DBase.marginProperty = pi)
+        @dynamicLevelProperty(10, 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
@@ -1976,7 +2003,7 @@
             return (this._margin !== null) || (this._marginAlignment !== null);
         }
 
-        @dynamicLevelProperty(10, pi => Prim2DBase.paddingProperty = pi)
+        @dynamicLevelProperty(11, pi => Prim2DBase.paddingProperty = 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
@@ -1997,7 +2024,7 @@
             return this._padding !== null;
         }
 
-        @dynamicLevelProperty(11, pi => Prim2DBase.marginAlignmentProperty = pi)
+        @dynamicLevelProperty(12, pi => Prim2DBase.marginAlignmentProperty = pi)
         /**
          * You can get/set the margin alignment through this property
          */
@@ -2008,6 +2035,45 @@
             return this._marginAlignment;
         }
 
+        @instanceLevelProperty(13, pi => Prim2DBase.opacityProperty = pi)
+        /**
+         * Get/set the opacity of the whole primitive
+         */
+        public get opacity(): number {
+            return this._opacity;
+        }
+
+        public set opacity(value: number) {
+            if (value < 0) {
+                value = 0;
+            } else if (value > 1) {
+                value = 1;
+            }
+
+            if (this._opacity === value) {
+                return;
+            }
+
+            this._opacity = value;
+            this._updateRenderMode();
+            this._spreadActualOpacityChanged();
+        }
+
+        public get actualOpacity(): number {
+            if (this._isFlagSet(SmartPropertyPrim.flagActualOpacityDirty)) {
+                let cur = this.parent;
+                let op = this.opacity;
+                while (cur) {
+                    op *= cur.opacity;
+                    cur = cur.parent;
+                }
+
+                this._actualOpacity = op;
+                this._clearFlags(SmartPropertyPrim.flagActualOpacityDirty);
+            }
+            return this._actualOpacity;
+        }
+
         /**
          * Get/set the layout engine to use for this primitive.
          * The default layout engine is the CanvasLayoutEngine.
@@ -2467,6 +2533,13 @@
             this._setFlags(SmartPropertyPrim.flagPositioningDirty);
         }
 
+        protected _spreadActualOpacityChanged() {
+            for (let child of this._children) {
+                child._setFlags(SmartPropertyPrim.flagActualOpacityDirty);
+                child._spreadActualOpacityChanged();
+            }
+        }
+
         private _changeLayoutEngine(engine: LayoutEngineBase) {
             this._layoutEngine = engine;
         }
@@ -2916,6 +2989,9 @@
             }
         }
 
+        protected _updateRenderMode() {
+        }
+
         /**
          * This method is used to alter the contentArea of the Primitive before margin is applied.
          * In most of the case you won't need to override this method, but it can prove some usefulness, check the Rectangle2D class for a concrete application.
@@ -2975,6 +3051,8 @@
         private _rotation: number;
         private _scale: number;
         private _origin: Vector2;
+        protected _opacity: number;
+        private _actualOpacity: number;
 
         // Stores the step of the parent for which the current global transform was computed
         // If the parent has a new step, it means this prim's global transform must be updated

+ 10 - 4
src/Canvas2d/babylon.rectangle2d.ts

@@ -57,9 +57,11 @@
                         this.instancingFillAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_FILLPARTID, effect);
                     }
 
+                    let glBuffer = context.instancedBuffers ? context.instancedBuffers[partIndex] : pid._partBuffer;
+                    let count = context.instancedBuffers ? context.instancesCount : pid._partData.usedElementCount;
                     canvas._addDrawCallCount(1, context.renderMode);
-                    engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingFillAttributes);
-                    engine.draw(true, 0, this.fillIndicesCount, pid._partData.usedElementCount);
+                    engine.updateAndBindInstancesBuffer(glBuffer, null, this.instancingFillAttributes);
+                    engine.draw(true, 0, this.fillIndicesCount, count);
                     engine.unbindInstanceAttributes();
                 } else {
                     canvas._addDrawCallCount(context.partDataEndIndex - context.partDataStartIndex, context.renderMode);
@@ -87,9 +89,11 @@
                         this.instancingBorderAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_BORDERPARTID, effect);
                     }
 
+                    let glBuffer = context.instancedBuffers ? context.instancedBuffers[partIndex] : pid._partBuffer;
+                    let count = context.instancedBuffers ? context.instancesCount : pid._partData.usedElementCount;
                     canvas._addDrawCallCount(1, context.renderMode);
-                    engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingBorderAttributes);
-                    engine.draw(true, 0, this.borderIndicesCount, pid._partData.usedElementCount);
+                    engine.updateAndBindInstancesBuffer(glBuffer, null, this.instancingBorderAttributes);
+                    engine.draw(true, 0, this.borderIndicesCount, count);
                     engine.unbindInstanceAttributes();
                 } else {
                     canvas._addDrawCallCount(context.partDataEndIndex - context.partDataStartIndex, context.renderMode);
@@ -309,6 +313,7 @@
          * - position: the X & Y positions relative to its parent. Alternatively the x and y settings can be set. Default is [0;0]
          * - rotation: the initial rotation (in radian) of the primitive. default is 0
          * - scale: the initial scale of the primitive. default is 1
+         * - opacity: set the overall opacity of the primitive, 1 to be opaque (default), less than 1 to be transparent.
          * - origin: define the normalized origin point location, default [0.5;0.5]
          * - size: the size of the group. Alternatively the width and height settings 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 edges rectangle).
@@ -340,6 +345,7 @@
             y                 ?: number,
             rotation          ?: number,
             scale             ?: number,
+            opacity           ?: number,
             origin            ?: Vector2,
             size              ?: Size,
             width             ?: number,

+ 114 - 21
src/Canvas2d/babylon.renderablePrim2d.ts

@@ -252,6 +252,8 @@
             this.id = partId;
             this.curElement = 0;
             this.dataElementCount = dataElementCount;
+            this.renderMode = 0;
+            this.arrayLengthChanged = false;
         }
 
         id: number;
@@ -272,6 +274,11 @@
             return null;
         }
 
+        @instanceData()
+        get opacity(): number {
+            return null;
+        }
+
         getClassTreeInfo(): ClassTreeInfo<InstanceClassInfo, InstancePropInfo> {
             if (!this.typeInfo) {
                 this.typeInfo = ClassTreeInfo.get<InstanceClassInfo, InstancePropInfo>(Object.getPrototypeOf(this));
@@ -309,12 +316,14 @@
                 return;
             }
 
+            this.arrayLengthChanged = true;
             this.freeElements();
             this._dataElementCount = value;
             this.allocElements();
         }
-
+        arrayLengthChanged: boolean;
         curElement: number;
+        renderMode: number;
         dataElements: DynamicFloatArrayElementInfo[];
         dataBuffer: DynamicFloatArray;
         typeInfo: ClassTreeInfo<InstanceClassInfo, InstancePropInfo>;
@@ -345,7 +354,11 @@
         }
 
         public set isAlphaTest(value: boolean) {
+            if (this._isAlphaTest === value) {
+                return;
+            }
             this._isAlphaTest = value;
+            this._updateRenderMode();
         }
 
         @dynamicLevelProperty(Prim2DBase.PRIM2DBASE_PROPCOUNT + 1, pi => RenderablePrim2D.isTransparentProperty = pi)
@@ -354,11 +367,19 @@
          * The setter should be used only by implementers of new primitive type.
          */
         public get isTransparent(): boolean {
-            return this._isTransparent;
+            return this._isTransparent || (this._opacity<1);
         }
 
         public set isTransparent(value: boolean) {
+            if (this._isTransparent === value) {
+                return;
+            }
             this._isTransparent = value;
+            this._updateRenderMode();
+        }
+
+        public get renderMode(): number {
+            return this._renderMode;
         }
 
         constructor(settings?: {
@@ -368,6 +389,7 @@
             isVisible    ?: boolean,
         }) {
             super(settings);
+
             this._isTransparent            = false;
             this._isAlphaTest              = false;
             this._transparentPrimitiveInfo = null;
@@ -441,10 +463,6 @@
             // 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._isFlagSet(SmartPropertyPrim.flagVisibilityChanged) || context.forceRefreshPrimitive || newInstance || (this._instanceDirtyFlags !== 0) || (this._globalTransformProcessStep !== this._globalTransformStep)) {
 
-                if (this.isTransparent) {
-                    //this.renderGroup._renderableData._transparentListChanged = true;
-                }
-
                 this._updateInstanceDataParts(gii);
             }
         }
@@ -498,13 +516,17 @@
             });
 
             // Get the GroupInfoDataPart corresponding to the render category of the part
+            let rm = 0;
             let gipd: GroupInfoPartData[] = null;
             if (this.isTransparent) {
                 gipd = gii.transparentData;
+                rm = Render2DContext.RenderModeTransparent;
             } else if (this.isAlphaTest) {
                 gipd = gii.alphaTestData;
+                rm = Render2DContext.RenderModeAlphaTest;
             } else {
                 gipd = gii.opaqueData;
+                rm = Render2DContext.RenderModeOpaque;
             }
 
             // For each instance data part of the primitive, allocate the instanced element it needs for render
@@ -512,6 +534,7 @@
                 let part = parts[i];
                 part.dataBuffer = gipd[i]._partData;
                 part.allocElements();
+                part.renderMode = rm;
             }
 
             // 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
@@ -574,22 +597,61 @@
 
         private _updateInstanceDataParts(gii: GroupInstanceInfo) {
             // Fetch the GroupInstanceInfo if we don't already have it
+            let rd = this.renderGroup._renderableData;
             if (!gii) {
-                gii = this.renderGroup._renderableData._renderGroupInstancesInfo.get(this.modelKey);
+                gii = rd._renderGroupInstancesInfo.get(this.modelKey);
+            }
+
+            let isTransparent = this.isTransparent;
+            let isAlphaTest = this.isAlphaTest;
+            let wereTransparent = false;
+
+            // Check a render mode change
+            let rmChanged = false;
+            if (this._instanceDataParts.length>0) {
+                let firstPart = this._instanceDataParts[0];
+                let partRM = firstPart.renderMode;
+                let curRM = this.renderMode;
+
+                if (partRM !== curRM) {
+                    wereTransparent = partRM === Render2DContext.RenderModeTransparent;
+                    rmChanged = true;
+                    let gipd: TransparentGroupInfoPartData[];
+                    switch (curRM) {
+                        case Render2DContext.RenderModeTransparent:
+                            gipd = gii.transparentData;
+                            break;
+                        case Render2DContext.RenderModeAlphaTest:
+                            gipd = gii.alphaTestData;
+                            break;
+                        default:
+                            gipd = gii.opaqueData;
+                    }
+
+                    for (let i = 0; i < this._instanceDataParts.length; i++) {
+                        let part = this._instanceDataParts[i];
+                        part.freeElements();
+                        part.dataBuffer = gipd[i]._partData;
+                        part.renderMode = curRM;
+                    }
+
+                }
             }
 
             // Handle changes related to ZOffset
-            if (this.isTransparent) {
+            let visChanged = this._isFlagSet(SmartPropertyPrim.flagVisibilityChanged);
+
+            if (isTransparent || wereTransparent) {
                 // Handle visibility change, which is also triggered when the primitive just got created
-                if (this._isFlagSet(SmartPropertyPrim.flagVisibilityChanged)) {
-                    if (this.isVisible) {
+                if (visChanged || rmChanged) {
+                    if (this.isVisible && !wereTransparent) {
                         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);
+                            this._transparentPrimitiveInfo = rd.addNewTransparentPrimitiveInfo(this, gii);
                         }
                     } else {
                         if (this._transparentPrimitiveInfo) {
-                            this.renderGroup._renderableData.removeTransparentPrimitiveInfo(this._transparentPrimitiveInfo);
+                            rd.removeTransparentPrimitiveInfo(this._transparentPrimitiveInfo);
                             this._transparentPrimitiveInfo = null;
                         }
                     }
@@ -597,29 +659,37 @@
                 }
             }
 
+            let rebuildTrans = false;
+
             // 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._isFlagSet(SmartPropertyPrim.flagVisibilityChanged) && !part.dataElements) {
+                if ((visChanged && !part.dataElements) || rmChanged) {
                     part.allocElements();
                 }
 
                 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)
+                part.arrayLengthChanged = false;
                 if (!this.refreshInstanceDataPart(part)) {
                     // Free the data element
                     if (part.dataElements) {
                         part.freeElements();
                     }
                 }
+
+                rebuildTrans = rebuildTrans || part.arrayLengthChanged;
             }
             this._instanceDirtyFlags = 0;
 
             // Make the appropriate data dirty
-            if (this.isTransparent) {
+            if (isTransparent) {
                 gii.transparentDirty = true;
-            } else if (this.isAlphaTest) {
+                if (rebuildTrans) {
+                    rd._transparentListChanged = true;
+                }
+            } else if (isAlphaTest) {
                 gii.alphaTestDirty = true;
             } else {
                 gii.opaqueDirty = true;
@@ -628,22 +698,33 @@
             this._clearFlags(SmartPropertyPrim.flagVisibilityChanged);    // Reset the flag as we've handled the case            
         }
 
-        public _getFirstIndexInDataBuffer(): number {
+        _updateTransparentSegmentIndices(ts: TransparentSegment) {
+            let minOff = Prim2DBase._bigInt;
+            let maxOff = 0;
+
             for (let part of this._instanceDataParts) {
                 if (part) {
-                    return part.dataElements[0].offset / part.dataBuffer.stride;
+                    for (let el of part.dataElements) {
+                        minOff = Math.min(minOff, el.offset);
+                        maxOff = Math.max(maxOff, el.offset);
+                    }
+                    ts.startDataIndex = minOff / part.dataBuffer.stride;
+                    ts.endDataIndex = (maxOff / part.dataBuffer.stride) + 1; // +1 for exclusive
                 }
             }
-            return null;
         }
 
-        public _getLastIndexInDataBuffer(): number {
+        _getPrimitiveLastIndex(): number {
+            let maxOff = 0;
+
             for (let part of this._instanceDataParts) {
                 if (part) {
-                    return part.dataElements[part.dataElements.length-1].offset / part.dataBuffer.stride;
+                    for (let el of part.dataElements) {
+                        maxOff = Math.max(maxOff, el.offset);
+                    }
+                    return (maxOff / part.dataBuffer.stride) + 1; // +1 for exclusive
                 }
             }
-            return null;
         }
 
         // This internal method is mainly used for transparency processing
@@ -812,11 +893,22 @@
             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.opacity = this.actualOpacity;
 
             // Stores zBias and it's inverse value because that's needed to compute the clip space W coordinate (which is 1/Z, so 1/zBias)
             part.zBias = new Vector2(zBias, invZBias);
         }
 
+        protected _updateRenderMode() {
+            if (this.isTransparent) {
+                this._renderMode = Render2DContext.RenderModeTransparent;
+            } else if (this.isAlphaTest) {
+                this._renderMode = Render2DContext.RenderModeAlphaTest;
+            } else {
+                this._renderMode = Render2DContext.RenderModeOpaque;
+            }
+        }
+
         private _modelRenderCache: ModelRenderCache;
         private _modelRenderInstanceID: string;
         private _transparentPrimitiveInfo: TransparentPrimitiveInfo;
@@ -824,6 +916,7 @@
         protected _instanceDataParts: InstanceDataBase[];
         protected _isAlphaTest: boolean;
         protected _isTransparent: boolean;
+        private _renderMode: number;
     }
 
 

+ 1 - 1
src/Canvas2d/babylon.shape2d.ts

@@ -171,7 +171,7 @@
         }
 
         private _updateTransparencyStatus() {
-            this.isTransparent = (this._border && this._border.isTransparent()) || (this._fill && this._fill.isTransparent());
+            this.isTransparent = (this._border && this._border.isTransparent()) || (this._fill && this._fill.isTransparent()) || (this.actualOpacity<1);
         }
 
         private _border: IBrush2D;

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

@@ -628,6 +628,7 @@
         public static flagWorldCacheChanged      = 0x0000800;    // set if the cached bitmap of a world space canvas changed
         public static flagChildrenFlatZOrder     = 0x0001000;    // set if all the children (direct and indirect) will share the same Z-Order
         public static flagZOrderDirty            = 0x0002000;    // set if the Z-Order for this prim and its children must be recomputed
+        public static flagActualOpacityDirty     = 0x0004000;    // set if the actualOpactity should be recomputed
 
         private   _flags             : number;
         private   _externalData      : StringDictionary<Object>;

+ 8 - 5
src/Canvas2d/babylon.sprite2d.ts

@@ -21,14 +21,13 @@
             let canvas = instanceInfo.owner.owner;
             var engine = canvas.engine;
 
+            var cur = engine.getAlphaMode();
             let effect = context.useInstancing ? this.effectInstanced : this.effect;
 
             engine.enableEffect(effect);
             effect.setTexture("diffuseSampler", this.texture);
             engine.bindBuffersDirectly(this.vb, this.ib, [1], 4, effect);
 
-            var cur = engine.getAlphaMode();
-
             if (context.renderMode !== Render2DContext.RenderModeOpaque) {
                 engine.setAlphaMode(Engine.ALPHA_COMBINE);
             }
@@ -38,9 +37,11 @@
                 if (!this.instancingAttributes) {
                     this.instancingAttributes = this.loadInstancingAttributes(Sprite2D.SPRITE2D_MAINPARTID, effect);
                 }
+                let glBuffer = context.instancedBuffers ? context.instancedBuffers[0] : pid._partBuffer;
+                let count = context.instancedBuffers ? context.instancesCount : pid._partData.usedElementCount;
                 canvas._addDrawCallCount(1, context.renderMode);
-                engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingAttributes);
-                engine.draw(true, 0, 6, pid._partData.usedElementCount);
+                engine.updateAndBindInstancesBuffer(glBuffer, null, this.instancingAttributes);
+                engine.draw(true, 0, 6, count);
                 engine.unbindInstanceAttributes();
             } else {
                 canvas._addDrawCallCount(context.partDataEndIndex - context.partDataStartIndex, context.renderMode);
@@ -239,6 +240,7 @@
          * - position: the X & Y positions relative to its parent. Alternatively the x and y properties can be set. Default is [0;0]
          * - rotation: the initial rotation (in radian) of the primitive. default is 0
          * - scale: the initial scale of the primitive. default is 1
+         * - opacity: set the overall opacity of the primitive, 1 to be opaque (default), less than 1 to be transparent.
          * - origin: define the normalized origin point location, default [0.5;0.5]
          * - spriteSize: the size of the sprite (in pixels), if null the size of the given texture will be used, default is null.
          * - spriteLocation: the location (in pixels) in the texture of the top/left corner of the Sprite to display, default is null (0,0)
@@ -270,6 +272,7 @@
             y                 ?: number,
             rotation          ?: number,
             scale             ?: number,
+            opacity           ?: number,
             origin            ?: Vector2,
             spriteSize        ?: Size,
             spriteLocation    ?: Vector2,
@@ -306,7 +309,7 @@
             this.spriteFrame = 0;
             this.invertY = (settings.invertY == null) ? false : settings.invertY;
             this.alignToPixel = (settings.alignToPixel == null) ? true : settings.alignToPixel;
-            this._isTransparent = true;
+            this.isAlphaTest = true;
 
             if (settings.spriteSize==null) {
                 var s = texture.getSize();

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

@@ -37,9 +37,11 @@
                     this.instancingAttributes = this.loadInstancingAttributes(Text2D.TEXT2D_MAINPARTID, effect);
                 }
 
+                let glBuffer = context.instancedBuffers ? context.instancedBuffers[0] : pid._partBuffer;
+                let count = context.instancedBuffers ? context.instancesCount : pid._partData.usedElementCount;
                 canvas._addDrawCallCount(1, context.renderMode);
-                engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingAttributes);
-                engine.draw(true, 0, 6, pid._partData.usedElementCount);
+                engine.updateAndBindInstancesBuffer(glBuffer, null, this.instancingAttributes);
+                engine.draw(true, 0, 6, count);
                 engine.unbindInstanceAttributes();
             } else {
                 canvas._addDrawCallCount(context.partDataEndIndex - context.partDataStartIndex, context.renderMode);
@@ -264,6 +266,7 @@
          * - position: the X & Y positions relative to its parent. Alternatively the x and y properties can be set. Default is [0;0]
          * - rotation: the initial rotation (in radian) of the primitive. default is 0
          * - scale: the initial scale of the primitive. default is 1
+         * - opacity: set the overall opacity of the primitive, 1 to be opaque (default), less than 1 to be transparent.
          * - 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".
          * - fontSuperSample: if true the text will be rendered with a superSampled font (the font is twice the given size). Use this settings if the text lies in world space or if it's scaled in.
@@ -296,6 +299,7 @@
             y                 ?: number,
             rotation          ?: number,
             scale             ?: number,
+            opacity           ?: number,
             origin            ?: Vector2,
             fontName          ?: string,
             fontSuperSample   ?: boolean,
@@ -332,7 +336,7 @@
             this._textSize        = null;
             this.text             = text;
             this.size             = (settings.size==null) ? null : settings.size;
-            this.isAlphaTest      = true;
+            this.isTransparent    = true;
         }
 
         protected levelIntersect(intersectInfo: IntersectInfo2D): boolean {

+ 2 - 0
src/Shaders/ellipse2d.vertex.fx

@@ -9,6 +9,7 @@ attribute float index;
 att vec2 zBias;
 att vec4 transformX;
 att vec4 transformY;
+att float opacity;
 
 #ifdef Border
 att float borderThickness;
@@ -98,6 +99,7 @@ void main(void) {
 	vColor = mix(borderGradientColor2, borderGradientColor1, v);	// As Y is inverted, Color2 first, then Color1
 #endif
 
+	vColor.a *= opacity;
 	vec4 pos;
 	pos.xy = pos2.xy * properties.xy;
 	pos.z = 1.0;

+ 2 - 0
src/Shaders/lines2d.vertex.fx

@@ -9,6 +9,7 @@ attribute vec2 position;
 att vec2 zBias;
 att vec4 transformX;
 att vec4 transformY;
+att float opacity;
 
 #ifdef FillSolid
 att vec4 fillSolidColor;
@@ -58,6 +59,7 @@ void main(void) {
 	vColor = mix(borderGradientColor2, borderGradientColor1, v);	// As Y is inverted, Color2 first, then Color1
 #endif
 
+	vColor.a *= opacity;
 	vec4 pos;
 	pos.xy = position.xy;
 	pos.z = 1.0;

+ 2 - 0
src/Shaders/rect2d.vertex.fx

@@ -9,6 +9,7 @@ attribute float index;
 att vec2 zBias;
 att vec4 transformX;
 att vec4 transformY;
+att float opacity;
 
 #ifdef Border
 att float borderThickness;
@@ -196,6 +197,7 @@ void main(void) {
 	vColor = mix(borderGradientColor2, borderGradientColor1, v);	// As Y is inverted, Color2 first, then Color1
 #endif
 
+	vColor.a *= opacity;
 	vec4 pos;
 	pos.xy = pos2.xy * properties.xy;
 	pos.z = 1.0;

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

@@ -1,4 +1,5 @@
 varying vec2 vUV;
+varying float vOpacity;
 uniform sampler2D diffuseSampler;
 
 void main(void) {
@@ -6,5 +7,6 @@ void main(void) {
 	if (color.a == 0.05) {
 		discard;
 	}
+	color.a *= vOpacity;
 	gl_FragColor = color;
 }

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

@@ -18,12 +18,13 @@ att vec3 properties;
 att vec2 zBias;
 att vec4 transformX;
 att vec4 transformY;
+att float opacity;
 
 // Uniforms
 
 // Output
 varying vec2 vUV;
-varying vec4 vColor;
+varying float vOpacity;
 
 void main(void) {
 
@@ -72,6 +73,7 @@ void main(void) {
 		pos.xy = pos2.xy * sizeUV * textureSize;
 	}
 
+	vOpacity = opacity;
 	pos.z = 1.0;
 	pos.w = 1.0;
 	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, 1);

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

@@ -11,6 +11,7 @@ att vec2 zBias;
 
 att vec4 transformX;
 att vec4 transformY;
+att float opacity;
 
 att vec2 topLeftUV;
 att vec2 sizeUV;
@@ -54,6 +55,7 @@ void main(void) {
 	vUV = (floor(vUV*textureSize) + vec2(0.0, 0.0)) / textureSize;
 
 	vColor = color;
+	vColor.a *= opacity;
 	vec4 pos;
 	pos.xy = floor(pos2.xy * superSampleFactor * sizeUV * textureSize);	// Align on target pixel to avoid bad interpolation
 	pos.z = 1.0;