@@ -8,138 +8,118 @@ import :fill;
88namespace Karm ::Gfx {
99
1010export struct CpuRast {
11- static constexpr auto AA = 3 ;
12- static constexpr auto UNIT = 1 .0f / AA;
13- static constexpr auto HALF_UNIT = 1 .0f / AA / 2.0 ;
14-
15- struct Active {
16- f64 x;
17- isize sign;
18- };
19-
2011 struct Frag {
2112 Math::Vec2i xy;
2213 Math::Vec2f uv;
2314 f64 a;
2415 };
2516
26- Vec<Active> _active{};
27- Vec<irange> _ranges;
2817 Vec<f64 > _scanline{};
2918
30- void _appendRange (irange range) {
31- usize i = 0 ;
32- for (auto & r : _ranges) {
33- if (r.overlaps (range)) {
34- auto merged = r.merge (range);
35- _ranges.removeAt (i);
36- _appendRange (merged);
37- return ;
38- }
19+ void _accumulate (isize xOffset, isize width, Math::Vec2f p0, Math::Vec2f p1) {
20+ f64 dy = p1.y - p0.y ;
3921
40- if (range.end () > r.start ) {
41- _ranges.insert (i, range);
42- return ;
43- }
22+ if (Math::epsilonEq (dy, 0.0 ))
23+ return ;
4424
45- i++;
25+ f64 xMid = (p0.x + p1.x ) / 2.0 ;
26+ f64 xRel = xMid - Math::floori (xMid);
27+ f64 area = dy * (1.0 - xRel);
28+
29+ isize idx = Math::floori (xMid) - xOffset;
30+
31+ if (idx < 0 ) {
32+ _scanline[0 ] += dy;
33+ return ;
4634 }
4735
48- _ranges. pushBack (range);
49- }
36+ if (idx >= width)
37+ return ;
5038
39+ _scanline[idx] += area;
40+ _scanline[idx + 1 ] += (dy - area);
41+ }
5142 void fill (Math::Polyf& poly, Math::Recti clip, FillRule fillRule, auto cb) {
52- auto polyBound = poly.bound ().grow (UNIT);
43+ auto polyBound = poly.bound ();
44+
5345 auto clipBound = polyBound
5446 .ceil ()
5547 .cast <isize>()
5648 .clipTo (clip);
5749
50+ if (clipBound.width <= 0 or clipBound.height <= 0 )
51+ return ;
52+
5853 _scanline.resize (clipBound.width + 1 );
5954
6055 for (isize y = clipBound.top (); y < clipBound.bottom (); y++) {
6156 zeroFill<f64 >(mutSub (_scanline, 0 , clipBound.width + 1 ));
62- _ranges.clear ();
6357
64- for ( f64 yy = y; yy < y + 1.0 ; yy += UNIT) {
65- _active. clear () ;
58+ f64 yTop = y;
59+ f64 yBot = y + 1.0 ;
6660
67- for (auto & edge : poly) {
68- auto sample = yy + HALF_UNIT;
61+ for (auto & edge : poly) {
62+ if (edge.bound ().bottom () <= yTop or edge.bound ().top () >= yBot)
63+ continue ;
6964
70- if (edge.bound ().top () <= sample and sample < edge.bound ().bottom ()) {
71- _active.pushBack ({
72- .x = edge.sx + (sample - edge.sy ) / (edge.ey - edge.sy ) * (edge.ex - edge.sx ),
73- .sign = edge.sy > edge.ey ? 1 : -1 ,
74- });
75- }
76- }
65+ auto d = edge.delta ();
7766
78- if (_active. len () == 0 )
67+ if (Math::epsilonEq (d. y , 0.0 ) )
7968 continue ;
8069
81- sort (_active, [](auto const & a, auto const & b) {
82- return a.x <=> b.x ;
83- });
70+ f64 t0 = clamp01 ((yTop - edge.start .y ) / d.y );
71+ f64 t1 = clamp01 ((yBot - edge.start .y ) / d.y );
8472
85- isize rule = 0 ;
86- for (usize i = 0 ; i + 1 < _active.len (); i++) {
87- if (fillRule == FillRule::NONZERO) {
88- rule += _active[i].sign ;
89- if (rule == 0 )
90- continue ;
91- }
73+ if (t0 > t1)
74+ std::swap (t0, t1);
9275
93- if (fillRule == FillRule::EVENODD) {
94- rule++;
95- if (rule % 2 == 0 )
96- continue ;
97- }
76+ Math::Vec2f p0 = edge.start + d * t0;
77+ Math::Vec2f p1 = edge.start + d * t1;
9878
99- // Clip the range to the clip rect
100- // NOTE: The following looks a bit verbose but it's necessary to avoid
101- // floating point errors when converting to integer coordinates.
102- f64 x1 = max (_active[i].x , clipBound.start ());
79+ f64 xDir = (p1.x > p0.x ) ? 1.0 : -1.0 ;
80+ Math::Vec2f cursor = p0;
10381
104- isize cx1 = max ( Math::ceili (_active[i]. x ), clipBound. start () );
105- isize fx1 = max ( Math::floori (_active[i]. x ), clipBound. start () );
82+ isize ixStart = Math::floori (p0. x );
83+ isize ixEnd = Math::floori (p1. x );
10684
107- f64 x2 = min (_active[i + 1 ].x , clipBound.end ());
108- isize cx2 = min (Math::ceili (_active[i + 1 ].x ), clipBound.end ());
109- isize fx2 = min (Math::floori (_active[i + 1 ].x ), clipBound.end ());
85+ if (ixStart == ixEnd) {
86+ _accumulate (clipBound.x , clipBound.width , cursor, p1);
87+ } else {
88+ isize steps = Math::abs (ixEnd - ixStart);
89+ for (isize i = 0 ; i < steps; i++) {
90+ f64 nextX = (xDir > 0 ) ? Math::floor (cursor.x ) + 1.0 : Math::ceil (cursor.x ) - 1.0 ;
11091
111- // Skip if the range is empty
112- if (x1 >= x2)
113- continue ;
92+ f64 t = (nextX - cursor.x ) / (p1.x - cursor.x );
11493
115- _appendRange (irange::fromStartEnd (fx1, cx2));
94+ Math::Vec2f nextP = cursor + (p1 - cursor) * t;
95+ nextP.x = nextX;
11696
117- // Are x1 and x2 on the same pixel?
118- if (fx1 == fx2) {
119- _scanline[fx1 - clipBound.x ] += (x2 - x1) * UNIT;
120- continue ;
97+ _accumulate (clipBound.x , clipBound.width , cursor, nextP);
98+ cursor = nextP;
12199 }
122-
123- // Compute the coverage for the first and last pixel
124- _scanline[x1 - clipBound.x ] += (cx1 - x1) * UNIT;
125- _scanline[x2 - clipBound.x ] += (x2 - fx2) * UNIT;
126-
127- // Fill the pixels in between
128- for (isize x = cx1; x < fx2; x++)
129- _scanline[x - clipBound.x ] += UNIT;
100+ _accumulate (clipBound.x , clipBound.width , cursor, p1);
130101 }
131102 }
132103
133- for (auto r : _ranges) {
134- for (isize x = r.start ; x < r.end (); x++) {
135- auto xy = Math::Vec2i{x, y};
104+ f64 accumulator = 0 ;
105+ for (isize x = 0 ; x < clipBound.width ; x++) {
106+ accumulator += _scanline[x];
107+ f64 coverage = accumulator;
108+ if (fillRule == FillRule::NONZERO) {
109+ coverage = clamp01 (Math::abs (coverage));
110+ } else {
111+ f64 val = Math::abs (coverage);
112+ val = val - 2.0 * Math::floor (val / 2.0 );
113+ if (val > 1.0 )
114+ val = 2.0 - val;
115+ coverage = val;
116+ }
136117
137- auto uv = Math::Vec2f{
138- (x - polyBound.start ()) / polyBound.width ,
139- (y - polyBound.top ()) / polyBound.height ,
140- };
118+ if (coverage > Limits<f64 >::EPSILON) {
119+ Math::Vec2i pos = {clipBound.x + x, y};
120+ Math::Vec2f uv = (pos.cast <f64 >() - polyBound.topStart ()) / polyBound.size ();
141121
142- cb (Frag{xy , uv, clamp01 (_scanline[x - clipBound. x ]) });
122+ cb (Frag{pos , uv, coverage });
143123 }
144124 }
145125 }
0 commit comments