feat: port DDDisplay draw_polyline with zero-alloc polygon fill
Browse filesWire draw_polyline (slot 12) to the pure Rust polygon fill pipeline.
All buffers are stack-allocated: 256-vertex input cap, 512-vertex clip
buffers, 1024-entry span tables. Make Vertex public, remove Vec
allocations from draw_polygon_filled. Add polygon snapshot capture.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-core/src/display/dd_display.rs
CHANGED
|
@@ -741,6 +741,44 @@ pub unsafe extern "thiscall" fn draw_line_clipped(
|
|
| 741 |
);
|
| 742 |
}
|
| 743 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 744 |
/// Port of DDDisplay::DrawPixelStrip (vtable slot 15, 0x56BE10).
|
| 745 |
///
|
| 746 |
/// Draws `count + 1` pixels starting at (x, y), stepping by (dx, dy) each
|
|
|
|
| 741 |
);
|
| 742 |
}
|
| 743 |
|
| 744 |
+
/// Port of DDDisplay::DrawPolyline (vtable slot 12, 0x56BCC0).
|
| 745 |
+
///
|
| 746 |
+
/// Transforms point array by camera offset, clips, and fills the polygon.
|
| 747 |
+
/// Points are Fixed-point (x, y) pairs. Pixel-integer camera is applied as
|
| 748 |
+
/// `camera * 0x10000 + coord`.
|
| 749 |
+
///
|
| 750 |
+
/// # Safety
|
| 751 |
+
/// `this` must be a valid `*mut DDDisplay` (actually a `*mut DisplayGfx`).
|
| 752 |
+
pub unsafe extern "thiscall" fn draw_polyline(
|
| 753 |
+
this: *mut DDDisplay,
|
| 754 |
+
points: *mut i32,
|
| 755 |
+
count: i32,
|
| 756 |
+
color: u32,
|
| 757 |
+
) {
|
| 758 |
+
let gfx = this as *mut DisplayGfx;
|
| 759 |
+
let cam_x = Fixed::from_int((*gfx).camera_x);
|
| 760 |
+
let cam_y = Fixed::from_int((*gfx).camera_y);
|
| 761 |
+
|
| 762 |
+
// Build vertex array with camera offset on the stack
|
| 763 |
+
let n = count as usize;
|
| 764 |
+
if n == 0 || n > 256 {
|
| 765 |
+
return;
|
| 766 |
+
}
|
| 767 |
+
|
| 768 |
+
let mut verts = [line_draw::Vertex::new(Fixed::ZERO, Fixed::ZERO); 256];
|
| 769 |
+
for i in 0..n {
|
| 770 |
+
verts[i] = line_draw::Vertex::new(
|
| 771 |
+
Fixed::from_raw(*points.add(i * 2)) + cam_x,
|
| 772 |
+
Fixed::from_raw(*points.add(i * 2 + 1)) + cam_y,
|
| 773 |
+
);
|
| 774 |
+
}
|
| 775 |
+
|
| 776 |
+
acquire_render_lock(gfx);
|
| 777 |
+
|
| 778 |
+
let mut writer = BitGridWriter((*gfx).layer_0);
|
| 779 |
+
line_draw::draw_polygon_filled(&mut writer, &verts[..n], color as u8);
|
| 780 |
+
}
|
| 781 |
+
|
| 782 |
/// Port of DDDisplay::DrawPixelStrip (vtable slot 15, 0x56BE10).
|
| 783 |
///
|
| 784 |
/// Draws `count + 1` pixels starting at (x, y), stepping by (dx, dy) each
|
crates/openwa-core/src/display/line_draw.rs
CHANGED
|
@@ -550,9 +550,16 @@ const MAX_CLIP_VERTS: usize = 512;
|
|
| 550 |
|
| 551 |
/// A Fixed-point vertex (x, y).
|
| 552 |
#[derive(Clone, Copy)]
|
| 553 |
-
struct Vertex {
|
| 554 |
-
x: Fixed,
|
| 555 |
-
y: Fixed,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 556 |
}
|
| 557 |
|
| 558 |
/// Clip polygon edges against a single axis boundary.
|
|
@@ -803,15 +810,13 @@ pub fn fill_polygon(writer: &mut impl PixelWriter, verts: &[Vertex], color: u8)
|
|
| 803 |
|
| 804 |
/// Draw a filled polygon. Clips against the writer's bounds, then scanline-fills.
|
| 805 |
///
|
| 806 |
-
///
|
| 807 |
-
pub fn draw_polygon_filled(writer: &mut impl PixelWriter, vertices: &[
|
| 808 |
-
let input: Vec<Vertex> = vertices.iter().map(|&(x, y)| Vertex { x, y }).collect();
|
| 809 |
-
|
| 810 |
let mut clipped = [Vertex {
|
| 811 |
x: Fixed::ZERO,
|
| 812 |
y: Fixed::ZERO,
|
| 813 |
}; MAX_CLIP_VERTS];
|
| 814 |
-
let count = clip_polygon(
|
| 815 |
|
| 816 |
if count > 2 {
|
| 817 |
fill_polygon(writer, &clipped[..count], color);
|
|
@@ -1109,6 +1114,10 @@ mod tests {
|
|
| 1109 |
}
|
| 1110 |
|
| 1111 |
// Polygon fill snapshot tests
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1112 |
macro_rules! snapshot_test_poly {
|
| 1113 |
($name:ident, $snap:expr, $verts:expr, $color:expr) => {
|
| 1114 |
#[test]
|
|
@@ -1123,17 +1132,17 @@ mod tests {
|
|
| 1123 |
snapshot_test_poly!(
|
| 1124 |
snap_poly_triangle,
|
| 1125 |
"poly_triangle",
|
| 1126 |
-
&[(f(64), f(10)), (f(118), f(100)), (f(10), f(100))],
|
| 1127 |
1
|
| 1128 |
);
|
| 1129 |
snapshot_test_poly!(
|
| 1130 |
snap_poly_square,
|
| 1131 |
"poly_square",
|
| 1132 |
&[
|
| 1133 |
-
(f(20), f(20)),
|
| 1134 |
-
(f(100), f(20)),
|
| 1135 |
-
(f(100), f(100)),
|
| 1136 |
-
(f(20), f(100))
|
| 1137 |
],
|
| 1138 |
2
|
| 1139 |
);
|
|
@@ -1141,17 +1150,17 @@ mod tests {
|
|
| 1141 |
snap_poly_diamond,
|
| 1142 |
"poly_diamond",
|
| 1143 |
&[
|
| 1144 |
-
(f(64), f(10)),
|
| 1145 |
-
(f(118), f(64)),
|
| 1146 |
-
(f(64), f(118)),
|
| 1147 |
-
(f(10), f(64))
|
| 1148 |
],
|
| 1149 |
3
|
| 1150 |
);
|
| 1151 |
snapshot_test_poly!(
|
| 1152 |
snap_poly_partially_outside,
|
| 1153 |
"poly_partially_outside",
|
| 1154 |
-
&[(f(64), f(-30)), (f(160), f(100)), (f(-30), f(100))],
|
| 1155 |
4
|
| 1156 |
);
|
| 1157 |
|
|
@@ -1164,7 +1173,7 @@ mod tests {
|
|
| 1164 |
grid.clip_bottom = 98;
|
| 1165 |
draw_polygon_filled(
|
| 1166 |
&mut grid,
|
| 1167 |
-
&[(f(64), f(10)), (f(118), f(100)), (f(10), f(100))],
|
| 1168 |
5,
|
| 1169 |
);
|
| 1170 |
assert_matches_snapshot(&grid, "poly_restricted_clip");
|
|
|
|
| 550 |
|
| 551 |
/// A Fixed-point vertex (x, y).
|
| 552 |
#[derive(Clone, Copy)]
|
| 553 |
+
pub struct Vertex {
|
| 554 |
+
pub x: Fixed,
|
| 555 |
+
pub y: Fixed,
|
| 556 |
+
}
|
| 557 |
+
|
| 558 |
+
impl Vertex {
|
| 559 |
+
#[inline]
|
| 560 |
+
pub const fn new(x: Fixed, y: Fixed) -> Self {
|
| 561 |
+
Self { x, y }
|
| 562 |
+
}
|
| 563 |
}
|
| 564 |
|
| 565 |
/// Clip polygon edges against a single axis boundary.
|
|
|
|
| 810 |
|
| 811 |
/// Draw a filled polygon. Clips against the writer's bounds, then scanline-fills.
|
| 812 |
///
|
| 813 |
+
/// No heap allocation — uses stack buffers for clipping.
|
| 814 |
+
pub fn draw_polygon_filled(writer: &mut impl PixelWriter, vertices: &[Vertex], color: u8) {
|
|
|
|
|
|
|
| 815 |
let mut clipped = [Vertex {
|
| 816 |
x: Fixed::ZERO,
|
| 817 |
y: Fixed::ZERO,
|
| 818 |
}; MAX_CLIP_VERTS];
|
| 819 |
+
let count = clip_polygon(vertices, &mut clipped, writer);
|
| 820 |
|
| 821 |
if count > 2 {
|
| 822 |
fill_polygon(writer, &clipped[..count], color);
|
|
|
|
| 1114 |
}
|
| 1115 |
|
| 1116 |
// Polygon fill snapshot tests
|
| 1117 |
+
fn v(x: Fixed, y: Fixed) -> Vertex {
|
| 1118 |
+
Vertex::new(x, y)
|
| 1119 |
+
}
|
| 1120 |
+
|
| 1121 |
macro_rules! snapshot_test_poly {
|
| 1122 |
($name:ident, $snap:expr, $verts:expr, $color:expr) => {
|
| 1123 |
#[test]
|
|
|
|
| 1132 |
snapshot_test_poly!(
|
| 1133 |
snap_poly_triangle,
|
| 1134 |
"poly_triangle",
|
| 1135 |
+
&[v(f(64), f(10)), v(f(118), f(100)), v(f(10), f(100))],
|
| 1136 |
1
|
| 1137 |
);
|
| 1138 |
snapshot_test_poly!(
|
| 1139 |
snap_poly_square,
|
| 1140 |
"poly_square",
|
| 1141 |
&[
|
| 1142 |
+
v(f(20), f(20)),
|
| 1143 |
+
v(f(100), f(20)),
|
| 1144 |
+
v(f(100), f(100)),
|
| 1145 |
+
v(f(20), f(100))
|
| 1146 |
],
|
| 1147 |
2
|
| 1148 |
);
|
|
|
|
| 1150 |
snap_poly_diamond,
|
| 1151 |
"poly_diamond",
|
| 1152 |
&[
|
| 1153 |
+
v(f(64), f(10)),
|
| 1154 |
+
v(f(118), f(64)),
|
| 1155 |
+
v(f(64), f(118)),
|
| 1156 |
+
v(f(10), f(64))
|
| 1157 |
],
|
| 1158 |
3
|
| 1159 |
);
|
| 1160 |
snapshot_test_poly!(
|
| 1161 |
snap_poly_partially_outside,
|
| 1162 |
"poly_partially_outside",
|
| 1163 |
+
&[v(f(64), f(-30)), v(f(160), f(100)), v(f(-30), f(100))],
|
| 1164 |
4
|
| 1165 |
);
|
| 1166 |
|
|
|
|
| 1173 |
grid.clip_bottom = 98;
|
| 1174 |
draw_polygon_filled(
|
| 1175 |
&mut grid,
|
| 1176 |
+
&[v(f(64), f(10)), v(f(118), f(100)), v(f(10), f(100))],
|
| 1177 |
5,
|
| 1178 |
);
|
| 1179 |
assert_matches_snapshot(&grid, "poly_restricted_clip");
|
crates/openwa-dll/src/replacements/display.rs
CHANGED
|
@@ -300,6 +300,7 @@ pub fn install() -> Result<(), String> {
|
|
| 300 |
// Patch DDDisplay vtable (0x66A218): replace ported methods with Rust.
|
| 301 |
vtable_replace!(DDDisplayVtable, va::DD_DISPLAY_VTABLE, {
|
| 302 |
get_dimensions => dd_display::get_dimensions,
|
|
|
|
| 303 |
draw_line => dd_display::draw_line,
|
| 304 |
draw_line_clipped => dd_display::draw_line_clipped,
|
| 305 |
draw_pixel_strip => dd_display::draw_pixel_strip,
|
|
|
|
| 300 |
// Patch DDDisplay vtable (0x66A218): replace ported methods with Rust.
|
| 301 |
vtable_replace!(DDDisplayVtable, va::DD_DISPLAY_VTABLE, {
|
| 302 |
get_dimensions => dd_display::get_dimensions,
|
| 303 |
+
draw_polyline => dd_display::draw_polyline,
|
| 304 |
draw_line => dd_display::draw_line,
|
| 305 |
draw_line_clipped => dd_display::draw_line_clipped,
|
| 306 |
draw_pixel_strip => dd_display::draw_pixel_strip,
|