@@ -565,6 +565,10 @@ private static void CaptureTileScanline(int y, Span<float> scanline, ref TileCap
565565 /// <summary>
566566 /// Builds a compact edge table in scanner-local coordinates.
567567 /// </summary>
568+ /// <remarks>
569+ /// Edges are converted to 24.8 fixed-point once during table construction so the hot
570+ /// band/tile rasterization loop does not pay float-to-fixed conversion costs repeatedly.
571+ /// </remarks>
568572 /// <param name="multipolygon">Input tessellated rings.</param>
569573 /// <param name="minX">Interest left in absolute coordinates.</param>
570574 /// <param name="minY">Interest top in absolute coordinates.</param>
@@ -609,7 +613,16 @@ private static int BuildEdgeTable(
609613 continue ;
610614 }
611615
612- destination [ count ++ ] = new EdgeData ( x0 , y0 , x1 , y1 , minRow , maxRow ) ;
616+ int fx0 = FloatToFixed24Dot8 ( x0 ) ;
617+ int fy0 = FloatToFixed24Dot8 ( y0 ) ;
618+ int fx1 = FloatToFixed24Dot8 ( x1 ) ;
619+ int fy1 = FloatToFixed24Dot8 ( y1 ) ;
620+ if ( fy0 == fy1 )
621+ {
622+ continue ;
623+ }
624+
625+ destination [ count ++ ] = new EdgeData ( fx0 , fy0 , fx1 , fy1 , minRow , maxRow ) ;
613626 }
614627 }
615628
@@ -703,6 +716,49 @@ private static bool TryGetBandHeight(int width, int height, int wordsPerRow, lon
703716 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
704717 private static int FloatToFixed24Dot8 ( float value ) => ( int ) MathF . Round ( value * FixedOne ) ;
705718
719+ /// <summary>
720+ /// Clips a fixed-point segment against vertical bounds.
721+ /// </summary>
722+ /// <param name="x0">Segment start X in 24.8 fixed-point (updated in place).</param>
723+ /// <param name="y0">Segment start Y in 24.8 fixed-point (updated in place).</param>
724+ /// <param name="x1">Segment end X in 24.8 fixed-point (updated in place).</param>
725+ /// <param name="y1">Segment end Y in 24.8 fixed-point (updated in place).</param>
726+ /// <param name="minY">Minimum Y bound in 24.8 fixed-point.</param>
727+ /// <param name="maxY">Maximum Y bound in 24.8 fixed-point.</param>
728+ /// <returns><see langword="true"/> when a non-horizontal clipped segment remains.</returns>
729+ private static bool ClipToVerticalBoundsFixed ( ref int x0 , ref int y0 , ref int x1 , ref int y1 , int minY , int maxY )
730+ {
731+ double t0 = 0D ;
732+ double t1 = 1D ;
733+ int originX0 = x0 ;
734+ int originY0 = y0 ;
735+ long dx = ( long ) x1 - originX0 ;
736+ long dy = ( long ) y1 - originY0 ;
737+ if ( ! ClipTestFixed ( - ( double ) dy , originY0 - ( double ) minY , ref t0 , ref t1 ) )
738+ {
739+ return false ;
740+ }
741+
742+ if ( ! ClipTestFixed ( dy , maxY - ( double ) originY0 , ref t0 , ref t1 ) )
743+ {
744+ return false ;
745+ }
746+
747+ if ( t1 < 1D )
748+ {
749+ x1 = originX0 + ( int ) Math . Round ( dx * t1 ) ;
750+ y1 = originY0 + ( int ) Math . Round ( dy * t1 ) ;
751+ }
752+
753+ if ( t0 > 0D )
754+ {
755+ x0 = originX0 + ( int ) Math . Round ( dx * t0 ) ;
756+ y0 = originY0 + ( int ) Math . Round ( dy * t0 ) ;
757+ }
758+
759+ return y0 != y1 ;
760+ }
761+
706762 /// <summary>
707763 /// Clips a segment against vertical bounds using Liang-Barsky style parametric tests.
708764 /// </summary>
@@ -785,6 +841,46 @@ private static bool ClipTest(float p, float q, ref float t0, ref float t1)
785841 return true ;
786842 }
787843
844+ /// <summary>
845+ /// One Liang-Barsky clip test step for fixed-point clipping.
846+ /// </summary>
847+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
848+ private static bool ClipTestFixed ( double p , double q , ref double t0 , ref double t1 )
849+ {
850+ if ( p == 0D )
851+ {
852+ return q >= 0D ;
853+ }
854+
855+ double r = q / p ;
856+ if ( p < 0D )
857+ {
858+ if ( r > t1 )
859+ {
860+ return false ;
861+ }
862+
863+ if ( r > t0 )
864+ {
865+ t0 = r ;
866+ }
867+ }
868+ else
869+ {
870+ if ( r < t0 )
871+ {
872+ return false ;
873+ }
874+
875+ if ( r < t1 )
876+ {
877+ t1 = r ;
878+ }
879+ }
880+
881+ return true ;
882+ }
883+
788884 /// <summary>
789885 /// Returns one when a fixed-point value lies exactly on a cell boundary at or below zero.
790886 /// This is used to keep edge ownership consistent for vertical lines.
@@ -922,34 +1018,27 @@ public void RasterizeMultipolygon(TessellatedMultipolygon multipolygon, int minX
9221018 /// <param name="bandTop">Top row of this context in global scanner-local coordinates.</param>
9231019 public void RasterizeEdgeTable ( ReadOnlySpan < EdgeData > edges , ReadOnlySpan < int > edgeIndices , int bandTop )
9241020 {
925- float minY = bandTop ;
926- float maxY = bandTop + this . height ;
1021+ int bandTopFixed = bandTop * FixedOne ;
1022+ int bandBottomFixed = bandTopFixed + ( this . height * FixedOne ) ;
9271023
9281024 for ( int i = 0 ; i < edgeIndices . Length ; i ++ )
9291025 {
9301026 EdgeData edge = edges [ edgeIndices [ i ] ] ;
931- float x0 = edge . X0 ;
932- float y0 = edge . Y0 ;
933- float x1 = edge . X1 ;
934- float y1 = edge . Y1 ;
1027+ int x0 = edge . X0 ;
1028+ int y0 = edge . Y0 ;
1029+ int x1 = edge . X1 ;
1030+ int y1 = edge . Y1 ;
9351031
936- if ( ! ClipToVerticalBounds ( ref x0 , ref y0 , ref x1 , ref y1 , minY , maxY ) )
1032+ if ( ! ClipToVerticalBoundsFixed ( ref x0 , ref y0 , ref x1 , ref y1 , bandTopFixed , bandBottomFixed ) )
9371033 {
9381034 continue ;
9391035 }
9401036
941- // Convert to fixed-point in band-local Y coordinates so downstream walkers can
942- // index 0..bandHeight-1 directly without extra subtraction in hot loops.
943- int fx0 = FloatToFixed24Dot8 ( x0 ) ;
944- int fy0 = FloatToFixed24Dot8 ( y0 - bandTop ) ;
945- int fx1 = FloatToFixed24Dot8 ( x1 ) ;
946- int fy1 = FloatToFixed24Dot8 ( y1 - bandTop ) ;
947- if ( fy0 == fy1 )
948- {
949- continue ;
950- }
1037+ // Convert global scanner Y to band-local Y after clipping.
1038+ y0 -= bandTopFixed ;
1039+ y1 -= bandTopFixed ;
9511040
952- this . RasterizeLine ( fx0 , fy0 , fx1 , fy1 ) ;
1041+ this . RasterizeLine ( x0 , y0 , x1 , y1 ) ;
9531042 }
9541043 }
9551044
@@ -1862,12 +1951,15 @@ private void RasterizeLine(int x0, int y0, int x1, int y1)
18621951 /// <summary>
18631952 /// Immutable scanner-local edge record with precomputed affected-row bounds.
18641953 /// </summary>
1954+ /// <remarks>
1955+ /// Coordinates are stored as signed 24.8 fixed-point values.
1956+ /// </remarks>
18651957 private readonly struct EdgeData
18661958 {
18671959 /// <summary>
18681960 /// Initializes a new instance of the <see cref="EdgeData"/> struct.
18691961 /// </summary>
1870- public EdgeData ( float x0 , float y0 , float x1 , float y1 , int minRow , int maxRow )
1962+ public EdgeData ( int x0 , int y0 , int x1 , int y1 , int minRow , int maxRow )
18711963 {
18721964 this . X0 = x0 ;
18731965 this . Y0 = y0 ;
@@ -1878,24 +1970,24 @@ public EdgeData(float x0, float y0, float x1, float y1, int minRow, int maxRow)
18781970 }
18791971
18801972 /// <summary>
1881- /// Gets edge start X in scanner-local coordinates.
1973+ /// Gets edge start X in scanner-local coordinates (24.8 fixed-point) .
18821974 /// </summary>
1883- public float X0 { get ; }
1975+ public int X0 { get ; }
18841976
18851977 /// <summary>
1886- /// Gets edge start Y in scanner-local coordinates.
1978+ /// Gets edge start Y in scanner-local coordinates (24.8 fixed-point) .
18871979 /// </summary>
1888- public float Y0 { get ; }
1980+ public int Y0 { get ; }
18891981
18901982 /// <summary>
1891- /// Gets edge end X in scanner-local coordinates.
1983+ /// Gets edge end X in scanner-local coordinates (24.8 fixed-point) .
18921984 /// </summary>
1893- public float X1 { get ; }
1985+ public int X1 { get ; }
18941986
18951987 /// <summary>
1896- /// Gets edge end Y in scanner-local coordinates.
1988+ /// Gets edge end Y in scanner-local coordinates (24.8 fixed-point) .
18971989 /// </summary>
1898- public float Y1 { get ; }
1990+ public int Y1 { get ; }
18991991
19001992 /// <summary>
19011993 /// Gets the first scanner row affected by this edge.
0 commit comments