浏览代码

Canvas2D: Positioning Engine, ZE Final Fight! + bonus devs

Big boring but necessary work was done to make the positioning engine supporting scale/rotation of primitives... with a little side effect.

The side effect: before the positioning engine would round the aligned position to ensure the rendered primitive to be aligned on the rendering target's pixel. This can't be done anymore due to scaling (an aligned position of 10.5 with a scale of 2 would be aligned correctly). That's why primitive alignment was introduced and is forced for Text2D.

Bonus Developments:

 - Introduced an AOP style of logging system using TypeScript decorators to log the execution of methods and properties getter/setter. Main methods/properties of the Canvas2D rendering pipeline are using it now.
 - Prim2DBase has now an alignToPixel property to align the primitive render to the render target's pixel.
 - Text2D rendering was not using premultiplied alpha as it should be.
 - BaseFontTexture has now a isPremultipliedAlpha property and Text2D is using accordingly.
 - FontTexture.GetCachedFontTexture is now generating a texture using NEAREST_SAMPLINGMODE when SDF is not used.
 - A **Breaking change** is introduced in the constructor of BitmapFontTexture because the premultipliedAlpha argument was inserted before the onLoad one.
nockawa 8 年之前
父节点
当前提交
19ed936670

+ 2 - 1
Tools/Gulp/config.json

@@ -467,7 +467,8 @@
     {
       "files": [
         "../../canvas2D/src/Tools/babylon.math2D.ts",
-        "../../canvas2D/src/Tools/babylon.IPropertyChanged.ts",
+        "../../canvas2D/src/Tools/babylon.iPropertyChanged.ts",
+        "../../canvas2D/src/Tools/babylon.c2dlogging.ts",
         "../../canvas2D/src/Tools/babylon.observableArray.ts",
         "../../canvas2D/src/Tools/babylon.observableStringDictionary.ts",
         "../../canvas2D/src/Engine/babylon.fontTexture.ts",

+ 4 - 0
canvas2D/src/Engine/babylon.bounding2d.ts

@@ -127,6 +127,10 @@
             b._worldAABBDirty = true;
         }
 
+        public toString(): string {
+            return `Center: ${this.center}, Extent: ${this.extent}, Radius: ${this.radius}`;
+        }
+
         /**
          * Duplicate this instance and return a new one
          * @return the duplicated instance

+ 95 - 60
canvas2D/src/Engine/babylon.canvas2d.ts

@@ -87,20 +87,6 @@
         }) {
             super(settings);
 
-            this._drawCallsOpaqueCounter          = new PerfCounter();
-            this._drawCallsAlphaTestCounter       = new PerfCounter();
-            this._drawCallsTransparentCounter     = new PerfCounter();
-            this._groupRenderCounter              = new PerfCounter();
-            this._updateTransparentDataCounter    = new PerfCounter();
-            this._cachedGroupRenderCounter        = new PerfCounter();
-            this._updateCachedStateCounter        = new PerfCounter();
-            this._updateLayoutCounter             = new PerfCounter();
-            this._updatePositioningCounter        = new PerfCounter();
-            this._updateLocalTransformCounter     = new PerfCounter();
-            this._updateGlobalTransformCounter    = new PerfCounter();
-            this._boundingInfoRecomputeCounter    = new PerfCounter();
-            this._layoutBoundingInfoUpdateCounter = new PerfCounter();
-
             this._cachedCanvasGroup = null;
 
             this._renderingGroupObserver = null;
@@ -200,18 +186,24 @@
                     this._renderingGroupObserver = this._scene.onRenderingGroupObservable.add((e, s) => {
                         if ((this._scene.activeCamera === settings.renderingPhase.camera) && (e.renderStage===RenderingGroupInfo.STAGE_POSTTRANSPARENT)) {
                             this._engine.clear(null, false, true, true);
+                            C2DLogging._startFrameRender();
                             this._render();
+                            C2DLogging._endFrameRender();
                         }
                     }, Math.pow(2, settings.renderingPhase.renderingGroupID));
                 } else {
                     this._afterRenderObserver = this._scene.onAfterRenderObservable.add((d, s) => {
                         this._engine.clear(null, false, true, true);
+                        C2DLogging._startFrameRender();
                         this._render();
+                        C2DLogging._endFrameRender();
                     });
                 }
             } else {
                 this._beforeRenderObserver = this._scene.onBeforeRenderObservable.add((d, s) => {
+                    C2DLogging._startFrameRender();
                     this._render();
+                    C2DLogging._endFrameRender();
                 });
             }
 
@@ -232,54 +224,93 @@
         }
 
         public get drawCallsOpaqueCounter(): PerfCounter {
+            if (!this._drawCallsOpaqueCounter) {
+                this._drawCallsOpaqueCounter = new PerfCounter();
+            }
             return this._drawCallsOpaqueCounter;
         }
 
         public get drawCallsAlphaTestCounter(): PerfCounter {
+            if (!this._drawCallsAlphaTestCounter) {
+                this._drawCallsAlphaTestCounter = new PerfCounter();
+            }
             return this._drawCallsAlphaTestCounter;
         }
 
         public get drawCallsTransparentCounter(): PerfCounter {
+            if (!this._drawCallsTransparentCounter) {
+                this._drawCallsTransparentCounter = new PerfCounter();
+            }
             return this._drawCallsTransparentCounter;
         }
 
         public get groupRenderCounter(): PerfCounter {
+            if (!this._groupRenderCounter) {
+                this._groupRenderCounter = new PerfCounter();
+            }
             return this._groupRenderCounter;
         }
 
         public get updateTransparentDataCounter(): PerfCounter {
+            if (!this._updateTransparentDataCounter) {
+                this._updateTransparentDataCounter = new PerfCounter();
+            }
             return this._updateTransparentDataCounter;
         }
 
         public get cachedGroupRenderCounter(): PerfCounter {
+            if (!this._cachedGroupRenderCounter) {
+                this._cachedGroupRenderCounter = new PerfCounter();
+            }
             return this._cachedGroupRenderCounter;
         }
 
         public get updateCachedStateCounter(): PerfCounter {
+            if (!this._updateCachedStateCounter) {
+                this._updateCachedStateCounter = new PerfCounter();
+            }
             return this._updateCachedStateCounter;
         }
 
         public get updateLayoutCounter(): PerfCounter {
+            if (!this._updateLayoutCounter) {
+                this._updateLayoutCounter = new PerfCounter();
+            }
             return this._updateLayoutCounter;
         }
 
         public get updatePositioningCounter(): PerfCounter {
+            if (!this._updatePositioningCounter) {
+                this._updatePositioningCounter = new PerfCounter();
+            }
             return this._updatePositioningCounter;
         }
 
         public get updateLocalTransformCounter(): PerfCounter {
+            if (!this._updateLocalTransformCounter) {
+                this._updateLocalTransformCounter = new PerfCounter();
+            }
             return this._updateLocalTransformCounter;
         }
 
         public get updateGlobalTransformCounter(): PerfCounter {
+            if (!this._updateGlobalTransformCounter) {
+                this._updateGlobalTransformCounter = new PerfCounter();
+            }
             return this._updateGlobalTransformCounter;
         }
 
         public get boundingInfoRecomputeCounter(): PerfCounter {
+            if (!this._boundingInfoRecomputeCounter) {
+                this._boundingInfoRecomputeCounter = new PerfCounter();
+            }
             return this._boundingInfoRecomputeCounter;
         }
 
         public get layoutBoundingInfoUpdateCounter(): PerfCounter {
+            if (!this._layoutBoundingInfoUpdateCounter) {
+                this._layoutBoundingInfoUpdateCounter = new PerfCounter();
+            }
             return this._layoutBoundingInfoUpdateCounter;
         }
 
@@ -1129,35 +1160,35 @@
         }
 
         private _initPerfMetrics() {
-            this._drawCallsOpaqueCounter.fetchNewFrame();
-            this._drawCallsAlphaTestCounter.fetchNewFrame();
-            this._drawCallsTransparentCounter.fetchNewFrame();
-            this._groupRenderCounter.fetchNewFrame();
-            this._updateTransparentDataCounter.fetchNewFrame();
-            this._cachedGroupRenderCounter.fetchNewFrame();
-            this._updateCachedStateCounter.fetchNewFrame();
-            this._updateLayoutCounter.fetchNewFrame();
-            this._updatePositioningCounter.fetchNewFrame();
-            this._updateLocalTransformCounter.fetchNewFrame();
-            this._updateGlobalTransformCounter.fetchNewFrame();
-            this._boundingInfoRecomputeCounter.fetchNewFrame();
-            this._layoutBoundingInfoUpdateCounter.fetchNewFrame();
+            this.drawCallsOpaqueCounter.fetchNewFrame();
+            this.drawCallsAlphaTestCounter.fetchNewFrame();
+            this.drawCallsTransparentCounter.fetchNewFrame();
+            this.groupRenderCounter.fetchNewFrame();
+            this.updateTransparentDataCounter.fetchNewFrame();
+            this.cachedGroupRenderCounter.fetchNewFrame();
+            this.updateCachedStateCounter.fetchNewFrame();
+            this.updateLayoutCounter.fetchNewFrame();
+            this.updatePositioningCounter.fetchNewFrame();
+            this.updateLocalTransformCounter.fetchNewFrame();
+            this.updateGlobalTransformCounter.fetchNewFrame();
+            this.boundingInfoRecomputeCounter.fetchNewFrame();
+            this.layoutBoundingInfoUpdateCounter.fetchNewFrame();
         }
 
         private _fetchPerfMetrics() {
-            this._drawCallsOpaqueCounter.addCount(0, true);
-            this._drawCallsAlphaTestCounter.addCount(0, true);
-            this._drawCallsTransparentCounter.addCount(0, true);
-            this._groupRenderCounter.addCount(0, true);
-            this._updateTransparentDataCounter.addCount(0, true);
-            this._cachedGroupRenderCounter.addCount(0, true);
-            this._updateCachedStateCounter.addCount(0, true);
-            this._updateLayoutCounter.addCount(0, true);
-            this._updatePositioningCounter.addCount(0, true);
-            this._updateLocalTransformCounter.addCount(0, true);
-            this._updateGlobalTransformCounter.addCount(0, true);
-            this._boundingInfoRecomputeCounter.addCount(0, true);
-            this._layoutBoundingInfoUpdateCounter.addCount(0, true);
+            this.drawCallsOpaqueCounter.addCount(0, true);
+            this.drawCallsAlphaTestCounter.addCount(0, true);
+            this.drawCallsTransparentCounter.addCount(0, true);
+            this.groupRenderCounter.addCount(0, true);
+            this.updateTransparentDataCounter.addCount(0, true);
+            this.cachedGroupRenderCounter.addCount(0, true);
+            this.updateCachedStateCounter.addCount(0, true);
+            this.updateLayoutCounter.addCount(0, true);
+            this.updatePositioningCounter.addCount(0, true);
+            this.updateLocalTransformCounter.addCount(0, true);
+            this.updateGlobalTransformCounter.addCount(0, true);
+            this.boundingInfoRecomputeCounter.addCount(0, true);
+            this.layoutBoundingInfoUpdateCounter.addCount(0, true);
         }
 
         private _updateProfileCanvas() {
@@ -1455,6 +1486,7 @@
                 this._setRenderingScale(scale);
             }
         }
+        private static _pCLS = Vector3.Zero();
 
         private _updateCanvasState(forceRecompute: boolean) {
             // Check if the update has already been made for this render Frame
@@ -1464,14 +1496,7 @@
 
             // Detect a change of HWRendering scale
             let hwsl = this.engine.getHardwareScalingLevel();
-            let hwslChanged = this._curHWScale !== hwsl;
-            if (hwslChanged) {
-                this._curHWScale = hwsl;
-                for (let child of this.children) {
-                    child._setFlags(SmartPropertyPrim.flagLocalTransformDirty|SmartPropertyPrim.flagGlobalTransformDirty);
-                }
-                this._setLayoutDirty();
-            }
+            this._curHWScale = hwsl;
 
             // Detect a change of rendering size
             let renderingSizeChanged = false;
@@ -1487,16 +1512,8 @@
             }
             this._renderingSize.height = newHeight;
 
-            // If the canvas fit the rendering size and it changed, update
-            if (renderingSizeChanged && this._fitRenderingDevice) {
-                this.size = this._renderingSize.clone();
-                if (this._background) {
-                    this._background.size = this.size;
-                }
-
-                // Dirty the Layout at the Canvas level to recompute as the size changed
-                this._setLayoutDirty();
-            }
+            let prevCLS = Canvas2D._pCLS;
+            prevCLS.copyFrom(this._canvasLevelScale);
 
             // If there's a design size, update the scale according to the renderingSize
             if (this._designSize) {
@@ -1508,11 +1525,29 @@
                 }
                 this.size = this._designSize.clone();
                 this._canvasLevelScale.copyFromFloats(scale, scale, 1);
-            } else if (this._curHWScale !== 1) {
+            } else {
                 let ratio = 1 / this._curHWScale;
                 this._canvasLevelScale.copyFromFloats(ratio, ratio, 1);
             }
 
+            if (!prevCLS.equals(this._canvasLevelScale)) {
+                for (let child of this.children) {
+                    child._setFlags(SmartPropertyPrim.flagLocalTransformDirty|SmartPropertyPrim.flagGlobalTransformDirty);
+                }
+                this._setLayoutDirty();
+            }
+
+            // If the canvas fit the rendering size and it changed, update
+            if (renderingSizeChanged && this._fitRenderingDevice) {
+                this.size = this._renderingSize.clone();
+                if (this._background) {
+                    this._background.size = this.size;
+                }
+
+                // Dirty the Layout at the Canvas level to recompute as the size changed
+                this._setLayoutDirty();
+            }
+
             var context = new PrepareRender2DContext();
 
             ++this._globalTransformProcessStep;
@@ -1527,8 +1562,8 @@
         /**
          * Method that renders the Canvas, you should not invoke
          */
+        @logMethod("==========CANVAS RENDER===============")
         private _render() {
-
             this._initPerfMetrics();
 
             if (this._renderObservable && this._renderObservable.hasObservers()) {

+ 2 - 0
canvas2D/src/Engine/babylon.canvas2dLayoutEngine.ts

@@ -61,6 +61,7 @@
         // A very simple (no) layout computing...
         // The Canvas and its direct children gets the Canvas' size as Layout Area
         // Indirect children have their Layout Area to the actualSize (margin area) of their parent
+        @logMethod()
         public updateLayout(prim: Prim2DBase) {
 
             // If this prim is layoutDiry we update  its layoutArea and also the one of its direct children
@@ -73,6 +74,7 @@
 
         }
 
+        @logMethod()
         private _doUpdate(prim: Prim2DBase) {
             // Canvas ?
             if (prim instanceof Canvas2D) {

+ 7 - 2
canvas2D/src/Engine/babylon.ellipse2d.ts

@@ -203,6 +203,7 @@
          * - rotation: the initial rotation (in radian) of the primitive. default is 0
          * - scale: the initial scale of the primitive. default is 1. You can alternatively use scaleX &| scaleY to apply non uniform scale
          * - dontInheritParentScale: if set the parent's scale won't be taken into consideration to compute the actualScale property
+         * - alignToPixel: if true the primitive will be aligned to the target rendering device's pixel
          * - opacity: set the overall opacity of the primitive, 1 to be opaque (default), less than 1 to be transparent.
          * - zOrder: override the zOrder with the specified value
          * - origin: define the normalized origin point location, default [0.5;0.5]
@@ -245,6 +246,7 @@
             scaleX                ?: number,
             scaleY                ?: number,
             dontInheritParentScale?: boolean,
+            alignToPixel          ?: boolean,
             opacity               ?: number,
             zOrder                ?: number, 
             origin                ?: Vector2,
@@ -419,20 +421,23 @@
             return res;
         }
 
+        private static _riv0 = new Vector2(0,0);
         protected refreshInstanceDataPart(part: InstanceDataBase): boolean {
             if (!super.refreshInstanceDataPart(part)) {
                 return false;
             }
+
+            let s = Ellipse2D._riv0;
+            this.getActualGlobalScaleToRef(s);
+
             if (part.id === Shape2D.SHAPE2D_BORDERPARTID) {
                 let d = <Ellipse2DInstanceData>part;
                 let size = this.actualSize;
-                let s = this.actualScale;
                 d.properties = new Vector3(size.width * s.x, size.height * s.y, this.subdivisions);
             }
             else if (part.id === Shape2D.SHAPE2D_FILLPARTID) {
                 let d = <Ellipse2DInstanceData>part;
                 let size = this.actualSize;
-                let s = this.actualScale;
                 d.properties = new Vector3(size.width * s.x, size.height * s.y, this.subdivisions);
             }
             return true;

+ 15 - 4
canvas2D/src/Engine/babylon.fontTexture.ts

@@ -26,12 +26,13 @@
      */
     export abstract class BaseFontTexture extends Texture {
 
-        constructor(url: string, scene: Scene, noMipmap: boolean = false, invertY: boolean = true, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE) {
+        constructor(url: string, scene: Scene, noMipmap: boolean = false, invertY: boolean = true, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE, premultipliedAlpha: boolean = false) {
 
             super(url, scene, noMipmap, invertY, samplingMode);
 
             this._cachedFontId = null;
             this._charInfos = new StringDictionary<CharInfo>();
+            this._isPremultipliedAlpha = premultipliedAlpha;
         }
 
         /**
@@ -50,6 +51,13 @@
         }
 
         /**
+         * True if the font was drawn using multiplied alpha
+         */
+        public get isPremultipliedAlpha(): boolean {
+            return this._isPremultipliedAlpha;
+        }
+
+        /**
          * Get the Width (in pixel) of the Space character
          */
         public get spaceWidth(): number {
@@ -148,6 +156,7 @@
         protected _spaceWidth;
         protected _superSample: boolean;
         protected _signedDistanceField: boolean;
+        protected _isPremultipliedAlpha: boolean;
         protected _cachedFontId: string;
     }
 
@@ -174,11 +183,12 @@
                             textureUrl: string = null,
                             noMipmap: boolean = false,
                             invertY: boolean = true,
-                            samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE, 
+                            samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE,
+                            premultipliedAlpha: boolean = false,
                             onLoad: () => void = null,
                             onError: (msg: string, code: number) => void = null)
         {
-            super(null, scene, noMipmap, invertY, samplingMode);
+            super(null, scene, noMipmap, invertY, samplingMode, premultipliedAlpha);
 
             var xhr = new XMLHttpRequest();
             xhr.onreadystatechange = () => {
@@ -361,7 +371,7 @@
                 return ft;
             }
 
-            ft = new FontTexture(null, fontName, scene, supersample ? 100 : 200, Texture.BILINEAR_SAMPLINGMODE, supersample, signedDistanceField);
+            ft = new FontTexture(null, fontName, scene, supersample ? 100 : 200, signedDistanceField ? Texture.BILINEAR_SAMPLINGMODE : Texture.NEAREST_SAMPLINGMODE, supersample, signedDistanceField);
             ft._cachedFontId = lfn;
             dic.add(lfn, ft);
 
@@ -404,6 +414,7 @@
             this._sdfScale = 8;
             this._signedDistanceField = signedDistanceField;
             this._superSample = false;
+            this._isPremultipliedAlpha = !signedDistanceField;
 
             // SDF will use super sample no matter what, the resolution is otherwise too poor to produce correct result
             if (superSample || signedDistanceField) {

+ 17 - 12
canvas2D/src/Engine/babylon.lines2d.ts

@@ -315,9 +315,6 @@
         }
 
         protected updateLevelBoundingInfo(): boolean {
-            if (!this._size) {
-                return false;
-            }
             if (!this._boundingMin) {
                 this._computeLines2D();
             }
@@ -336,6 +333,7 @@
          * - rotation: the initial rotation (in radian) of the primitive. default is 0
          * - scale: the initial scale of the primitive. default is 1. You can alternatively use scaleX &| scaleY to apply non uniform scale
          * - dontInheritParentScale: if set the parent's scale won't be taken into consideration to compute the actualScale property
+         * - alignToPixel: if true the primitive will be aligned to the target rendering device's pixel
          * - opacity: set the overall opacity of the primitive, 1 to be opaque (default), less than 1 to be transparent.
          * - zOrder: override the zOrder with the specified value
          * - origin: define the normalized origin point location, default [0.5;0.5]
@@ -380,6 +378,7 @@
             scaleX                ?: number,
             scaleY                ?: number,
             dontInheritParentScale?: boolean,
+            alignToPixel          ?: boolean,
             opacity               ?: number,
             zOrder                ?: number, 
             origin                ?: Vector2,
@@ -423,8 +422,6 @@
             this._borderVB = null;
             this._borderIB = null;
 
-            this._size = Size.Zero();
-
             this._boundingMin = null;
             this._boundingMax = null;
 
@@ -1152,12 +1149,12 @@
                 let pta = this._primTriArray;
                 let l = this.closed ? pl + 1 : pl;
 
-                this.transformPointWithOriginToRef(contour[0], null, Lines2D._prevA);
-                this.transformPointWithOriginToRef(contour[1], null, Lines2D._prevB);
+                Lines2D._prevA.copyFrom(contour[0]);
+                Lines2D._prevB.copyFrom(contour[1]);
                 let si = 0;
                 for (let i = 1; i < l; i++) {
-                    this.transformPointWithOriginToRef(contour[(i % pl) * 2 + 0], null, Lines2D._curA);
-                    this.transformPointWithOriginToRef(contour[(i % pl) * 2 + 1], null, Lines2D._curB);
+                    Lines2D._curA.copyFrom(contour[(i % pl) * 2 + 0]);
+                    Lines2D._curB.copyFrom(contour[(i % pl) * 2 + 1]);
 
                     pta.storeTriangle(si++, Lines2D._prevA, Lines2D._prevB, Lines2D._curA);
                     pta.storeTriangle(si++, Lines2D._curA,  Lines2D._prevB, Lines2D._curB);
@@ -1173,15 +1170,15 @@
                     for (let i = 0; i < l; i += 3) {
                         Lines2D._curA.x = points[tri[i + 0] * 2 + 0];
                         Lines2D._curA.y = points[tri[i + 0] * 2 + 1];
-                        this.transformPointWithOriginToRef(Lines2D._curA, null, Lines2D._curB);
+                        Lines2D._curB.copyFrom(Lines2D._curA);
 
                         Lines2D._curA.x = points[tri[i + 1] * 2 + 0];
                         Lines2D._curA.y = points[tri[i + 1] * 2 + 1];
-                        this.transformPointWithOriginToRef(Lines2D._curA, null, Lines2D._prevA);
+                        Lines2D._prevA.copyFrom(Lines2D._curA);
 
                         Lines2D._curA.x = points[tri[i + 2] * 2 + 0];
                         Lines2D._curA.y = points[tri[i + 2] * 2 + 1];
-                        this.transformPointWithOriginToRef(Lines2D._curA, null, Lines2D._prevB);
+                        Lines2D._prevB.copyFrom(Lines2D._curA);
 
                         pta.storeTriangle(si++, Lines2D._prevA, Lines2D._prevB, Lines2D._curB);
                     }
@@ -1200,8 +1197,16 @@
             }
 
             let bs = this._boundingMax.subtract(this._boundingMin);
+
+            // If the size is not size we were computing the first pass to determine the size took by the primitive
+            if (!this._size) {
+                // Set the size and compute a second time to consider the size while computing the points using the origin
+                this._size = new Size(bs.x, bs.y);
+                this._updatePositioningState();
+            }
             this._size.width = bs.x;
             this._size.height = bs.y;
+            this._actualSize = null;
         }
 
         public get size(): Size {

文件差异内容过多而无法显示
+ 320 - 258
canvas2D/src/Engine/babylon.prim2dBase.ts


+ 7 - 2
canvas2D/src/Engine/babylon.rectangle2d.ts

@@ -288,6 +288,7 @@
          * - rotation: the initial rotation (in radian) of the primitive. default is 0
          * - scale: the initial scale of the primitive. default is 1. You can alternatively use scaleX &| scaleY to apply non uniform scale
          * - dontInheritParentScale: if set the parent's scale won't be taken into consideration to compute the actualScale property
+         * - alignToPixel: if true the primitive will be aligned to the target rendering device's pixel
          * - opacity: set the overall opacity of the primitive, 1 to be opaque (default), less than 1 to be transparent.
          * - zOrder: override the zOrder with the specified value
          * - origin: define the normalized origin point location, default [0.5;0.5]
@@ -329,6 +330,7 @@
             scaleX                ?: number,
             scaleY                ?: number,
             dontInheritParentScale?: boolean,
+            alignToPixel          ?: boolean,
             opacity               ?: number,
             zOrder                ?: number, 
             origin                ?: Vector2,
@@ -585,20 +587,23 @@
             return res;
         }
 
+        private static _riv0 = new Vector2(0,0);
         protected refreshInstanceDataPart(part: InstanceDataBase): boolean {
             if (!super.refreshInstanceDataPart(part)) {
                 return false;
             }
+
+            let s = Rectangle2D._riv0;
+            this.getActualGlobalScaleToRef(s);
+
             if (part.id === Shape2D.SHAPE2D_BORDERPARTID) {
                 let d = <Rectangle2DInstanceData>part;
                 let size = this.actualSize;
-                let s = this.actualScale;
                 d.properties = new Vector3(size.width * s.x, size.height * s.y, this.roundRadius || 0);
             }
             else if (part.id === Shape2D.SHAPE2D_FILLPARTID) {
                 let d = <Rectangle2DInstanceData>part;
                 let size = this.actualSize;
-                let s = this.actualScale;
                 d.properties = new Vector3(size.width * s.x, size.height * s.y, this.roundRadius || 0);
             }
             return true;

+ 16 - 20
canvas2D/src/Engine/babylon.renderablePrim2d.ts

@@ -284,6 +284,15 @@
         set transformY(value: Vector4) {
         }
 
+        // The vector3 is: rendering width, height and 1 if the primitive must be aligned to pixel or 0 otherwise
+        @instanceData()
+        get renderingInfo(): Vector3 {
+            return null;
+        }
+
+        set renderingInfo(val: Vector3) {
+        }
+
         @instanceData()
         get opacity(): number {
             return null;
@@ -824,23 +833,7 @@
             return null;
         }
 
-        /**
-         * Transform a given point using the Primitive's origin setting.
-         * This method requires the Primitive's actualSize to be accurate
-         * @param p the point to transform
-         * @param originOffset an offset applied on the current origin before performing the transformation. Depending on which frame of reference your data is expressed you may have to apply a offset. (if you data is expressed from the bottom/left, no offset is required. If it's expressed from the center the a [-0.5;-0.5] offset has to be applied.
-         * @param res an allocated Vector2 that will receive the transformed content
-         */
-        protected transformPointWithOriginByRef(p: Vector2, originOffset:Vector2, res: Vector2) {
-            let actualSize = this.actualSize;
-            res.x = p.x - ((this.origin.x + (originOffset ? originOffset.x : 0)) * actualSize.width);
-            res.y = p.y - ((this.origin.y + (originOffset ? originOffset.y : 0)) * actualSize.height);
-        }
-
-        protected transformPointWithOriginToRef(p: Vector2, originOffset: Vector2, res: Vector2) {
-            this.transformPointWithOriginByRef(p, originOffset, res);
-            return res;
-        }
+        private static _toz = Size.Zero();
 
         /**
          * Get the info for a given effect based on the dataPart metadata
@@ -932,6 +925,7 @@
         private static _r = Quaternion.Identity();
         private static _t = Vector3.Zero();
         private static _iV3 = new Vector3(1, 1, 1); // Must stay identity vector3
+
         /**
          * Update the instanceDataBase level properties of a part
          * @param part the part to update
@@ -944,8 +938,9 @@
             let trn = RenderablePrim2D._t;
             t.decompose(scl, rot, trn);
             let pas = this.actualScale;
-            scl.x = pas.x;
-            scl.y = pas.y;
+            let canvasScale = this.owner._canvasLevelScale;
+            scl.x = pas.x * canvasScale.x * this._postScale.x;
+            scl.y = pas.y * canvasScale.y * this._postScale.y;
             scl.z = 1;
             t = Matrix.Compose(this.applyActualScaleOnTransform() ? scl : RenderablePrim2D._iV3, rot, trn);
 
@@ -970,10 +965,11 @@
             let w = size.width;
             let h = size.height;
             let invZBias = 1 / zBias;
+
             let tx = new Vector4(t.m[0] * 2 / w, t.m[4] * 2 / w, 0, ((t.m[12] + offX) * 2 / w) - 1);
             let ty = new Vector4(t.m[1] * 2 / h, t.m[5] * 2 / h, 0, ((t.m[13] + offY) * 2 / h) - 1);
 
-
+            part.renderingInfo = new Vector3(w, h, this.alignToPixel ? 1 : 0);
             part.transformX = tx;
             part.transformY = ty;
             part.opacity = this.actualOpacity;

+ 38 - 2
canvas2D/src/Engine/babylon.smartPropertyPrim.ts

@@ -675,6 +675,7 @@
             } 
         }
 
+        @logProp()
         protected _triggerPropertyChanged(propInfo: Prim2DPropInfo, newValue: any) {
             if (this.isDisposed) {
                 return;
@@ -1103,13 +1104,13 @@
         }
 
         protected _boundingBoxDirty() {
-            this._setFlags(SmartPropertyPrim.flagLevelBoundingInfoDirty);
+            this._setFlags(SmartPropertyPrim.flagLevelBoundingInfoDirty|SmartPropertyPrim.flagLayoutBoundingInfoDirty);
 
             // Escalate the dirty flag in the instance hierarchy, stop when a renderable group is found or at the end
             if (this instanceof Prim2DBase) {
                 let curprim: Prim2DBase = (<any>this);
                 while (curprim) {
-                    curprim._setFlags(SmartPropertyPrim.flagBoundingInfoDirty);
+                    curprim._setFlags(SmartPropertyPrim.flagBoundingInfoDirty|SmartPropertyPrim.flagLayoutBoundingInfoDirty);
                     if (curprim.isSizeAuto) {
                         curprim.onPrimitivePropertyDirty(Prim2DBase.sizeProperty.flagId);
                         if (curprim._isFlagSet(SmartPropertyPrim.flagUsePositioning)) {
@@ -1198,6 +1199,7 @@
         /**
          * Retrieve the boundingInfo for this Primitive, computed based on the primitive itself and NOT its children
          */
+        @logProp()
         public get levelBoundingInfo(): BoundingInfo2D {
             if (this._isFlagSet(SmartPropertyPrim.flagLevelBoundingInfoDirty)) {
                 if (this.updateLevelBoundingInfo()) {
@@ -1283,6 +1285,39 @@
             }
         }
 
+        public _getFlagsDebug(flags: number): string {
+            let res = "";
+            if (flags & SmartPropertyPrim.flagNoPartOfLayout)          res += "NoPartOfLayout, ";
+            if (flags & SmartPropertyPrim.flagLevelBoundingInfoDirty)  res += "LevelBoundingInfoDirty, ";
+            if (flags & SmartPropertyPrim.flagModelDirty)              res += "ModelDirty, ";
+            if (flags & SmartPropertyPrim.flagLayoutDirty)             res += "LayoutDirty, ";
+            if (flags & SmartPropertyPrim.flagLevelVisible)            res += "LevelVisible, ";
+            if (flags & SmartPropertyPrim.flagBoundingInfoDirty)       res += "BoundingInfoDirty, ";
+            if (flags & SmartPropertyPrim.flagIsPickable)              res += "IsPickable, ";
+            if (flags & SmartPropertyPrim.flagIsVisible)               res += "IsVisible, ";
+            if (flags & SmartPropertyPrim.flagVisibilityChanged)       res += "VisibilityChanged, ";
+            if (flags & SmartPropertyPrim.flagPositioningDirty)        res += "PositioningDirty, ";
+            if (flags & SmartPropertyPrim.flagTrackedGroup)            res += "TrackedGroup, ";
+            if (flags & SmartPropertyPrim.flagWorldCacheChanged)       res += "WorldCacheChanged, ";
+            if (flags & SmartPropertyPrim.flagChildrenFlatZOrder)      res += "ChildrenFlatZOrder, ";
+            if (flags & SmartPropertyPrim.flagZOrderDirty)             res += "ZOrderDirty, ";
+            if (flags & SmartPropertyPrim.flagActualOpacityDirty)      res += "ActualOpacityDirty, ";
+            if (flags & SmartPropertyPrim.flagPrimInDirtyList)         res += "PrimInDirtyList, ";
+            if (flags & SmartPropertyPrim.flagIsContainer)             res += "IsContainer, ";
+            if (flags & SmartPropertyPrim.flagNeedRefresh)             res += "NeedRefresh, ";
+            if (flags & SmartPropertyPrim.flagActualScaleDirty)        res += "ActualScaleDirty, ";
+            if (flags & SmartPropertyPrim.flagDontInheritParentScale)  res += "DontInheritParentScale, ";
+            if (flags & SmartPropertyPrim.flagGlobalTransformDirty)    res += "GlobalTransformDirty, ";
+            if (flags & SmartPropertyPrim.flagLayoutBoundingInfoDirty) res += "LayoutBoundingInfoDirty, ";
+            if (flags & SmartPropertyPrim.flagCollisionActor)          res += "CollisionActor, ";
+            if (flags & SmartPropertyPrim.flagModelUpdate)             res += "ModelUpdate, ";
+            if (flags & SmartPropertyPrim.flagLocalTransformDirty)     res += "LocalTransformDirty, ";
+            if (flags & SmartPropertyPrim.flagUsePositioning)          res += "UsePositioning, ";
+            if (flags & SmartPropertyPrim.flagComputingPositioning)    res += "ComputingPositioning, ";
+
+            return res.slice(0, res.length - 2);
+        }
+
         public static flagNoPartOfLayout          = 0x0000001;    // set if the primitive's position/size must not be computed by Layout Engine
         public static flagLevelBoundingInfoDirty  = 0x0000002;    // set if the primitive's level bounding box (not including children) is dirty
         public static flagModelDirty              = 0x0000004;    // set if the model must be changed
@@ -1310,6 +1345,7 @@
         public static flagLocalTransformDirty     = 0x1000000;    // set if the local transformation matrix must be recomputed
         public static flagUsePositioning          = 0x2000000;    // set if the primitive rely on the positioning engine (padding or margin is used)
         public static flagComputingPositioning    = 0x4000000;    // set if the positioning engine is computing the primitive, used to avoid re entrance
+        public static flagAlignPrimitive          = 0x8000000;    // set if the primitive should be pixel aligned to the render target
 
         private   _uid                : string;
         private   _flags              : number;

+ 2 - 14
canvas2D/src/Engine/babylon.sprite2d.ts

@@ -224,17 +224,6 @@
         //    this._spriteScaleFactor = value;
         //}
 
-        /**
-         * Get/set if the sprite rendering should be aligned to the target rendering device pixel or not
-         */
-        public get alignToPixel(): boolean {
-            return this._alignToPixel;
-        }
-
-        public set alignToPixel(value: boolean) {
-            this._alignToPixel = value;
-        }
-
         protected updateLevelBoundingInfo(): boolean {
             BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo);
             return true;
@@ -273,6 +262,7 @@
          * - scale: the initial scale of the primitive. default is 1. You can alternatively use scaleX &| scaleY to apply non uniform scale
          * - size: the size of the sprite displayed in the canvas, if not specified the spriteSize will be used
          * - dontInheritParentScale: if set the parent's scale won't be taken into consideration to compute the actualScale property
+         * - alignToPixel: if true the sprite's texels will be aligned to the rendering viewport pixels, ensuring the best rendering quality but slow animations won't be done as smooth as if you set false. If false a texel could lies between two pixels, being blended by the texture sampling mode you choose, the rendering result won't be as good, but very slow animation will be overall better looking. Default is true: content will be aligned.
          * - opacity: set the overall opacity of the primitive, 1 to be opaque (default), less than 1 to be transparent.
          * - zOrder: override the zOrder with the specified value
          * - origin: define the normalized origin point location, default [0.5;0.5]
@@ -281,7 +271,6 @@
          * - spriteScaleFactor: DEPRECATED. Old behavior: say you want to display a sprite twice as big as its bitmap which is 64,64, you set the spriteSize to 128,128 and have to set the spriteScaleFactory to 0.5,0.5 in order to address only the 64,64 pixels of the bitmaps. Default is 1,1.
          * - scale9: draw the sprite as a Scale9 sprite, see http://yannickloriot.com/2013/03/9-patch-technique-in-cocos2d/ for more info. x, y, w, z are left, bottom, right, top coordinate of the resizable box
          * - invertY: if true the texture Y will be inverted, default is false.
-         * - alignToPixel: if true the sprite's texels will be aligned to the rendering viewport pixels, ensuring the best rendering quality but slow animations won't be done as smooth as if you set false. If false a texel could lies between two pixels, being blended by the texture sampling mode you choose, the rendering result won't be as good, but very slow animation will be overall better looking. Default is true: content will be aligned.
          * - isVisible: true if the sprite must be visible, false for hidden. Default is true.
          * - isPickable: if true the Primitive can be used with interaction mode and will issue Pointer Event. If false it will be ignored for interaction/intersection test. Default value is true.
          * - isContainer: if true the Primitive acts as a container for interaction, if the primitive is not pickable or doesn't intersection, no further test will be perform on its children. If set to false, children will always be considered for intersection/interaction. Default value is true.
@@ -317,6 +306,7 @@
             scaleX                ?: number,
             scaleY                ?: number,
             dontInheritParentScale?: boolean,
+            alignToPixel          ?: boolean,
             opacity               ?: number,
             zOrder                ?: number, 
             origin                ?: Vector2,
@@ -325,7 +315,6 @@
             spriteScaleFactor     ?: Vector2,
             scale9                ?: Vector4,
             invertY               ?: boolean,
-            alignToPixel          ?: boolean,
             isVisible             ?: boolean,
             isPickable            ?: boolean,
             isContainer           ?: boolean,
@@ -552,7 +541,6 @@
         private _spriteFrame: number;
         private _scale9: Vector4;
         private _invertY: boolean;
-        private _alignToPixel: boolean;
     }
 
     export class Sprite2DInstanceData extends InstanceDataBase {

文件差异内容过多而无法显示
+ 16 - 8
canvas2D/src/Engine/babylon.text2d.ts


+ 249 - 0
canvas2D/src/Tools/babylon.c2dlogging.ts

@@ -0,0 +1,249 @@
+module BABYLON {
+    // logging stuffs
+    export class C2DLogging {
+        // Set to true to temporary disable logging.
+        public static snooze = true;
+        public static logFrameRender(frameCount: number) {
+            C2DLogging.snooze = true;
+            C2DLogging._logFramesCount = frameCount;
+        }
+        public static setPostMessage(message: () => string) {
+            if (C2DLoggingInternals.enableLog) {
+                C2DLoggingInternals.postMessages[C2DLoggingInternals.callDepth-1] = message();
+            }
+        }
+
+        public static _startFrameRender() {
+            if (C2DLogging._logFramesCount === 0) {
+                return;
+            }
+            C2DLogging.snooze = false;
+        }
+
+        public static _endFrameRender() {
+            if (C2DLogging._logFramesCount === 0) {
+                return;
+            }
+            C2DLogging.snooze = true;
+            --C2DLogging._logFramesCount;
+        }
+
+        private static _logFramesCount = 0;
+    }
+
+    class C2DLoggingInternals {
+        //-------------FLAG TO CHANGE TO ENABLE/DISABLE LOGGING ACTIVATION--------------
+        // This flag can't be changed at runtime you must manually change it in the code
+        public static enableLog = false;
+
+        public static callDepth = 0;
+
+        public static depths = [
+            "|-", "|--", "|---", "|----", "|-----", "|------", "|-------", "|--------", "|---------", "|----------",
+            "|-----------", "|------------", "|-------------", "|--------------", "|---------------", "|----------------", "|-----------------", "|------------------", "|-------------------", "|--------------------"
+        ];
+        public static postMessages = [];
+
+        public static computeIndent(): string {
+            // Compute the indent
+            let indent: string = null;
+            if (C2DLoggingInternals.callDepth < 20) {
+                indent = C2DLoggingInternals.depths[C2DLoggingInternals.callDepth];
+            } else {
+                indent = "|";
+                for (let i = 0; i <= C2DLoggingInternals.callDepth; i++) {
+                    indent = indent + "-";
+                }
+            }
+            return indent;
+        }
+
+        public static getFormattedValue(a): string {
+            if (a instanceof Prim2DBase) {
+                return a.id;
+            }
+            if (a == null) {
+                return "[null]";
+            }
+            return a.toString();
+        }
+    }
+
+    export function logProp<T>(message: string = "", alsoGet = false, setNoProlog=false, getNoProlog=false): (target: Object, propName: string | symbol, descriptor: TypedPropertyDescriptor<T>) => void {
+        return (target: Object, propName: string | symbol, descriptor: TypedPropertyDescriptor<T>) => {
+            if (!C2DLoggingInternals.enableLog) {
+                return descriptor;
+            }
+
+            let getter = descriptor.get, setter = descriptor.set;
+
+            if (getter && alsoGet) {
+                descriptor.get = function (): T {
+                    if (C2DLogging.snooze) {
+                        return getter.call(this);
+                    } else {
+                        let indent = C2DLoggingInternals.computeIndent();
+                        let id = this.id || "";
+
+                        if (message !== null && message !== "") {
+                            console.log(message);
+                        }
+
+                        let isSPP = this instanceof SmartPropertyPrim;
+                        let flags = isSPP ? this._flags : 0;
+                        let depth = C2DLoggingInternals.callDepth;
+                        if (!getNoProlog) {
+                            console.log(`${indent} [${id}] (${depth}) ==> get ${propName} property`);
+                        }
+                        ++C2DLoggingInternals.callDepth;
+                        C2DLogging.setPostMessage(() => "[no msg]");
+
+                        // Call the initial getter
+                        let r = getter.call(this);
+
+                        --C2DLoggingInternals.callDepth;
+                        let flagsStr = "";
+                        if (isSPP) {
+                            let nflags = this._flags;
+                            let newFlags = this._getFlagsDebug((nflags & flags) ^ nflags);
+                            let removedFlags = this._getFlagsDebug((nflags & flags) ^ flags);
+                            flagsStr = "";
+                            if (newFlags !== "") {
+                                flagsStr = ` +++[${newFlags}]`;
+                            }
+                            if (removedFlags !== "") {
+                                if (flagsStr !== "") {
+                                    flagsStr += ",";
+                                }
+                                flagsStr += ` ---[${removedFlags}]`;
+                            }
+                        }
+                        console.log(`${indent} [${id}] (${depth})${getNoProlog ? "" : " <=="} get ${propName} property => ${C2DLoggingInternals.getFormattedValue(r)}${flagsStr}, ${C2DLoggingInternals.postMessages[C2DLoggingInternals.callDepth]}`);
+
+                        return r;
+                    }
+                }
+            }
+
+            // Overload the property setter implementation to add our own logic
+            if (setter) {
+                descriptor.set = function (val) {
+                    if (C2DLogging.snooze) {
+                        setter.call(this, val);
+                    } else {
+                        let indent = C2DLoggingInternals.computeIndent();
+                        let id = this.id || "";
+
+                        if (message !== null && message !== "") {
+                            console.log(message);
+                        }
+                        let isSPP = this instanceof SmartPropertyPrim;
+                        let flags = isSPP ? this._flags : 0;
+                        let depth = C2DLoggingInternals.callDepth;
+                        if (!setNoProlog) {
+                            console.log(`${indent} [${id}] (${depth}) ==> set ${propName} property with ${C2DLoggingInternals.getFormattedValue(val)}`);
+                        }
+                        ++C2DLoggingInternals.callDepth;
+                        C2DLogging.setPostMessage(() => "[no msg]");
+
+                        // Change the value
+                        setter.call(this, val);
+
+                        --C2DLoggingInternals.callDepth;
+                        let flagsStr = "";
+                        if (isSPP) {
+                            let nflags = this._flags;
+                            let newFlags = this._getFlagsDebug((nflags & flags) ^ nflags);
+                            let removedFlags = this._getFlagsDebug((nflags & flags) ^ flags);
+                            flagsStr = "";
+                            if (newFlags !== "") {
+                                flagsStr = ` +++[${newFlags}]`;
+                            }
+                            if (removedFlags !== "") {
+                                if (flagsStr !== "") {
+                                    flagsStr += ",";
+                                }
+                                flagsStr += ` ---[${removedFlags}]`;
+                            }
+                        }
+                        console.log(`${indent} [${id}] (${depth})${setNoProlog ? "" : " <=="} set ${propName} property, ${C2DLoggingInternals.postMessages[C2DLoggingInternals.callDepth]}${flagsStr}`);
+                    }
+                }
+            }
+
+            return descriptor;
+        }
+    }
+
+    export function logMethod(message: string = "", noProlog = false) {
+        return (target, key, descriptor) => {
+            if (!C2DLoggingInternals.enableLog) {
+                return descriptor;
+            }
+
+            if (descriptor === undefined) {
+                descriptor = Object.getOwnPropertyDescriptor(target, key);
+            }
+            var originalMethod = descriptor.value;
+
+            //editing the descriptor/value parameter
+            descriptor.value = function () {
+                var args = [];
+                for (var _i = 0; _i < arguments.length; _i++) {
+                    args[_i - 0] = arguments[_i];
+                }
+                if (C2DLogging.snooze) {
+                    return originalMethod.apply(this, args);
+                } else {
+                    var a = args.map(a => C2DLoggingInternals.getFormattedValue(a) + ", ").join();
+                    a = a.slice(0, a.length - 2);
+
+                    let indent = C2DLoggingInternals.computeIndent();
+                    let id = this.id || "";
+
+                    if (message !== null && message !== "") {
+                        console.log(message);
+                    }
+
+                    let isSPP = this instanceof SmartPropertyPrim;
+                    let flags = isSPP ? this._flags : 0;
+                    let depth = C2DLoggingInternals.callDepth;
+                    if (!noProlog) {
+                        console.log(`${indent} [${id}] (${depth}) ==> call: ${key} (${a})`);
+                    }
+                    ++C2DLoggingInternals.callDepth;
+                    C2DLogging.setPostMessage(() => "[no msg]");
+
+                    // Call the method!
+                    var result = originalMethod.apply(this, args);
+
+                    --C2DLoggingInternals.callDepth;
+                    let flagsStr = "";
+                    if (isSPP) {
+                        let nflags = this._flags;
+                        let newFlags = this._getFlagsDebug((nflags & flags) ^ nflags);
+                        let removedFlags = this._getFlagsDebug((nflags & flags) ^ flags);
+                        flagsStr = "";
+                        if (newFlags !== "") {
+                            flagsStr = ` +++[${newFlags}]`;
+                        }
+                        if (removedFlags !== "") {
+                            if (flagsStr !== "") {
+                                flagsStr += ",";
+                            }
+                            flagsStr += ` ---[${removedFlags}]`;
+                        }
+                    }
+                    console.log(`${indent} [${id}] (${depth})${noProlog ? "" : " <=="} call: ${key} (${a}) Res: ${C2DLoggingInternals.getFormattedValue(result)}, ${C2DLoggingInternals.postMessages[C2DLoggingInternals.callDepth]}${flagsStr}`);
+
+                    return result;
+                }
+            };
+
+            // return edited descriptor as opposed to overwriting the descriptor
+            return descriptor;
+        }
+        
+    }
+
+}

+ 10 - 1
canvas2D/src/shaders/ellipse2d.vertex.fx

@@ -9,6 +9,7 @@ attribute float index;
 att vec2 zBias;
 att vec4 transformX;
 att vec4 transformY;
+att vec3 renderingInfo;
 att float opacity;
 
 #ifdef Border
@@ -104,6 +105,14 @@ void main(void) {
 	pos.xy = pos2.xy * properties.xy;
 	pos.z = 1.0;
 	pos.w = 1.0;
-	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, 1);
 
+	float rx = dot(pos, transformX);
+	float ry = dot(pos, transformY);
+
+	if (renderingInfo.z == 1.0) {
+		rx = floor((rx / renderingInfo.x) + 0.5) * renderingInfo.x;
+		ry = floor((ry / renderingInfo.y) + 0.5) * renderingInfo.y;
+	}
+
+	gl_Position = vec4(rx, ry, zBias.x, 1);
 }

+ 10 - 1
canvas2D/src/shaders/lines2d.vertex.fx

@@ -9,6 +9,7 @@ attribute vec2 position;
 att vec2 zBias;
 att vec4 transformX;
 att vec4 transformY;
+att vec3 renderingInfo;
 att float opacity;
 
 #ifdef FillSolid
@@ -64,6 +65,14 @@ void main(void) {
 	pos.xy = position.xy;
 	pos.z = 1.0;
 	pos.w = 1.0;
-	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, 1);
+	
+	float rx = dot(pos, transformX);
+	float ry = dot(pos, transformY);
 
+	if (renderingInfo.z == 1.0) {
+		rx = floor((rx / renderingInfo.x) + 0.5) * renderingInfo.x;
+		ry = floor((ry / renderingInfo.y) + 0.5) * renderingInfo.y;
+	}
+
+	gl_Position = vec4(rx, ry, zBias.x, 1);
 }

+ 10 - 1
canvas2D/src/shaders/rect2d.vertex.fx

@@ -9,6 +9,7 @@ attribute float index;
 att vec2 zBias;
 att vec4 transformX;
 att vec4 transformY;
+att vec3 renderingInfo;
 att float opacity;
 
 #ifdef Border
@@ -202,6 +203,14 @@ void main(void) {
 	pos.xy = pos2.xy * properties.xy;
 	pos.z = 1.0;
 	pos.w = 1.0;
-	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, 1);
 
+	float rx = dot(pos, transformX);
+	float ry = dot(pos, transformY);
+
+	if (renderingInfo.z == 1.0) {
+		rx = floor((rx / renderingInfo.x) + 0.5) * renderingInfo.x;
+		ry = floor((ry / renderingInfo.y) + 0.5) * renderingInfo.y;
+	}
+
+	gl_Position = vec4(rx, ry, zBias.x, 1);
 }

+ 15 - 1
canvas2D/src/shaders/sprite2d.vertex.fx

@@ -25,6 +25,7 @@ att vec4 scale9;
 att vec2 zBias;
 att vec4 transformX;
 att vec4 transformY;
+att vec3 renderingInfo;
 att float opacity;
 
 // Uniforms
@@ -101,5 +102,18 @@ void main(void) {
 	vOpacity = opacity;
 	pos.z = 1.0;
 	pos.w = 1.0;
-	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, 1);
+
+	float x = dot(pos, transformX);
+	float y = dot(pos, transformY);
+	if (renderingInfo.z == 1.0) {
+		float rw = renderingInfo.x;
+		float rh = renderingInfo.y;
+		float irw = 2.0 / rw;
+		float irh = 2.0 / rh;
+
+		x = floor((x / irw) + 0.5) * irw;
+		y = floor((y / irh) + 0.5) * irh;
+	}
+
+	gl_Position = vec4(x, y, zBias.x, 1);
 }	

+ 15 - 1
canvas2D/src/shaders/text2d.vertex.fx

@@ -11,6 +11,7 @@ att vec2 zBias;
 
 att vec4 transformX;
 att vec4 transformY;
+att vec3 renderingInfo;
 att float opacity;
 
 att vec2 topLeftUV;
@@ -60,5 +61,18 @@ void main(void) {
 	pos.xy = floor(pos2.xy * superSampleFactor * sizeUV * textureSize);	// Align on target pixel to avoid bad interpolation
 	pos.z = 1.0;
 	pos.w = 1.0;
-	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, 1);
+
+	float x = dot(pos, transformX);
+	float y = dot(pos, transformY);
+	if (renderingInfo.z == 1.0) {
+		float rw = renderingInfo.x;
+		float rh = renderingInfo.y;
+		float irw = 2.0 / rw;
+		float irh = 2.0 / rh;
+
+		x = floor((x / irw) + 0.5) * irw;
+		y = floor((y / irh) + 0.5) * irh;
+	}
+
+	gl_Position = vec4(x, y, zBias.x, 1);
 }