paavohtl Claude Opus 4.6 (1M context) commited on
Commit
edcdcc5
·
1 Parent(s): bdfa78f

refactor: switch slot 22 (DrawTiledTerrain) to native BlitBitmapClipped

Browse files

`draw_tiled_terrain` was the last in-game caller of the asm bridge to
WA's `DisplayGfx::BlitBitmapClipped` (0x56A700). Slot 11's port already
had `blit_bitmap_clipped_native` doing the same clipping + dispatch
through `cbitmap_blit_via_wrapper`, so the bridge was redundant.

Verified the call site at 0x56C6A1: `EAX=this, EDX=col_width, stack
=(dst_x, dst_y, row_height, bitmap_ptr, flags)` maps cleanly to
`blit_bitmap_clipped_native(this, dst_x, dst_y, width, height,
surface, flags)`. Verified `TileBitmapSet::bitmap_ptrs[]` entries
share the CBitmap layout used by slot 11 by tracing FUN_00403c60's
`[ESI+0x4]` surface read against `CBitmap.surface`.

- Retypes `TileBitmapSet::bitmap_ptrs` from `*const u32` to
`*const *mut CBitmap`.
- Slot 22 now calls `blit_bitmap_clipped_native` directly.
- Deletes the `blit_bitmap_clipped` Rust wrapper and the
`blit_bitmap_clipped_bridge` naked-asm trampoline (~50 lines).
- Keeps `va::DISPLAY_GFX_BLIT_BITMAP_CLIPPED` because the slot-19
bitmap-sprite fallback path (`wa_blit_bitmap_clipped`) still
bridges to WA — that gets cleaned up by follow-up plan item 4.

Verification: cargo build clean, 15/15 headless replay tests pass,
headful napalm replay confirmed by user (no visual changes).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

crates/openwa-core/src/render/display/gfx.rs CHANGED
@@ -1,6 +1,7 @@
1
  use super::base::DisplayBase;
2
  use super::vtable::DisplayGfxVtable;
3
  use crate::bitgrid::DisplayBitGrid;
 
4
  use core::ffi::c_void;
5
 
6
  /// DisplayGfx — full display/graphics subsystem (derived from DisplayBase).
@@ -212,10 +213,15 @@ impl DisplayGfx {
212
  ///
213
  /// Pointed to by `DisplayGfx::tile_bitmap_sets`. Used by draw_tiled_terrain (slot 22)
214
  /// to iterate over bitmap tiles in a grid pattern.
 
 
 
 
 
215
  #[repr(C)]
216
  pub struct TileBitmapSet {
217
  /// 0x00: Bitmap count or total size (observed values: 0x168 = 360)
218
  pub count: u32,
219
- /// 0x04: Pointer to array of bitmap pointers (one per grid tile, row-major order)
220
- pub bitmap_ptrs: *const u32,
221
  }
 
1
  use super::base::DisplayBase;
2
  use super::vtable::DisplayGfxVtable;
3
  use crate::bitgrid::DisplayBitGrid;
4
+ use crate::render::sprite::sprite::CBitmap;
5
  use core::ffi::c_void;
6
 
7
  /// DisplayGfx — full display/graphics subsystem (derived from DisplayBase).
 
213
  ///
214
  /// Pointed to by `DisplayGfx::tile_bitmap_sets`. Used by draw_tiled_terrain (slot 22)
215
  /// to iterate over bitmap tiles in a grid pattern.
216
+ ///
217
+ /// Each `bitmap_ptrs[i]` is a [`CBitmap`] — the same struct slot 11's
218
+ /// `bitmap_vec` uses. WA's BlitBitmapClipped (`0x56A700`) and the inner
219
+ /// FUN_00403c60 read `+0x4` (the `surface` field) on every entry; the
220
+ /// vtable+surface+pad layout matches.
221
  #[repr(C)]
222
  pub struct TileBitmapSet {
223
  /// 0x00: Bitmap count or total size (observed values: 0x168 = 360)
224
  pub count: u32,
225
+ /// 0x04: Pointer to array of CBitmap pointers (one per grid tile, row-major).
226
+ pub bitmap_ptrs: *const *mut CBitmap,
227
  }
crates/openwa-core/src/render/display/vtable.rs CHANGED
@@ -1126,11 +1126,11 @@ pub unsafe extern "thiscall" fn draw_tiled_terrain(
1126
 
1127
  let bitmap_ptr = *bitmap_array.add(bitmap_idx as usize);
1128
 
1129
- blit_bitmap_clipped(
1130
  this,
1131
- col_w,
1132
  pixel_x + x_offset,
1133
  pixel_y + y_offset,
 
1134
  row_h,
1135
  bitmap_ptr,
1136
  blit_flags,
@@ -1144,59 +1144,6 @@ pub unsafe extern "thiscall" fn draw_tiled_terrain(
1144
  }
1145
  }
1146
 
1147
- /// Bridge to DisplayGfx__BlitBitmapClipped (0x56A700).
1148
- ///
1149
- /// Usercall: EAX=this (DisplayGfx*), EDX=col_width, 5 stack params (dst_x, dst_y,
1150
- /// row_height, bitmap_ptr, flags), RET 0x14.
1151
- ///
1152
- /// Clips the bitmap rectangle against the display clip rect, calls flush_render_lock,
1153
- /// then delegates to the low-level blit function (0x403C60).
1154
- unsafe fn blit_bitmap_clipped(
1155
- gfx: *mut DisplayGfx,
1156
- col_width: i32,
1157
- dst_x: i32,
1158
- dst_y: i32,
1159
- row_height: i32,
1160
- bitmap_ptr: u32,
1161
- flags: u32,
1162
- ) {
1163
- blit_bitmap_clipped_bridge(
1164
- gfx as u32,
1165
- col_width as u32,
1166
- dst_x as u32,
1167
- dst_y as u32,
1168
- row_height as u32,
1169
- bitmap_ptr,
1170
- flags,
1171
- rb(va::DISPLAY_GFX_BLIT_BITMAP_CLIPPED),
1172
- );
1173
- }
1174
-
1175
- /// Naked bridge: sets EAX=this, EDX=col_width, pushes 5 stack params, calls target.
1176
- #[unsafe(naked)]
1177
- unsafe extern "cdecl" fn blit_bitmap_clipped_bridge(
1178
- _this: u32,
1179
- _col_width: u32,
1180
- _dst_x: u32,
1181
- _dst_y: u32,
1182
- _row_height: u32,
1183
- _bitmap_ptr: u32,
1184
- _flags: u32,
1185
- _target: u32,
1186
- ) {
1187
- core::arch::naked_asm!(
1188
- "mov eax, [esp + 4]", // EAX = this
1189
- "mov edx, [esp + 8]", // EDX = col_width
1190
- "push [esp + 28]", // flags
1191
- "push [esp + 28]", // bitmap_ptr (shifted by our push)
1192
- "push [esp + 28]", // row_height
1193
- "push [esp + 28]", // dst_y
1194
- "push [esp + 28]", // dst_x
1195
- "call [esp + 52]", // target (offset: 5 pushes × 4 + 32 original)
1196
- "ret",
1197
- );
1198
- }
1199
-
1200
  /// Port of DisplayGfx::DrawPixelStrip (vtable slot 15, 0x56BE10).
1201
  ///
1202
  /// Draws `count + 1` pixels starting at (x, y), stepping by (dx, dy) each
@@ -2764,12 +2711,11 @@ unsafe fn cbitmap_blit_via_wrapper(
2764
  /// render lock if held, and dispatches the actual blit through
2765
  /// `cbitmap_blit_via_wrapper`.
2766
  ///
2767
- /// The original WA function still exists and is called by other slots
2768
- /// (`DrawTiledTerrain`, `BlitBitmapTiled_Maybe`); we don't replace it
2769
- /// in-place. This Rust version is only used by our ported slot 11.
2770
- /// (The earlier `blit_bitmap_clipped` in this file is the asm bridge to
2771
- /// the original WA function used by slot 22 — kept until it has a typed
2772
- /// caller to validate against.)
2773
  ///
2774
  /// # Safety
2775
  /// `this` must be a valid `*mut DisplayGfx`. `surface` must be a valid
 
1126
 
1127
  let bitmap_ptr = *bitmap_array.add(bitmap_idx as usize);
1128
 
1129
+ blit_bitmap_clipped_native(
1130
  this,
 
1131
  pixel_x + x_offset,
1132
  pixel_y + y_offset,
1133
+ col_w,
1134
  row_h,
1135
  bitmap_ptr,
1136
  blit_flags,
 
1144
  }
1145
  }
1146
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1147
  /// Port of DisplayGfx::DrawPixelStrip (vtable slot 15, 0x56BE10).
1148
  ///
1149
  /// Draws `count + 1` pixels starting at (x, y), stepping by (dx, dy) each
 
2711
  /// render lock if held, and dispatches the actual blit through
2712
  /// `cbitmap_blit_via_wrapper`.
2713
  ///
2714
+ /// Used by ported slots 11 (`DrawTiledBitmap`) and 22 (`DrawTiledTerrain`).
2715
+ /// The original WA function at `0x56A700` still exists and is called by
2716
+ /// the slot-19 bitmap-sprite fallback path (`wa_blit_bitmap_clipped`) —
2717
+ /// see follow-up plan item 4. The address constant
2718
+ /// `DISPLAY_GFX_BLIT_BITMAP_CLIPPED` is kept until that path is ported.
 
2719
  ///
2720
  /// # Safety
2721
  /// `this` must be a valid `*mut DisplayGfx`. `surface` must be a valid