浏览代码

Fill/Border based Brushes types are now a single Brush2D type
https://trello.com/c/lgXCLWuk

Primitives can now be rendered as multiple parts. Wiring of Effect's attributes and defines is automatic. @instanceData can now be categorized and Shaders may use define to vary their implem based on the used Brush2D.
https://trello.com/c/kidvE5tG

nockawa 9 年之前
父节点
当前提交
554fc0e935

+ 6 - 58
src/Canvas2d/babylon.brushes2d.ts

@@ -20,24 +20,12 @@
     }
 
     /**
-     * This interface defines the IBorder2D contract.
-     * Classes implementing a new type of 2D Border must implement this interface
+     * This interface defines the IBrush2D contract.
+     * Classes implementing a new type of Brush2D must implement this interface
      */
-    export interface IBorder2D extends ILockable {
+    export interface IBrush2D extends ILockable {
         /**
-         * It is critical for each instance of a given Border2D type to return a unique string that identifies it because the Border instance will certainly be part of the computed ModelKey for a given Primitive
-         * @returns A string identifier that uniquely identify the instance
-         */
-        toString(): string;
-    }
-
-    /**
-     * This interface defines the IFill2D contract.
-     * Classes implementing a new type of 2D Fill must implement this interface
-     */
-    export interface IFill2D extends ILockable {
-        /**
-         * It is critical for each instance of a given Fill2D type to return a unique string that identifies it because the Fill instance will certainly be part of the computed ModelKey for a given Primitive
+         * It is critical for each instance of a given Brush2D type to return a unique string that identifies it because the Border instance will certainly be part of the computed ModelKey for a given Primitive
          * @returns A string identifier that uniquely identify the instance
          */
         toString(): string;
@@ -73,46 +61,9 @@
     }
 
     /**
-     * This classs implements a Border that will be drawn with a uniform solid color (i.e. the same color everywhere in the border).
-     */
-    export class SolidColorBorder2D extends LockableBase implements IBorder2D {
-        constructor(color: Color4, lock: boolean = false) {
-            super();
-            this._color = color;
-            if (lock) {
-                this.lock();
-            }
-        }
-
-        /**
-         * The color used by this instance to render
-         * @returns the color object. Note that it's not a clone of the actual object stored in the instance so you MUST NOT modify it, otherwise unexpected behavior might occurs.
-         */
-        public get color(): Color4 {
-            return this._color;
-        }
-
-        public set color(value: Color4) {
-            if (this.isLocked()) {
-                return;
-            }
-
-            this._color = value;
-        }
-
-        /**
-         * Return a unique identifier of the instance, which is simply the hexadecimal representation (CSS Style) of the solid color.
-         */
-        public toString(): string {
-            return this._color.toHexString();
-        }
-        private _color: Color4;
-    }
-
-    /**
-     * This class implements a Fill that will be drawn with a uniform solid color (i.e. the same everywhere inside the primitive).
+     * This classs implements a Brush that will be drawn with a uniform solid color (i.e. the same color everywhere in the content where the brush is assigned to).
      */
-    export class SolidColorFill2D extends LockableBase implements IFill2D {
+    export class SolidColorBrush2D extends LockableBase implements IBrush2D {
         constructor(color: Color4, lock: boolean = false) {
             super();
             this._color = color;
@@ -143,9 +94,6 @@
         public toString(): string {
             return this._color.toHexString();
         }
-
         private _color: Color4;
     }
-
-
 }

+ 13 - 34
src/Canvas2d/babylon.canvas2d.ts

@@ -115,14 +115,14 @@
          * Note that Canvas with a Caching Strategy of
          * @returns If the background is not set, null will be returned, otherwise a valid fill object is returned.
          */
-        public get backgroundFill(): IFill2D {
+        public get backgroundFill(): IBrush2D {
             if (!this._background || !this._background.isVisible) {
                 return null;
             }
             return this._background.fill;
         }
 
-        public set backgroundFill(value: IFill2D) {
+        public set backgroundFill(value: IBrush2D) {
             this.checkBackgroundAvailability();
 
             if (value === this._background.fill) {
@@ -137,14 +137,14 @@
          * Property that defines the border object used to draw the background of the Canvas.
          * @returns If the background is not set, null will be returned, otherwise a valid border object is returned.
          */
-        public get border(): IBorder2D {
+        public get border(): IBrush2D {
             if (!this._background || !this._background.isVisible) {
                 return null;
             }
             return this._background.border;
         }
 
-        public set border(value: IBorder2D) {
+        public set border(value: IBrush2D) {
             this.checkBackgroundAvailability();
 
             if (value === this._background.border) {
@@ -261,44 +261,23 @@
         private static _groupTextureCacheSize = 1024;
 
         /**
-         * Get a Solid Color Fill instance matching the given color.
+         * Get a Solid Color Brush instance matching the given color.
          * @param color The color to retrieve
-         * @return A shared instance of the SolidColorFill2D class that use the given color
+         * @return A shared instance of the SolidColorBrush2D class that use the given color
          */
-        public static GetSolidColorFill(color: Color4): IFill2D {
-            return Canvas2D._solidColorFills.getOrAddWithFactory(color.toHexString(), () => new SolidColorFill2D(color.clone(), true));
+        public static GetSolidColorFill(color: Color4): IBrush2D {
+            return Canvas2D._solidColorBrushes.getOrAddWithFactory(color.toHexString(), () => new SolidColorBrush2D(color.clone(), true));
         }
 
         /**
-         * Get a Solid Color Border instance matching the given color.
+         * Get a Solid Color Brush instance matching the given color expressed as a CSS formatted hexadecimal value.
          * @param color The color to retrieve
-         * @return A shared instance of the SolidColorBorder2D class that use the given color
+         * @return A shared instance of the SolidColorBrush2D class that uses the given color
          */
-        public static GetSolidColorBorder(color: Color4): IBorder2D {
-            return Canvas2D._solidColorBorders.getOrAddWithFactory(color.toHexString(), () => new SolidColorBorder2D(color.clone(), true));
+        public static GetSolidColorBrushFromHex(hexValue: string): IBrush2D {
+            return Canvas2D._solidColorBrushes.getOrAddWithFactory(hexValue, () => new SolidColorBrush2D(Color4.FromHexString(hexValue), true));
         }
 
-        /**
-         * Get a Solid Color Fill instance matching the given color expressed as a CSS formatted hexadecimal value.
-         * @param color The color to retrieve
-         * @return A shared instance of the SolidColorFill2D class that use the given color
-         */
-        public static GetSolidColorFillFromHex(hexValue: string): IFill2D {
-            return Canvas2D._solidColorFills.getOrAddWithFactory(hexValue, () => new SolidColorFill2D(Color4.FromHexString(hexValue), true));
-        }
-
-        /**
-         * Get a Solid Color Border instance matching the given color expressed as a CSS formatted hexadecimal value.
-         * @param color The color to retrieve
-         * @return A shared instance of the SolidColorBorder2D class that use the given color
-         */
-        public static GetSolidColorBorderFromHex(hexValue: string): IBorder2D {
-            return Canvas2D._solidColorBorders.getOrAddWithFactory(hexValue, () => new SolidColorBorder2D(Color4.FromHexString(hexValue), true));
-        }
-
-        private static _solidColorFills: StringDictionary<IFill2D> = new StringDictionary<IFill2D>();
-        private static _solidColorBorders: StringDictionary<IBorder2D> = new StringDictionary<IBorder2D>();
+        private static _solidColorBrushes: StringDictionary<IBrush2D> = new StringDictionary<IBrush2D>();
     }
-
-
 }

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

@@ -218,37 +218,39 @@
 
                 // For each different model of primitive to render
                 this.groupRenderInfo.forEach((k, v) => {
-                    // If the instances of the model was changed, pack the data
-                    let instanceData = v._instancesData.pack();
-
-                    // Compute the size the instance buffer should have
-                    let neededSize = v._instancesData.usedElementCount * v._instancesData.stride * 4;
-
-                    // Check if we have to (re)create the instancesBuffer because there's none or the size doesn't match
-                    if (!v._instancesBuffer || (v._instancesBufferSize !== neededSize)) {
-                        if (v._instancesBuffer) {
-                            engine.deleteInstancesBuffer(v._instancesBuffer);
+                    for (let i = 0; i < v._instancesPartsData.length; i++) {
+                        // If the instances of the model was changed, pack the data
+                        let instanceData = v._instancesPartsData[i].pack();
+
+                        // Compute the size the instance buffer should have
+                        let neededSize = v._instancesPartsData[i].usedElementCount * v._instancesPartsData[i].stride * 4;
+
+                        // Check if we have to (re)create the instancesBuffer because there's none or the size doesn't match
+                        if (!v._instancesPartsBuffer[i] || (v._instancesPartsBufferSize[i] !== neededSize)) {
+                            if (v._instancesPartsBuffer[i]) {
+                                engine.deleteInstancesBuffer(v._instancesPartsBuffer[i]);
+                            }
+                            v._instancesPartsBuffer[i] = engine.createInstancesBuffer(neededSize);
+                            v._instancesPartsBufferSize[i] = neededSize;
+                            v._dirtyInstancesData = true;
+
+                            // Update the WebGL buffer to match the new content of the instances data
+                            engine._gl.bufferSubData(engine._gl.ARRAY_BUFFER, 0, instanceData);
+                        } else if (v._dirtyInstancesData) {
+                            // Update the WebGL buffer to match the new content of the instances data
+                            engine._gl.bindBuffer(engine._gl.ARRAY_BUFFER, v._instancesPartsBuffer[i]);
+                            engine._gl.bufferSubData(engine._gl.ARRAY_BUFFER, 0, instanceData);
+
+                            v._dirtyInstancesData = false;
                         }
-                        v._instancesBuffer = engine.createInstancesBuffer(neededSize);
-                        v._instancesBufferSize = neededSize;
-                        v._dirtyInstancesData = true;
-
-                        // Update the WebGL buffer to match the new content of the instances data
-                        engine._gl.bufferSubData(engine._gl.ARRAY_BUFFER, 0, instanceData);
-                    } else if (v._dirtyInstancesData) {
-                        // Update the WebGL buffer to match the new content of the instances data
-                        engine._gl.bindBuffer(engine._gl.ARRAY_BUFFER, v._instancesBuffer);
-                        engine._gl.bufferSubData(engine._gl.ARRAY_BUFFER, 0, instanceData);
-
-                        v._dirtyInstancesData = false;
-                    }
 
-                    // render all the instances of this model, if the render method returns true then our instances are no longer dirty
-                    let renderFailed = !v._modelCache.render(v, context);
+                        // render all the instances of this model, if the render method returns true then our instances are no longer dirty
+                        let renderFailed = !v._modelCache.render(v, context);
 
-                    // Update dirty flag/related
-                    v._dirtyInstancesData = renderFailed;
-                    failedCount += renderFailed ? 1 : 0;
+                        // Update dirty flag/related
+                        v._dirtyInstancesData = renderFailed;
+                        failedCount += renderFailed ? 1 : 0;
+                    }
                 });
 
                 // The group's content is no longer dirty

+ 32 - 20
src/Canvas2d/babylon.modelRenderCache.ts

@@ -4,22 +4,28 @@
     }
 
     export class GroupInstanceInfo {
-        constructor(owner: Group2D, classTreeInfo: ClassTreeInfo<InstanceClassInfo, InstancePropInfo>, cache: ModelRenderCacheBase) {
+        constructor(owner: Group2D, cache: ModelRenderCache) {
             this._owner = owner;
-            this._classTreeInfo = classTreeInfo;
             this._modelCache = cache;
+            this._instancesPartsData = new Array<DynamicFloatArray>();
+            this._instancesPartsBuffer = new Array<WebGLBuffer>();
+            this._instancesPartsBufferSize = new Array<number>();
         }
 
         _owner: Group2D;
-        _classTreeInfo: ClassTreeInfo<InstanceClassInfo, InstancePropInfo>;
-        _modelCache: ModelRenderCacheBase;
-        _instancesData: DynamicFloatArray;
+        _modelCache: ModelRenderCache;
+        _instancesPartsData: DynamicFloatArray[];
         _dirtyInstancesData: boolean;
-        _instancesBuffer: WebGLBuffer;
-        _instancesBufferSize: number;
+        _instancesPartsBuffer: WebGLBuffer[];
+        _instancesPartsBufferSize: number[];
     }
 
-    export class ModelRenderCacheBase {
+    export class ModelRenderCache {
+        constructor() {
+            this._nextKey = 1;
+            this._instancesData = new StringDictionary<InstanceDataBase[]>();
+        }
+
         /**
          * Render the model instances
          * @param instanceInfo
@@ -29,18 +35,10 @@
         render(instanceInfo: GroupInstanceInfo, context: Render2DContext): boolean {
             return true;
         }
-    }
 
-    export class ModelRenderCache<TInstData> extends ModelRenderCacheBase {
-
-        constructor() {
-            super();
-            this._nextKey = 1;
-            this._instancesData = new StringDictionary<TInstData>();
-        }
-
-        addInstanceData(data: TInstData): string {
+        addInstanceDataParts(data: InstanceDataBase[]): string {
             let key = this._nextKey.toString();
+
             if (!this._instancesData.add(key, data)) {
                 throw Error(`Key: ${key} is already allocated`);
             }
@@ -54,9 +52,23 @@
             this._instancesData.remove(key);
         }
 
-        _instancesData: StringDictionary<TInstData>;
+        protected loadInstancingAttributes(effect: Effect): Array<InstancingAttributeInfo[]> {
+            var iai = new Array<InstancingAttributeInfo[]>();
+
+            for (let i = 0; i < this._partsClassInfo.length; i++) {
+                var ci = this._partsClassInfo[i];
+                var categories = this._partsUsedCategories[i];
+                iai.push(ci.classContent.getInstancingAttributeInfos(effect, categories));
+            }
+
+            return iai;
+        }
+
+        _instancesData: StringDictionary<InstanceDataBase[]>;
 
         private _nextKey: number;
+        _partsDataStride: number[];
+        _partsUsedCategories: Array<string[]>;
+        _partsClassInfo: ClassTreeInfo<InstanceClassInfo, InstancePropInfo>[];
     }
-
 }

+ 31 - 20
src/Canvas2d/babylon.rectangle2d.ts

@@ -1,10 +1,10 @@
 module BABYLON {
-    export class Rectangle2DRenderCache extends ModelRenderCache<Rectangle2DInstanceData> {
+    export class Rectangle2DRenderCache extends ModelRenderCache {
         fillVB: WebGLBuffer;
         fillIB: WebGLBuffer;
         borderVB: WebGLBuffer;
         borderIB: WebGLBuffer;
-        instancingAttributes: InstancingAttributeInfo[];
+        instancingAttributes: Array<InstancingAttributeInfo[]>;
 
         effect: Effect;
 
@@ -16,24 +16,28 @@
 
             // Compute the offset locations of the attributes in the vertexshader that will be mapped to the instance buffer data
             if (!this.instancingAttributes) {
-                this.instancingAttributes = instanceInfo._classTreeInfo.classContent.getInstancingAttributeInfos(this.effect);
+                this.instancingAttributes = this.loadInstancingAttributes(this.effect);
             }
             var engine = instanceInfo._owner.owner.engine;
 
             engine.enableEffect(this.effect);
             engine.bindBuffers(this.fillVB, this.fillIB, [1], 4, this.effect);
 
-            engine.updateAndBindInstancesBuffer(instanceInfo._instancesBuffer, null, this.instancingAttributes);
+            engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[0], null, this.instancingAttributes[0]);
 
-            engine.draw(true, 0, Rectangle2D.roundSubdivisions * 4 * 3, instanceInfo._instancesData.usedElementCount);
+            engine.draw(true, 0, Rectangle2D.roundSubdivisions * 4 * 3, instanceInfo._instancesPartsData[0].usedElementCount);
 
-            engine.unBindInstancesBuffer(instanceInfo._instancesBuffer, this.instancingAttributes);
+            engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[0], this.instancingAttributes[0]);
 
             return true;
         }
     }
 
-    export class Rectangle2DInstanceData extends InstanceDataBase {
+    export class Rectangle2DInstanceData extends Shape2DInstanceData {
+        constructor(partId: number) {
+            super(partId);
+        }
+
         @instanceData()
         get properties(): Vector3 {
             return null;
@@ -41,7 +45,7 @@
     }
 
     @className("Rectangle2D")
-    export class Rectangle2D extends Shape2D<Rectangle2DInstanceData> {
+    export class Rectangle2D extends Shape2D {
 
         public static sizeProperty: Prim2DPropInfo;
         public static notRoundedProperty: Prim2DPropInfo;
@@ -80,37 +84,42 @@
             this._levelBoundingInfo.extent = this.size.clone();
         }
 
-        protected setupRectangle2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, size: Size, roundRadius = 0, fill?: IFill2D, border?: IBorder2D) {
+        protected setupRectangle2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, size: Size, roundRadius = 0, fill?: IBrush2D, border?: IBrush2D) {
             this.setupRenderablePrim2D(owner, parent, id, position, true, fill, border);
             this.size = size;
             this.notRounded = !roundRadius;
             this.roundRadius = roundRadius;
         }
 
-        public static Create(parent: Prim2DBase, id: string, x: number, y: number, width: number, height: number, fill?: IFill2D, border?: IBorder2D): Rectangle2D {
+        public static Create(parent: Prim2DBase, id: string, x: number, y: number, width: number, height: number, fill?: IBrush2D, border?: IBrush2D): Rectangle2D {
             Prim2DBase.CheckParent(parent);
 
             let rect = new Rectangle2D();
             rect.setupRectangle2D(parent.owner, parent, id, new Vector2(x, y), new Size(width, height), null);
-            rect.fill = fill || Canvas2D.GetSolidColorFillFromHex("#FFFFFFFF");
+            rect.fill = fill || Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
             rect.border = border;
             return rect;
         }
 
-        public static CreateRounded(parent: Prim2DBase, id: string, x: number, y: number, width: number, height: number, roundRadius = 0, fill?: IFill2D, border?: IBorder2D): Rectangle2D {
+        public static CreateRounded(parent: Prim2DBase, id: string, x: number, y: number, width: number, height: number, roundRadius = 0, fill?: IBrush2D, border?: IBrush2D): Rectangle2D {
             Prim2DBase.CheckParent(parent);
 
             let rect = new Rectangle2D();
             rect.setupRectangle2D(parent.owner, parent, id, new Vector2(x, y), new Size(width, height), roundRadius);
-            rect.fill = fill || Canvas2D.GetSolidColorFillFromHex("#FFFFFFFF");
+            rect.fill = fill || Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
             rect.border = border;
             return rect;
         }
 
         public static roundSubdivisions = 16;
 
-        protected createModelRenderCache(): ModelRenderCache<Rectangle2DInstanceData> {
+        protected createModelRenderCache(): ModelRenderCache {
             let renderCache = new Rectangle2DRenderCache();
+            return renderCache;
+        }
+
+        protected setupModelRenderCache(modelRenderCache: ModelRenderCache) {
+            let renderCache = <Rectangle2DRenderCache>modelRenderCache;
             let engine = this.owner.engine;
 
             // Need to create vb/ib for the fill part?
@@ -133,23 +142,25 @@
 
                 renderCache.fillIB = engine.createIndexBuffer(ib);
 
-                renderCache.effect = engine.createEffect({ vertex: "rect2d", fragment: "rect2d" }, ["index", "zBias", "transformX", "transformY", "origin", "properties"], [], [], "");
+
+                var ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_FILLPARTID, ["index"]);
+                renderCache.effect = engine.createEffect({ vertex: "rect2d", fragment: "rect2d" }, ei.attributes, [], [], ei.defines);
             }
 
             return renderCache;
         }
 
 
-        protected createInstanceData(): Rectangle2DInstanceData {
-            return new Rectangle2DInstanceData();
+        protected createInstanceDataParts(): InstanceDataBase[] {
+            return [new Rectangle2DInstanceData(Shape2D.SHAPE2D_FILLPARTID)];
         }
 
-        protected refreshInstanceData(): boolean {
-            if (!super.refreshInstanceData()) {
+        protected refreshInstanceDataParts(): boolean {
+            if (!super.refreshInstanceDataParts()) {
                 return false;
             }
 
-            let d = this._instanceData;
+            let d = <Rectangle2DInstanceData>this._instanceDataParts[0];
             let size = this.size;
             d.properties = new Vector3(size.width, size.height, this.roundRadius || 0);
             return true;

+ 171 - 81
src/Canvas2d/babylon.renderablePrim2d.ts

@@ -7,22 +7,26 @@
         }
 
         mapProperty(propInfo: InstancePropInfo) {
-            propInfo.instanceOffset = (this._baseInfo ? this._baseInfo._nextOffset : 0) + this._nextOffset;
+            propInfo.instanceOffset = this._baseOffset + this._nextOffset;
+            //console.log(`New PropInfo. Category: ${propInfo.category}, Name: ${propInfo.attributeName}, Offset: ${propInfo.instanceOffset}, Size: ${propInfo.size/4}`);
             this._nextOffset += (propInfo.size / 4);
             this._attributes.push(propInfo);
         }
 
-        getInstancingAttributeInfos(effect: Effect): InstancingAttributeInfo[] {
+        getInstancingAttributeInfos(effect: Effect, categories: string[]): InstancingAttributeInfo[] {
             let res = new Array<InstancingAttributeInfo>();
             let curInfo: InstanceClassInfo = this;
             while (curInfo) {
                 for (let attrib of curInfo._attributes) {
-                    let index = effect.getAttributeLocationByName(attrib.attributeName);
-                    let iai = new InstancingAttributeInfo();
-                    iai.index = index;
-                    iai.attributeSize = attrib.size / 4; // attrib.size is in byte and we need to store in "component" (i.e float is 1, vec3 is 3)
-                    iai.offset = attrib.instanceOffset * 4; // attrub.instanceOffset is in float, iai.offset must be in bytes
-                    res.push(iai);
+                    // Only map if there's no category assigned to the instance data or if there's a category and it's in the given list
+                    if (!attrib.category || categories.indexOf(attrib.category) !== -1) {
+                        let index = effect.getAttributeLocationByName(attrib.attributeName);
+                        let iai = new InstancingAttributeInfo();
+                        iai.index = index;
+                        iai.attributeSize = attrib.size / 4; // attrib.size is in byte and we need to store in "component" (i.e float is 1, vec3 is 3)
+                        iai.offset = attrib.instanceOffset * 4; // attrub.instanceOffset is in float, iai.offset must be in bytes
+                        res.push(iai);
+                    }
                 }
 
                 curInfo = curInfo._baseInfo;
@@ -30,7 +34,31 @@
             return res;
         }
 
-        public instanceDataStride;
+        getShaderAttributes(categories: string[]): string[] {
+            let res = new Array<string>();
+            let curInfo: InstanceClassInfo = this;
+            while (curInfo) {
+                for (let attrib of curInfo._attributes) {
+                    // Only map if there's no category assigned to the instance data or if there's a category and it's in the given list
+                    if (!attrib.category || categories.indexOf(attrib.category) !== -1) {
+                        res.push(attrib.attributeName);
+                    }
+                }
+
+                curInfo = curInfo._baseInfo;
+            }
+            return res;
+        }
+
+        private get _baseOffset(): number {
+            var curOffset = 0;
+            var curBase = this._baseInfo;
+            while (curBase) {
+                curOffset += curBase._nextOffset;
+                curBase = curBase._baseInfo;
+            }
+            return curOffset;
+        }
 
         private _baseInfo: InstanceClassInfo;
         private _nextOffset;
@@ -39,6 +67,7 @@
 
     export class InstancePropInfo {
         attributeName: string;
+        category: string;
         size: number;
         shaderOffset: number;
         instanceOffset: number;
@@ -77,6 +106,7 @@
                 this.dataType = ShaderDataType.Color4;
                 return;
             }
+            return;
         }
 
         writeData(array: Float32Array, offset: number, val) {
@@ -140,22 +170,25 @@
         }
     }
 
-    export function instanceData<T>(name?: string): (target: Object, propName: string | symbol, descriptor: TypedPropertyDescriptor<T>) => void {
+    export function instanceData<T>(category?: string, shaderAttributeName?: string): (target: Object, propName: string | symbol, descriptor: TypedPropertyDescriptor<T>) => void {
         return (target: Object, propName: string | symbol, descriptor: TypedPropertyDescriptor<T>) => {
 
             let dic = ClassTreeInfo.getOrRegister<InstanceClassInfo, InstancePropInfo>(target, (base) => new InstanceClassInfo(base));
             var node = dic.getLevelOf(target);
-            name = name || <string>propName;
+            let instanceDataName = <string>propName;
+            shaderAttributeName = shaderAttributeName || instanceDataName;
+
 
-            let info = node.levelContent.get(name);
+            let info = node.levelContent.get(instanceDataName);
             if (info) {
-                throw new Error(`The ID ${name} is already taken by another instance data`);
+                throw new Error(`The ID ${instanceDataName} is already taken by another instance data`);
             }
 
             info = new InstancePropInfo();
-            info.attributeName = name;
+            info.attributeName = shaderAttributeName;
+            info.category = category;
 
-            node.levelContent.add(name, info);
+            node.levelContent.add(instanceDataName, info);
 
             descriptor.get = function () {
                 return null;
@@ -177,6 +210,11 @@
     }
 
     export class InstanceDataBase {
+        constructor(partId: number) {
+            this.id = partId;
+        }
+
+        id: number;
         isVisible: boolean;
 
         @instanceData()
@@ -212,10 +250,10 @@
     }
 
     @className("RenderablePrim2D")
-    export class RenderablePrim2D<TInstData extends InstanceDataBase> extends Prim2DBase {
+    export class RenderablePrim2D extends Prim2DBase {
         static RENDERABLEPRIM2D_PROPCOUNT: number = Prim2DBase.PRIM2DBASE_PROPCOUNT + 5;
 
-        setupRenderablePrim2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, isVisible: boolean, fill: IFill2D, border: IBorder2D) {
+        setupRenderablePrim2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, isVisible: boolean, fill: IBrush2D, border: IBrush2D) {
             this.setupPrim2DBase(owner, parent, id, position);
             this._isTransparent = false;
         }
@@ -230,9 +268,11 @@
             }
 
             // Need to create the model?
+            let setupModelRenderCache = false;
             if (!this._modelRenderCache || this._modelDirty) {
                 this._modelRenderCache = SmartPropertyPrim.GetOrAddModelCache(this.modelKey, (key: string) => this.createModelRenderCache());
                 this._modelDirty = false;
+                setupModelRenderCache = true;
             }
 
             // Need to create the instance?
@@ -240,48 +280,74 @@
             let newInstance = false;
             if (!this._modelRenderInstanceID) {
                 newInstance = true;
-                let id = this.createInstanceData();
-                this._instanceData = id;
-
-                let cti = id.getClassTreeInfo();
-                if (!cti.classContent.instanceDataStride) {
-                    // Make sure the instance is visible other the properties won't be set and their size/offset wont be computed
-                    let curVisible = this.isVisible;
-                    this.isVisible = true;
-                    // We manually trigger refreshInstanceData for the only sake of evaluating each isntance property size and offset in the instance data, this can only be made at runtime. Once it's done we have all the information to create the instance data buffer.
-                    this.refreshInstanceData();
-                    this.isVisible = curVisible;
-
-                    var size = 0;
-                    cti.fullContent.forEach((k, v) => {
-                        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;
-                        }
-                    });
-                    cti.classContent.instanceDataStride = size;
+                let parts = this.createInstanceDataParts();
+                this._instanceDataParts = parts;
+
+                if (!this._modelRenderCache._partsDataStride) {
+                    let ctiArray = new Array<ClassTreeInfo<InstanceClassInfo, InstancePropInfo>>();
+                    var dataStrides = new Array<number>();
+                    var usedCat = new Array<string[]>();
+
+                    for (var dataPart of parts) {
+                        let cat = this.getUsedShaderCategories(dataPart);
+                        let 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 isntance property size and offset in the instance data, this can only be made at runtime. Once it's done we have all the information to create the instance data buffer.
+                        this.refreshInstanceDataParts();
+                        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);
+                        usedCat.push(cat);
+                        ctiArray.push(cti);
+                    }
+                    this._modelRenderCache._partsDataStride = dataStrides;
+                    this._modelRenderCache._partsUsedCategories = usedCat;
+                    this._modelRenderCache._partsClassInfo = ctiArray;
                 }
 
-                gii = this.renderGroup.groupRenderInfo.getOrAddWithFactory(this.modelKey, k => new GroupInstanceInfo(this.renderGroup, cti, this._modelRenderCache));
-                if (!gii._instancesData) {
-                    // instanceDataStride's unit is byte but DynamicFloatArray is float32, so div by four to get the correct number
-                    gii._instancesData = new DynamicFloatArray(cti.classContent.instanceDataStride / 4, 50);
+                gii = this.renderGroup.groupRenderInfo.getOrAddWithFactory(this.modelKey, k => new GroupInstanceInfo(this.renderGroup, this._modelRenderCache));
+
+                if (gii._instancesPartsData.length===0) {
+                    for (let stride of this._modelRenderCache._partsDataStride) {
+                        // instanceDataStride's unit is byte but DynamicFloatArray is float32, so div by four to get the correct number
+                        gii._instancesPartsData.push(new DynamicFloatArray(stride / 4, 50));
+                    }
                 }
 
-                id._dataBuffer = gii._instancesData;
-                id._dataElement = id._dataBuffer.allocElement();
+                for (let i = 0; i < parts.length; i++) {
+                    let part = parts[i];
+                    part._dataBuffer = gii._instancesPartsData[i];
+                    part._dataElement = part._dataBuffer.allocElement();
+                }
+
+                this._modelRenderInstanceID = this._modelRenderCache.addInstanceDataParts(this._instanceDataParts);
+            }
 
-                this._modelRenderInstanceID = this._modelRenderCache.addInstanceData(this._instanceData);
+            if (setupModelRenderCache) {
+                this.setupModelRenderCache(this._modelRenderCache);
             }
 
             if (context.forceRefreshPrimitive || newInstance || (this._instanceDirtyFlags !== 0) || (this._globalTransformPreviousStep !== this._globalTransformStep)) {
                 // Will return false if the instance should not be rendered (not visible or other any reasons)
-                if (!this.refreshInstanceData()) {
-                    // Free the data element
-                    if (this._instanceData._dataElement) {
-                        this._instanceData._dataBuffer.freeElement(this._instanceData._dataElement);
-                        this._instanceData._dataElement = null;
+                if (!this.refreshInstanceDataParts()) {
+                    for (let part of this._instanceDataParts) {
+                        // Free the data element
+                        if (part._dataElement) {
+                            part._dataBuffer.freeElement(part._dataElement);
+                            part._dataElement = null;
+                        }
                     }
                 }
                 this._instanceDirtyFlags = 0;
@@ -294,54 +360,78 @@
             }
         }
 
+        protected getDataPartEffectInfo(dataPartId: number, vertexBufferAttributes: string[]): {attributes: string[], defines: string} {
+            var dataPart = Tools.first(this._instanceDataParts, i => i.id === dataPartId);
+            if (!dataPart) {
+                return null;
+            }
+
+            var cti = dataPart.getClassTreeInfo();
+            var categories = this.getUsedShaderCategories(dataPart);
+            var attributes = vertexBufferAttributes.concat(cti.classContent.getShaderAttributes(categories));
+            var defines = "";
+            categories.forEach(c => { defines += `#define ${c}\n`});
+
+            return { attributes: attributes, defines: defines };
+        }
+
         public get isTransparent(): boolean {
             return this._isTransparent;
         }
 
-        protected createModelRenderCache(): ModelRenderCache<TInstData> {
+        protected createModelRenderCache(): ModelRenderCache {
             return null;
         }
 
-        protected createInstanceData(): TInstData {
+        protected setupModelRenderCache(modelRenderCache: ModelRenderCache) {
+        }
+
+        protected createInstanceDataParts(): InstanceDataBase[] {
             return null;
         }
 
-        protected refreshInstanceData(): boolean {
-            var d = this._instanceData;
-            if (!this.isVisible) {
-                return false;
+        protected getUsedShaderCategories(dataPart: InstanceDataBase): string[] {
+            return [];
+        }
+
+        protected refreshInstanceDataParts(): boolean {
+            for (let part of this._instanceDataParts) {
+                if (!this.isVisible) {
+                    return false;
+                }
+
+                part.isVisible = this.isVisible;
+                let t = this.renderGroup.invGlobalTransform.multiply(this._globalTransform);
+                let size = (<Size>this.renderGroup.viewportSize);
+                let zBias = this.getActualZOffset();
+
+                // 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. Has 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.
+                // Same thing for Y, except the "* -2" instead of "* 2" to switch the origin from top to bottom (has expected by the clip space)
+                let w = size.width * zBias;
+                let h = size.height * zBias;
+                let invZBias = 1 / zBias;
+                let tx = new Vector4(t.m[0] * 2 / w, t.m[4] * 2 / w, t.m[8], (t.m[12] * 2 / w) - (invZBias));
+                let ty = new Vector4(t.m[1] * -2 / h, t.m[5] * -2 / h, t.m[9], ((t.m[13] * 2 / h) - (invZBias)) * -1);
+                part.transformX = tx;
+                part.transformY = ty;
+                part.origin = this.origin;
+
+                // 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);
             }
 
-            d.isVisible = this.isVisible;
-            let t = this.renderGroup.invGlobalTransform.multiply(this._globalTransform);
-            let size = (<Size>this.renderGroup.viewportSize);
-            let zBias = this.getActualZOffset();
-
-            // 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. Has 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.
-            // Same thing for Y, except the "* -2" instead of "* 2" to switch the origin from top to bottom (has expected by the clip space)
-            let w = size.width * zBias;
-            let h = size.height * zBias;
-            let invZBias = 1 / zBias;
-            let tx = new Vector4(t.m[0] * 2 / w, t.m[4] * 2 / w, t.m[8], (t.m[12] * 2 / w) - (invZBias));
-            let ty = new Vector4(t.m[1] * -2 / h, t.m[5] * -2 / h, t.m[9], ((t.m[13] * 2 / h) - (invZBias)) * -1);
-            d.transformX = tx;
-            d.transformY = ty;
-            d.origin = this.origin;
-
-            // 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)
-            d.zBias = new Vector2(zBias, invZBias);
             return true;
         }
 
-        private _modelRenderCache: ModelRenderCache<TInstData>;
+        private _modelRenderCache: ModelRenderCache;
         private _modelRenderInstanceID: string;
 
-        protected _instanceData: TInstData;
+        protected _instanceDataParts: InstanceDataBase[];
         protected _isTransparent: boolean;
     }
 

+ 71 - 14
src/Canvas2d/babylon.shape2d.ts

@@ -1,38 +1,95 @@
 module BABYLON {
+
     @className("Shape2D")
-    export class Shape2D<TInstData extends InstanceDataBase> extends RenderablePrim2D<TInstData> {
+    export class Shape2D extends RenderablePrim2D {
+        static SHAPE2D_BORDERPARTID = 1;
+        static SHAPE2D_FILLPARTID = 1;
+        static SHAPE2D_CATEGORY_BORDERSOLID = "BorderSolid";
+        static SHAPE2D_CATEGORY_FILLSOLID = "FillSolid";
+
         static SHAPE2D_PROPCOUNT: number = RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 5;
         public static borderProperty: Prim2DPropInfo;
         public static fillProperty: Prim2DPropInfo;
 
         @modelLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 1, pi => Shape2D.borderProperty = pi, true)
-        public get border(): IBorder2D {
+        public get border(): IBrush2D {
             return this._border;
         }
 
-        public set border(value: IBorder2D) {
-            if (value === this._border) {
-                return;
-            }
-
+        public set border(value: IBrush2D) {
             this._border = value;
         }
 
         @modelLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 2, pi => Shape2D.fillProperty = pi, true)
-        public get fill(): IFill2D {
+        public get fill(): IBrush2D {
             return this._fill;
         }
 
-        public set fill(value: IBorder2D) {
-            if (value === this._fill) {
-                return;
+        public set fill(value: IBrush2D) {
+            this._fill = value;
+        }
+
+        protected getUsedShaderCategories(dataPart: InstanceDataBase): string[] {
+            var cat = super.getUsedShaderCategories(dataPart);
+
+            // Fill Part
+            if (dataPart.id === Shape2D.SHAPE2D_FILLPARTID) {
+                let fill = this.fill;
+                if (fill instanceof SolidColorBrush2D) {
+                    cat.push(Shape2D.SHAPE2D_CATEGORY_FILLSOLID);
+                }
             }
 
-            this._fill = value;
+            // Fill Part
+            if (dataPart.id === Shape2D.SHAPE2D_BORDERPARTID) {
+                let border = this.border;
+                if (border instanceof SolidColorBrush2D) {
+                    cat.push(Shape2D.SHAPE2D_CATEGORY_BORDERSOLID);
+                }
+            }
+
+            return cat;
+        }
+
+        protected refreshInstanceDataParts(): boolean {
+            if (!super.refreshInstanceDataParts()) {
+                return false;
+            }
+
+            let d = <Shape2DInstanceData>this._instanceDataParts[0];
+
+            if (this.border) {
+                let border = this.border;
+                if (border instanceof SolidColorBrush2D) {
+                    d.borderSolidColor = border.color;
+                }
+            }
+
+            if (this.fill) {
+                let fill = this.fill;
+                if (fill instanceof SolidColorBrush2D) {
+                    d.fillSolidColor = fill.color;
+                }
+            }
+
+            return true;
+        }
+
+        private _border: IBrush2D;
+        private _fill: IBrush2D;
+    }
+
+    export class Shape2DInstanceData extends InstanceDataBase {
+        @instanceData(Shape2D.SHAPE2D_CATEGORY_BORDERSOLID)
+        get borderSolidColor(): Color4 {
+            return null;
+        }
+
+        @instanceData(Shape2D.SHAPE2D_CATEGORY_FILLSOLID)
+        get fillSolidColor(): Color4 {
+            return null;
         }
 
-        private _border: IBorder2D;
-        private _fill: IFill2D;
     }
 
 

+ 3 - 3
src/Canvas2d/babylon.smartPropertyPrim.ts

@@ -175,11 +175,11 @@
             return (this._instanceDirtyFlags !== 0) || this._modelDirty;
         }
 
-        protected static GetOrAddModelCache<TInstData>(key: string, factory: (key: string) => ModelRenderCache<TInstData>): ModelRenderCache<TInstData> {
-            return <ModelRenderCache<TInstData>>SmartPropertyPrim.ModelCache.getOrAddWithFactory(key, factory);
+        protected static GetOrAddModelCache<TInstData>(key: string, factory: (key: string) => ModelRenderCache): ModelRenderCache {
+            return SmartPropertyPrim.ModelCache.getOrAddWithFactory(key, factory);
         }
 
-        protected static ModelCache: StringDictionary<ModelRenderCacheBase> = new StringDictionary<ModelRenderCacheBase>();
+        protected static ModelCache: StringDictionary<ModelRenderCache> = new StringDictionary<ModelRenderCache>();
 
         private get propDic(): StringDictionary<Prim2DPropInfo> {
             if (!this._propInfo) {

+ 27 - 19
src/Canvas2d/babylon.sprite2d.ts

@@ -1,10 +1,10 @@
 module BABYLON {
-    export class Sprite2DRenderCache extends ModelRenderCache<Sprite2DInstanceData> {
+    export class Sprite2DRenderCache extends ModelRenderCache {
         vb: WebGLBuffer;
         ib: WebGLBuffer;
         borderVB: WebGLBuffer;
         borderIB: WebGLBuffer;
-        instancingAttributes: InstancingAttributeInfo[];
+        instancingAttributes: Array<InstancingAttributeInfo[]>;
 
         texture: Texture;
         effect: Effect;
@@ -17,7 +17,7 @@
 
             // Compute the offset locations of the attributes in the vertexshader that will be mapped to the instance buffer data
             if (!this.instancingAttributes) {
-                this.instancingAttributes = instanceInfo._classTreeInfo.classContent.getInstancingAttributeInfos(this.effect);
+                this.instancingAttributes = this.loadInstancingAttributes(this.effect);
             }
             var engine = instanceInfo._owner.owner.engine;
 
@@ -25,13 +25,13 @@
             this.effect.setTexture("diffuseSampler", this.texture);
             engine.bindBuffers(this.vb, this.ib, [1], 4, this.effect);
 
-            engine.updateAndBindInstancesBuffer(instanceInfo._instancesBuffer, null, this.instancingAttributes);
+            engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[0], null, this.instancingAttributes[0]);
             var cur = engine.getAlphaMode();
             engine.setAlphaMode(Engine.ALPHA_COMBINE);
-            engine.draw(true, 0, 6, instanceInfo._instancesData.usedElementCount);
+            engine.draw(true, 0, 6, instanceInfo._instancesPartsData[0].usedElementCount);
             engine.setAlphaMode(cur);
 
-            engine.unBindInstancesBuffer(instanceInfo._instancesBuffer, this.instancingAttributes);
+            engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[0], this.instancingAttributes[0]);
 
             return true;
         }
@@ -65,7 +65,8 @@
     }
 
     @className("Sprite2D")
-    export class Sprite2D extends Shape2D<Sprite2DInstanceData> {
+    export class Sprite2D extends RenderablePrim2D {
+        static SPRITE2D_MAINPARTID = 1;
 
         public static textureProperty: Prim2DPropInfo;
         public static spriteSizeProperty: Prim2DPropInfo;
@@ -73,7 +74,7 @@
         public static spriteFrameProperty: Prim2DPropInfo;
         public static invertYProperty: Prim2DPropInfo;
 
-        @modelLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 1, pi => Sprite2D.textureProperty = pi)
+        @modelLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 1, pi => Sprite2D.textureProperty = pi)
         public get texture(): Texture {
             return this._texture;
         }
@@ -82,7 +83,7 @@
             this._texture = value;
         }
 
-        @instanceLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 2, pi => Sprite2D.spriteSizeProperty = pi, false, true)
+        @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 2, pi => Sprite2D.spriteSizeProperty = pi, false, true)
         public get spriteSize(): Size {
             return this._size;
         }
@@ -91,7 +92,7 @@
             this._size = value;
         }
 
-        @instanceLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 3, pi => Sprite2D.spriteLocationProperty = pi)
+        @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 3, pi => Sprite2D.spriteLocationProperty = pi)
         public get spriteLocation(): Vector2 {
             return this._location;
         }
@@ -100,7 +101,7 @@
             this._location = value;
         }
 
-        @instanceLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 4, pi => Sprite2D.spriteFrameProperty = pi)
+        @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 4, pi => Sprite2D.spriteFrameProperty = pi)
         public get spriteFrame(): number {
             return this._spriteFrame;
         }
@@ -109,7 +110,7 @@
             this._spriteFrame = value;
         }
 
-        @instanceLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 5, pi => Sprite2D.invertYProperty = pi)
+        @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 5, pi => Sprite2D.invertYProperty = pi)
         public get invertY(): boolean {
             return this._invertY;
         }
@@ -141,8 +142,13 @@
             return sprite;
         }
 
-        protected createModelRenderCache(): ModelRenderCache<Sprite2DInstanceData> {
+        protected createModelRenderCache(): ModelRenderCache {
             let renderCache = new Sprite2DRenderCache();
+            return renderCache;
+        }
+
+        protected setupModelRenderCache(modelRenderCache: ModelRenderCache) {
+            let renderCache = <Sprite2DRenderCache>modelRenderCache;
             let engine = this.owner.engine;
 
             let vb = new Float32Array(4);
@@ -162,21 +168,23 @@
             renderCache.ib = engine.createIndexBuffer(ib);
 
             renderCache.texture = this.texture;
-            renderCache.effect = engine.createEffect({ vertex: "sprite2d", fragment: "sprite2d" }, ["index", "zBias", "transformX", "transformY", "topLeftUV", "sizeUV", "origin", "textureSize", "frame", "invertY"], [], ["diffuseSampler"], "");
+
+            var ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_FILLPARTID, ["index"]);
+            renderCache.effect = engine.createEffect({ vertex: "sprite2d", fragment: "sprite2d" }, ei.attributes, [], ["diffuseSampler"], ei.defines);
 
             return renderCache;
         }
 
-        protected createInstanceData(): Sprite2DInstanceData {
-            return new Sprite2DInstanceData();
+        protected createInstanceDataParts(): InstanceDataBase[] {
+            return [new Sprite2DInstanceData(Sprite2D.SPRITE2D_MAINPARTID)];
         }
 
-        protected refreshInstanceData(): boolean {
-            if (!super.refreshInstanceData()) {
+        protected refreshInstanceDataParts(): boolean {
+            if (!super.refreshInstanceDataParts()) {
                 return false;
             }
 
-            let d = this._instanceData;
+            let d = <Sprite2DInstanceData>this._instanceDataParts[0];
             let ts = this.texture.getSize();
             let sl = this.spriteLocation;
             let ss = this.spriteSize;

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

@@ -1,5 +1,5 @@
 varying vec4 vColor;
 
 void main(void) {
-	gl_FragColor = vec4(1,0,0,1);
+	gl_FragColor = vColor;
 }

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

@@ -5,6 +5,8 @@ attribute vec4 transformX;
 attribute vec4 transformY;
 attribute vec2 origin;
 
+attribute vec4 fillSolidColor;
+
 attribute vec3 properties;
 
 // First index is the center, then there's four sections of 16 subdivisions
@@ -50,6 +52,8 @@ void main(void) {
 
 	}
 
+	vColor = fillSolidColor;
+
 	vec4 pos;
 	pos.xy = pos2.xy - (origin * properties.xy);
 	pos.z = 1.0;

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

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