Skip to content

Commit 7767e83

Browse files
committed
karm-gfx: New analytical rasterizer.
1 parent 9322377 commit 7767e83

File tree

1 file changed

+69
-89
lines changed

1 file changed

+69
-89
lines changed

src/karm-gfx/cpu/rast.cpp

Lines changed: 69 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -8,138 +8,118 @@ import :fill;
88
namespace Karm::Gfx {
99

1010
export 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

Comments
 (0)