Bläddra i källkod

- Rectange2D now supporting Border and Gradient Brush and nonRounded mode: https://trello.com/c/6t4baIDP
- Added attributeName to InstancingAttributeInfo class (in babylon.engine.ts)

nockawa 9 år sedan
förälder
incheckning
a75b5807b3

+ 16 - 8
src/Canvas2d/babylon.modelRenderCache.ts

@@ -10,10 +10,12 @@
             this._instancesPartsData = new Array<DynamicFloatArray>();
             this._instancesPartsBuffer = new Array<WebGLBuffer>();
             this._instancesPartsBufferSize = new Array<number>();
+            this._partIndexFromId = new StringDictionary<number>();
         }
 
         _owner: Group2D;
         _modelCache: ModelRenderCache;
+        _partIndexFromId: StringDictionary<number>;
         _instancesPartsData: DynamicFloatArray[];
         _dirtyInstancesData: boolean;
         _instancesPartsBuffer: WebGLBuffer[];
@@ -52,21 +54,27 @@
             this._instancesData.remove(key);
         }
 
-        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));
+        protected loadInstancingAttributes(partId: number, effect: Effect): InstancingAttributeInfo[] {
+            for (var i = 0; i < this._partIdList.length; i++) {
+                if (this._partIdList[i] === partId) {
+                    break;
+                }
+            }
+            if (i === this._partIdList.length) {
+                return null;
             }
 
-            return iai;
+            var ci = this._partsClassInfo[i];
+            var categories = this._partsUsedCategories[i];
+            let res = ci.classContent.getInstancingAttributeInfos(effect, categories);
+
+            return res;
         }
 
         _instancesData: StringDictionary<InstanceDataBase[]>;
 
         private _nextKey: number;
+        _partIdList: number[];
         _partsDataStride: number[];
         _partsUsedCategories: Array<string[]>;
         _partsClassInfo: ClassTreeInfo<InstanceClassInfo, InstancePropInfo>[];

+ 91 - 23
src/Canvas2d/babylon.rectangle2d.ts

@@ -2,33 +2,59 @@
     export class Rectangle2DRenderCache extends ModelRenderCache {
         fillVB: WebGLBuffer;
         fillIB: WebGLBuffer;
+        fillIndicesCount: number;
+        instancingFillAttributes: InstancingAttributeInfo[];
+        effectFill: Effect;
+
         borderVB: WebGLBuffer;
         borderIB: WebGLBuffer;
-        instancingAttributes: Array<InstancingAttributeInfo[]>;
+        borderIndicesCount: number;
+        instancingBorderAttributes: InstancingAttributeInfo[];
+        effectBorder: Effect;
 
-        effect: Effect;
 
         render(instanceInfo: GroupInstanceInfo, context: Render2DContext): boolean {
             // Do nothing if the shader is still loading/preparing
-            if (!this.effect.isReady()) {
+            if ((this.effectFill && !this.effectFill.isReady()) || (this.effectBorder && !this.effectBorder.isReady())) {
                 return false;
             }
 
-            // Compute the offset locations of the attributes in the vertexshader that will be mapped to the instance buffer data
-            if (!this.instancingAttributes) {
-                this.instancingAttributes = this.loadInstancingAttributes(this.effect);
-            }
             var engine = instanceInfo._owner.owner.engine;
 
-            engine.enableEffect(this.effect);
-            engine.bindBuffers(this.fillVB, this.fillIB, [1], 4, this.effect);
+            if (this.effectFill) {
+                let partIndex = instanceInfo._partIndexFromId.get(Shape2D.SHAPE2D_FILLPARTID.toString());
+
+                // Compute the offset locations of the attributes in the vertexshader that will be mapped to the instance buffer data
+                if (!this.instancingFillAttributes) {
+                    this.instancingFillAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_FILLPARTID, this.effectFill);
+                }
+
+                engine.enableEffect(this.effectFill);
+                engine.bindBuffers(this.fillVB, this.fillIB, [1], 4, this.effectFill);
 
-            engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[0], null, this.instancingAttributes[0]);
+                engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], null, this.instancingFillAttributes);
 
-            engine.draw(true, 0, Rectangle2D.roundSubdivisions * 4 * 3, instanceInfo._instancesPartsData[0].usedElementCount);
+                engine.draw(true, 0, this.fillIndicesCount, instanceInfo._instancesPartsData[partIndex].usedElementCount);
 
-            engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[0], this.instancingAttributes[0]);
+                engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], this.instancingFillAttributes);
+            }
+
+            if (this.effectBorder) {
+                let partIndex = instanceInfo._partIndexFromId.get(Shape2D.SHAPE2D_BORDERPARTID.toString());
+
+                // Compute the offset locations of the attributes in the vertexshader that will be mapped to the instance buffer data
+                if (!this.instancingBorderAttributes) {
+                    this.instancingBorderAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_BORDERPARTID, this.effectBorder);
+                }
+                engine.enableEffect(this.effectBorder);
+                engine.bindBuffers(this.borderVB, this.borderIB, [1], 4, this.effectBorder);
 
+                engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], null, this.instancingBorderAttributes);
+
+                engine.draw(true, 0, this.borderIndicesCount, instanceInfo._instancesPartsData[partIndex].usedElementCount);
+
+                engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], this.instancingBorderAttributes);
+            }
             return true;
         }
     }
@@ -84,8 +110,8 @@
             this._levelBoundingInfo.extent = this.size.clone();
         }
 
-        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);
+        protected setupRectangle2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, size: Size, roundRadius = 0, fill?: IBrush2D, border?: IBrush2D, borderThickness: number = 1) {
+            this.setupShape2D(owner, parent, id, position, true, fill, border, borderThickness);
             this.size = size;
             this.notRounded = !roundRadius;
             this.roundRadius = roundRadius;
@@ -96,7 +122,7 @@
 
             let rect = new Rectangle2D();
             rect.setupRectangle2D(parent.owner, parent, id, new Vector2(x, y), new Size(width, height), null);
-            rect.fill = fill || Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
+            rect.fill = fill;
             rect.border = border;
             return rect;
         }
@@ -122,9 +148,9 @@
             let renderCache = <Rectangle2DRenderCache>modelRenderCache;
             let engine = this.owner.engine;
 
-            // Need to create vb/ib for the fill part?
+            // Need to create webgl resources for fill part?
             if (this.fill) {
-                var vbSize = ((this.notRounded ? 1 : Rectangle2D.roundSubdivisions) * 4) + 1;
+                let vbSize = ((this.notRounded ? 1 : Rectangle2D.roundSubdivisions) * 4) + 1;
                 let vb = new Float32Array(vbSize);
                 for (let i = 0; i < vbSize; i++) {
                     vb[i] = i;
@@ -141,10 +167,42 @@
                 ib[triCount * 3 - 1] = 1;
 
                 renderCache.fillIB = engine.createIndexBuffer(ib);
+                renderCache.fillIndicesCount = triCount * 3;
+
+                let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_FILLPARTID, ["index"]);
+                renderCache.effectFill = engine.createEffect({ vertex: "rect2d", fragment: "rect2d" }, ei.attributes, [], [], ei.defines);
+            }
+
+            // Need to create webgl resource for border part?
+            if (this.border) {
+                let vbSize = (this.notRounded ? 1 : Rectangle2D.roundSubdivisions) * 4 * 2;
+                let vb = new Float32Array(vbSize);
+                for (let i = 0; i < vbSize; i++) {
+                    vb[i] = i;
+                }
+                renderCache.borderVB = engine.createVertexBuffer(vb);
+
+                let triCount = vbSize;
+                let rs = triCount / 2;
+                let ib = new Float32Array(triCount * 3);
+                for (let i = 0; i < rs; i++) {
+                    let r0 = i;
+                    let r1 = (i + 1) % rs;
 
+                    ib[i * 6 + 0] = rs + r1;
+                    ib[i * 6 + 1] = rs + r0;
+                    ib[i * 6 + 2] = r0;
 
-                var ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_FILLPARTID, ["index"]);
-                renderCache.effect = engine.createEffect({ vertex: "rect2d", fragment: "rect2d" }, ei.attributes, [], [], ei.defines);
+                    ib[i * 6 + 3] = r1;
+                    ib[i * 6 + 4] = rs + r1;
+                    ib[i * 6 + 5] = r0;
+                }
+
+                renderCache.borderIB = engine.createIndexBuffer(ib);
+                renderCache.borderIndicesCount = triCount * 3;
+
+                let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_BORDERPARTID, ["index"]);
+                renderCache.effectBorder = engine.createEffect({ vertex: "rect2d", fragment: "rect2d" }, ei.attributes, [], [], ei.defines);
             }
 
             return renderCache;
@@ -152,14 +210,26 @@
 
 
         protected createInstanceDataParts(): InstanceDataBase[] {
-            return [new Rectangle2DInstanceData(Shape2D.SHAPE2D_FILLPARTID)];
+            var res = new Array<InstanceDataBase>();
+            if (this.border) {
+                res.push(new Rectangle2DInstanceData(Shape2D.SHAPE2D_BORDERPARTID));
+            }
+            if (this.fill) {
+                res.push(new Rectangle2DInstanceData(Shape2D.SHAPE2D_FILLPARTID));
+            }
+            return res;
         }
 
         protected refreshInstanceDataParts(part: InstanceDataBase): boolean {
             if (!super.refreshInstanceDataParts(part)) {
                 return false;
             }
-            if (part.id === Shape2D.SHAPE2D_FILLPARTID) {
+            if (part.id === Shape2D.SHAPE2D_BORDERPARTID) {
+                let d = <Rectangle2DInstanceData>part;
+                let size = this.size;
+                d.properties = new Vector3(size.width, size.height, this.roundRadius || 0);
+            }
+            else if (part.id === Shape2D.SHAPE2D_FILLPARTID) {
                 let d = <Rectangle2DInstanceData>part;
                 let size = this.size;
                 d.properties = new Vector3(size.width, size.height, this.roundRadius || 0);
@@ -171,6 +241,4 @@
         private _notRounded: boolean;
         private _roundRadius: number;
     }
-
-
 }

+ 9 - 4
src/Canvas2d/babylon.renderablePrim2d.ts

@@ -31,6 +31,7 @@
                         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.get(catInline) * 4; // attrib.instanceOffset is in float, iai.offset must be in bytes
+                        iai.attributeName = attrib.attributeName;
                         res.push(iai);
                     }
                 }
@@ -268,7 +269,7 @@
     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: IBrush2D, border: IBrush2D) {
+        setupRenderablePrim2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, isVisible: boolean) {
             this.setupPrim2DBase(owner, parent, id, position);
             this._isTransparent = false;
         }
@@ -302,6 +303,7 @@
                     let ctiArray = new Array<ClassTreeInfo<InstanceClassInfo, InstancePropInfo>>();
                     var dataStrides = new Array<number>();
                     var usedCatList = new Array<string[]>();
+                    var partIdList = new Array<number>();
 
                     for (var dataPart of parts) {
                         let cat = this.getUsedShaderCategories(dataPart);
@@ -328,18 +330,21 @@
                         dataStrides.push(size);
                         usedCatList.push(cat);
                         ctiArray.push(cti);
+                        partIdList.push(dataPart.id);
                     }
                     this._modelRenderCache._partsDataStride = dataStrides;
                     this._modelRenderCache._partsUsedCategories = usedCatList;
                     this._modelRenderCache._partsClassInfo = ctiArray;
+                    this._modelRenderCache._partIdList = partIdList;
                 }
 
                 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
+                if (gii._instancesPartsData.length === 0) {
+                    for (let j = 0; j < this._modelRenderCache._partsDataStride.length; j++) {
+                        let stride = this._modelRenderCache._partsDataStride[j];
                         gii._instancesPartsData.push(new DynamicFloatArray(stride / 4, 50));
+                        gii._partIndexFromId.add(this._modelRenderCache._partIdList[j].toString(), j);
                     }
                 }
 

+ 72 - 16
src/Canvas2d/babylon.shape2d.ts

@@ -4,6 +4,7 @@
     export class Shape2D extends RenderablePrim2D {
         static SHAPE2D_BORDERPARTID            = 1;
         static SHAPE2D_FILLPARTID              = 2;
+        static SHAPE2D_CATEGORY_BORDER         = "Border";
         static SHAPE2D_CATEGORY_BORDERSOLID    = "BorderSolid";
         static SHAPE2D_CATEGORY_BORDERGRADIENT = "BorderGradient";
         static SHAPE2D_CATEGORY_FILLSOLID      = "FillSolid";
@@ -12,6 +13,7 @@
         static SHAPE2D_PROPCOUNT: number = RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 5;
         public static borderProperty: Prim2DPropInfo;
         public static fillProperty: Prim2DPropInfo;
+        public static borderThicknessProperty: Prim2DPropInfo;
 
         @modelLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 1, pi => Shape2D.borderProperty = pi, true)
         public get border(): IBrush2D {
@@ -31,6 +33,22 @@
             this._fill = value;
         }
 
+        @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 3, pi => Shape2D.borderThicknessProperty = pi)
+        public get borderThickness(): number {
+            return this._borderThickness;
+        }
+
+        public set borderThickness(value: number) {
+            this._borderThickness = value;
+        }
+
+        setupShape2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, isVisible: boolean, fill: IBrush2D, border: IBrush2D, borderThickness: number = 1.0) {
+            this.setupRenderablePrim2D(owner, parent, id, position, isVisible);
+            this.border = border;
+            this.fill = fill;
+            this.borderThickness = borderThickness;
+        }
+
         protected getUsedShaderCategories(dataPart: InstanceDataBase): string[] {
             var cat = super.getUsedShaderCategories(dataPart);
 
@@ -45,8 +63,10 @@
                 }
             }
 
-            // Fill Part
+            // Border Part
             if (dataPart.id === Shape2D.SHAPE2D_BORDERPARTID) {
+                cat.push(Shape2D.SHAPE2D_CATEGORY_BORDER);
+
                 let border = this.border;
                 if (border instanceof SolidColorBrush2D) {
                     cat.push(Shape2D.SHAPE2D_CATEGORY_BORDERSOLID);
@@ -68,20 +88,11 @@
             if (part.id === Shape2D.SHAPE2D_FILLPARTID) {
                 let d = <Shape2DInstanceData>part;
 
-                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;
-                    }
-
-                    else if (fill instanceof GradientColorBrush2D) {
+                    } else if (fill instanceof GradientColorBrush2D) {
                         d.fillGradientColor1 = fill.color1;
                         d.fillGradientColor2 = fill.color2;
                         var t = Matrix.Compose(new Vector3(fill.scale, fill.scale, fill.scale), Quaternion.RotationAxis(new Vector3(0, 0, 1), fill.rotation), new Vector3(fill.translation.x, fill.translation.y, 0));
@@ -91,18 +102,39 @@
                     }
                 }
             }
+
+            else if (part.id === Shape2D.SHAPE2D_BORDERPARTID) {
+                let d = <Shape2DInstanceData>part;
+
+                if (this.border) {
+                    d.borderThickness = this.borderThickness;
+
+                    let border = this.border;
+                    if (border instanceof SolidColorBrush2D) {
+                        d.borderSolidColor = border.color;
+                    } else if (border instanceof GradientColorBrush2D) {
+                        d.borderGradientColor1 = border.color1;
+                        d.borderGradientColor2 = border.color2;
+                        var t = Matrix.Compose(new Vector3(border.scale, border.scale, border.scale), Quaternion.RotationAxis(new Vector3(0, 0, 1), border.rotation), new Vector3(border.translation.x, border.translation.y, 0));
+
+                        let ty = new Vector4(t.m[1], t.m[5], t.m[9], t.m[13]);
+                        d.borderGradientTY = ty;
+                    }
+                }
+
+
+            }
+
             return true;
         }
 
         private _border: IBrush2D;
+        private _borderThickness: number;
         private _fill: IBrush2D;
     }
 
     export class Shape2DInstanceData extends InstanceDataBase {
-        @instanceData(Shape2D.SHAPE2D_CATEGORY_BORDERSOLID)
-        get borderSolidColor(): Color4 {
-            return null;
-        }
+        // FILL ATTRIBUTES
 
         @instanceData(Shape2D.SHAPE2D_CATEGORY_FILLSOLID)
         get fillSolidColor(): Color4 {
@@ -124,7 +156,31 @@
             return null;
         }
 
-    }
+        // BORDER ATTRIBUTES
 
+        @instanceData(Shape2D.SHAPE2D_CATEGORY_BORDER)
+        get borderThickness(): number {
+            return null;
+        }
 
+        @instanceData(Shape2D.SHAPE2D_CATEGORY_BORDERSOLID)
+        get borderSolidColor(): Color4 {
+            return null;
+        }
+
+        @instanceData(Shape2D.SHAPE2D_CATEGORY_BORDERGRADIENT)
+        get borderGradientColor1(): Color4 {
+            return null;
+        }
+
+        @instanceData(Shape2D.SHAPE2D_CATEGORY_BORDERGRADIENT)
+        get borderGradientColor2(): Color4 {
+            return null;
+        }
+
+        @instanceData(Shape2D.SHAPE2D_CATEGORY_BORDERGRADIENT)
+        get borderGradientTY(): Vector4 {
+            return null;
+        }
+    }
 }

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

@@ -4,7 +4,7 @@
         ib: WebGLBuffer;
         borderVB: WebGLBuffer;
         borderIB: WebGLBuffer;
-        instancingAttributes: Array<InstancingAttributeInfo[]>;
+        instancingAttributes: 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 = this.loadInstancingAttributes(this.effect);
+                this.instancingAttributes = this.loadInstancingAttributes(Sprite2D.SPRITE2D_MAINPARTID, 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._instancesPartsBuffer[0], null, this.instancingAttributes[0]);
+            engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[0], null, this.instancingAttributes);
             var cur = engine.getAlphaMode();
             engine.setAlphaMode(Engine.ALPHA_COMBINE);
             engine.draw(true, 0, 6, instanceInfo._instancesPartsData[0].usedElementCount);
             engine.setAlphaMode(cur);
 
-            engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[0], this.instancingAttributes[0]);
+            engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[0], this.instancingAttributes);
 
             return true;
         }
@@ -125,7 +125,7 @@
         }
 
         protected setupSprite2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, texture: Texture, spriteSize: Size, spriteLocation: Vector2, invertY: boolean) {
-            this.setupRenderablePrim2D(owner, parent, id, position, true, null, null);
+            this.setupRenderablePrim2D(owner, parent, id, position, true);
             this.texture = texture;
             this.spriteSize = spriteSize;
             this.spriteLocation = spriteLocation;

+ 96 - 8
src/Shaders/rect2d.vertex.fx

@@ -5,16 +5,31 @@ attribute vec4 transformX;
 attribute vec4 transformY;
 attribute vec2 origin;
 
+#ifdef Border
+attribute float borderThickness;
+#endif
+
 #ifdef FillSolid
 attribute vec4 fillSolidColor;
 #endif
 
+#ifdef BorderSolid
+attribute vec4 borderSolidColor;
+#endif
+
 #ifdef FillGradient
 attribute vec4 fillGradientColor1;
 attribute vec4 fillGradientColor2;
 attribute vec4 fillGradientTY;
 #endif
 
+#ifdef BorderGradient
+attribute vec4 borderGradientColor1;
+attribute vec4 borderGradientColor2;
+attribute vec4 borderGradientTY;
+#endif
+
+// xyzw are: width, height, roundRadius (0.0 for simple rectangle with four vertices)
 attribute vec3 properties;
 
 // First index is the center, then there's four sections of 16 subdivisions
@@ -33,45 +48,118 @@ varying vec4 vColor;
 void main(void) {
 
 	vec2 pos2;
-	if (index == 0.0) {
-		pos2 = vec2(0.5, 0.5);
+
+	// notRound case, only four vertices
+	if (properties.z == 0.0) {
+		if (index == 1.0) {
+			pos2 = vec2(1.0, 1.0);
+		} else if (index == 2.0) {
+			pos2 = vec2(0.0, 1.0);
+		} else if (index == 3.0) {
+			pos2 = vec2(0.0, 0.0);
+		} else {
+			pos2 = vec2(1.0, 0.0);
+		}
 	}
-	else {
+	else 
+	{
+#ifdef Border
 		float w = properties.x;
 		float h = properties.y;
 		float r = properties.z;
 		float nru = r / w;
 		float nrv = r / h;
+		vec2 borderOffset = vec2(1.0, 1.0);
+
+		float segi = index;
+		if (index < rsub) {
+			borderOffset = vec2(1.0-(borderThickness*2.0 / w), 1.0-(borderThickness*2.0 / h));
+		}
+		else {
+			segi -= rsub;
+		}
 
-		if (index < rsub0) {
-			pos2 = vec2(1.0-nru, nrv);
+		// right/bottom
+		if (segi < rsub0) {
+			pos2 = vec2(1.0 - nru, nrv);
 		}
-		else if (index < rsub1) {
+		// left/bottom
+		else if (segi < rsub1) {
 			pos2 = vec2(nru, nrv);
 		}
-		else if (index < rsub2) {
+		// left/top
+		else if (segi < rsub2) {
 			pos2 = vec2(nru, 1.0 - nrv);
 		}
+		// right/top
 		else {
 			pos2 = vec2(1.0 - nru, 1.0 - nrv);
 		}
 
-		float angle = TWOPI - ((index - 1.0) * TWOPI / (rsub-1.0));
+		float angle = TWOPI - ((index - 1.0) * TWOPI / (rsub - 0.5));
 		pos2.x += cos(angle) * nru;
 		pos2.y += sin(angle) * nrv;
+
+		pos2.x = ((pos2.x - 0.5) * borderOffset.x) + 0.5;
+		pos2.y = ((pos2.y - 0.5) * borderOffset.y) + 0.5;
+
+#else
+		if (index == 0.0) {
+			pos2 = vec2(0.5, 0.5);
+		}
+		else {
+			float w = properties.x;
+			float h = properties.y;
+			float r = properties.z;
+			float nru = r / w;
+			float nrv = r / h;
+
+			// right/bottom
+			if (index < rsub0) {
+				pos2 = vec2(1.0 - nru, nrv);
+			}
+			// left/bottom
+			else if (index < rsub1) {
+				pos2 = vec2(nru, nrv);
+			}
+			// left/top
+			else if (index < rsub2) {
+				pos2 = vec2(nru, 1.0 - nrv);
+			}
+			// right/top
+			else {
+				pos2 = vec2(1.0 - nru, 1.0 - nrv);
+			}
+
+			float angle = TWOPI - ((index - 1.0) * TWOPI / (rsub - 0.5));
+			pos2.x += cos(angle) * nru;
+			pos2.y += sin(angle) * nrv;
+		}
+#endif
 	}
 
 #ifdef FillSolid
 	vColor = fillSolidColor;
 #endif
+
+#ifdef BorderSolid
+	vColor = borderSolidColor;
+#endif
+
 #ifdef FillGradient
 	float v = dot(vec4(pos2.xy, 1, 1), fillGradientTY);
 	vColor = mix(fillGradientColor2, fillGradientColor1, v);	// As Y is inverted, Color2 first, then Color1
 #endif
 
+#ifdef BorderGradient
+	float v = dot(vec4(pos2.xy, 1, 1), borderGradientTY);
+	vColor = mix(borderGradientColor2, borderGradientColor1, v);	// As Y is inverted, Color2 first, then Color1
+#endif
+
 	vec4 pos;
 	pos.xy = (pos2.xy - origin) * properties.xy;
 	pos.z = 1.0;
 	pos.w = 1.0;
 	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, zBias.y);
+
 }

+ 5 - 0
src/babylon.engine.ts

@@ -149,6 +149,11 @@
          * Offset of the data in the Vertex Buffer acting as the instancing buffer
          */
         offset: number;
+
+        /**
+         * Name of the GLSL attribute, for debugging purpose only
+         */
+        attributeName: string;
     }
 
     export class EngineCapabilities {