paavohtl Claude Opus 4.6 (1M context) commited on
Commit
3ce5627
·
1 Parent(s): b62b0ea

feat: port DDDisplay draw_polyline with zero-alloc polygon fill

Browse files

Wire 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
- /// Input vertices are Fixed-point (x, y) pairs.
807
- pub fn draw_polygon_filled(writer: &mut impl PixelWriter, vertices: &[(Fixed, Fixed)], color: u8) {
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(&input, &mut clipped, writer);
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,