feat: port DDDisplay set_camera_offset, set_clip_rect, fill_rect to Rust
Browse filesThree new DDDisplay vtable methods ported plus a shared
flush_render_lock helper. All use the typed DDDisplayWrapper
fastcall dispatch for rendering operations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-core/src/display/dd_display.rs
CHANGED
|
@@ -325,3 +325,174 @@ pub unsafe extern "thiscall" fn flush_render(this: *mut DDDisplay) {
|
|
| 325 |
let mut buf = FastcallResult::default();
|
| 326 |
DDDisplayWrapper::get_renderer_surface_raw(wrapper, &mut buf);
|
| 327 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 325 |
let mut buf = FastcallResult::default();
|
| 326 |
DDDisplayWrapper::get_renderer_surface_raw(wrapper, &mut buf);
|
| 327 |
}
|
| 328 |
+
|
| 329 |
+
/// Port of DDDisplay::SetCameraOffset (vtable slot 27, 0x56CC40).
|
| 330 |
+
///
|
| 331 |
+
/// Converts Fixed-point camera coordinates to pixel integers and stores them.
|
| 332 |
+
///
|
| 333 |
+
/// # Safety
|
| 334 |
+
/// `this` must be a valid `*mut DDDisplay` (actually a `*mut DisplayGfx`).
|
| 335 |
+
pub unsafe extern "thiscall" fn set_camera_offset(this: *mut DDDisplay, x: Fixed, y: Fixed) {
|
| 336 |
+
let gfx = this as *mut DisplayGfx;
|
| 337 |
+
(*gfx).camera_x = x.to_int();
|
| 338 |
+
(*gfx).camera_y = y.to_int();
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
/// Port of DDDisplay::SetClipRect (vtable slot 28, 0x56CC60).
|
| 342 |
+
///
|
| 343 |
+
/// Converts Fixed-point clip rectangle to pixel integers, clamps to display
|
| 344 |
+
/// dimensions, stores in DisplayBase, and mirrors to the layer_0 object.
|
| 345 |
+
///
|
| 346 |
+
/// # Safety
|
| 347 |
+
/// `this` must be a valid `*mut DDDisplay` (actually a `*mut DisplayGfx`).
|
| 348 |
+
/// `layer_0` (at +0x3D9C) must be initialized.
|
| 349 |
+
pub unsafe extern "thiscall" fn set_clip_rect(
|
| 350 |
+
this: *mut DDDisplay,
|
| 351 |
+
x1: Fixed,
|
| 352 |
+
y1: Fixed,
|
| 353 |
+
x2: Fixed,
|
| 354 |
+
y2: Fixed,
|
| 355 |
+
) {
|
| 356 |
+
let gfx = this as *mut DisplayGfx;
|
| 357 |
+
let base = &mut (*gfx).base;
|
| 358 |
+
|
| 359 |
+
// Convert Fixed to pixel integers
|
| 360 |
+
let mut cx1 = x1.to_int();
|
| 361 |
+
let mut cy1 = y1.to_int();
|
| 362 |
+
let mut cx2 = x2.to_int();
|
| 363 |
+
let mut cy2 = y2.to_int();
|
| 364 |
+
|
| 365 |
+
// Store and clamp to display dimensions
|
| 366 |
+
base.clip_x1 = cx1;
|
| 367 |
+
base.clip_y1 = cy1;
|
| 368 |
+
base.clip_x2 = cx2;
|
| 369 |
+
base.clip_y2 = cy2;
|
| 370 |
+
|
| 371 |
+
if cx1 < 0 {
|
| 372 |
+
base.clip_x1 = 0;
|
| 373 |
+
cx1 = 0;
|
| 374 |
+
}
|
| 375 |
+
if cy1 < 0 {
|
| 376 |
+
base.clip_y1 = 0;
|
| 377 |
+
cy1 = 0;
|
| 378 |
+
}
|
| 379 |
+
if cx2 > base.display_width as i32 {
|
| 380 |
+
base.clip_x2 = base.display_width as i32;
|
| 381 |
+
cx2 = base.display_width as i32;
|
| 382 |
+
}
|
| 383 |
+
if cy2 > base.display_height as i32 {
|
| 384 |
+
base.clip_y2 = base.display_height as i32;
|
| 385 |
+
cy2 = base.display_height as i32;
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
// Mirror clip rect to layer_0 object (0x4C-byte layer context).
|
| 389 |
+
// Layout: +0x14 = width, +0x18 = height, +0x1C-0x28 = clip rect.
|
| 390 |
+
let layer = (*gfx).layer_0 as *mut u8;
|
| 391 |
+
let layer_w = *(layer.add(0x14) as *const i32);
|
| 392 |
+
let layer_h = *(layer.add(0x18) as *const i32);
|
| 393 |
+
|
| 394 |
+
*(layer.add(0x1C) as *mut i32) = cx1;
|
| 395 |
+
*(layer.add(0x20) as *mut i32) = cy1;
|
| 396 |
+
*(layer.add(0x24) as *mut i32) = cx2;
|
| 397 |
+
*(layer.add(0x28) as *mut i32) = cy2;
|
| 398 |
+
|
| 399 |
+
if cx1 < 0 {
|
| 400 |
+
*(layer.add(0x1C) as *mut i32) = 0;
|
| 401 |
+
}
|
| 402 |
+
if cy1 < 0 {
|
| 403 |
+
*(layer.add(0x20) as *mut i32) = 0;
|
| 404 |
+
}
|
| 405 |
+
if cx2 > layer_w {
|
| 406 |
+
*(layer.add(0x24) as *mut i32) = layer_w;
|
| 407 |
+
}
|
| 408 |
+
if cy2 > layer_h {
|
| 409 |
+
*(layer.add(0x28) as *mut i32) = layer_h;
|
| 410 |
+
}
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
/// Port of the render-lock flush helper at 0x56A330.
|
| 414 |
+
///
|
| 415 |
+
/// If the render lock is held, releases it by calling
|
| 416 |
+
/// DDDisplayWrapper::unlock_surface_write (slot 18), then clears the flag.
|
| 417 |
+
/// Called by drawing methods (fill_rect, draw_line, etc.) before rendering.
|
| 418 |
+
///
|
| 419 |
+
/// Original uses ESI = this (usercall).
|
| 420 |
+
///
|
| 421 |
+
/// # Safety
|
| 422 |
+
/// `gfx` must be a valid `*mut DisplayGfx` with `layer_0` initialized.
|
| 423 |
+
/// `g_DDDisplayWrapper` must be initialized.
|
| 424 |
+
unsafe fn flush_render_lock(gfx: *mut DisplayGfx) {
|
| 425 |
+
if (*gfx).render_lock != 0 {
|
| 426 |
+
let wrapper = *(rb(va::G_DD_DISPLAY_WRAPPER) as *const *mut DDDisplayWrapper);
|
| 427 |
+
let layer = (*gfx).layer_0 as *mut u8;
|
| 428 |
+
let data = *(layer.add(0x08) as *const u32);
|
| 429 |
+
let mut buf = FastcallResult::default();
|
| 430 |
+
DDDisplayWrapper::unlock_surface_write_raw(wrapper, &mut buf, data);
|
| 431 |
+
(*gfx).render_lock = 0;
|
| 432 |
+
}
|
| 433 |
+
}
|
| 434 |
+
|
| 435 |
+
/// Port of DDDisplay::FillRect (vtable slot 18, 0x56B810).
|
| 436 |
+
///
|
| 437 |
+
/// Fills a rectangle with a solid color. Pixel-integer coordinates are
|
| 438 |
+
/// offset by the camera position and clipped to the clip rect before
|
| 439 |
+
/// dispatching to DDDisplayWrapper::fill_rect (slot 19).
|
| 440 |
+
///
|
| 441 |
+
/// # Safety
|
| 442 |
+
/// `this` must be a valid `*mut DDDisplay` (actually a `*mut DisplayGfx`).
|
| 443 |
+
/// `g_DDDisplayWrapper` must be initialized.
|
| 444 |
+
pub unsafe extern "thiscall" fn fill_rect(
|
| 445 |
+
this: *mut DDDisplay,
|
| 446 |
+
x1: i32,
|
| 447 |
+
y1: i32,
|
| 448 |
+
x2: i32,
|
| 449 |
+
y2: i32,
|
| 450 |
+
color: u32,
|
| 451 |
+
) {
|
| 452 |
+
let gfx = this as *mut DisplayGfx;
|
| 453 |
+
let base = &(*gfx).base;
|
| 454 |
+
|
| 455 |
+
// Apply camera offset
|
| 456 |
+
let mut left = x1 + (*gfx).camera_x;
|
| 457 |
+
let mut top = y1 + (*gfx).camera_y;
|
| 458 |
+
let mut right = x2 + (*gfx).camera_x;
|
| 459 |
+
let mut bottom = y2 + (*gfx).camera_y;
|
| 460 |
+
|
| 461 |
+
// Early-out: no intersection with clip rect
|
| 462 |
+
if right <= base.clip_x1
|
| 463 |
+
|| bottom <= base.clip_y1
|
| 464 |
+
|| left >= base.clip_x2
|
| 465 |
+
|| top >= base.clip_y2
|
| 466 |
+
{
|
| 467 |
+
return;
|
| 468 |
+
}
|
| 469 |
+
|
| 470 |
+
// Clamp to clip rect
|
| 471 |
+
if left < base.clip_x1 {
|
| 472 |
+
left = base.clip_x1;
|
| 473 |
+
}
|
| 474 |
+
if top < base.clip_y1 {
|
| 475 |
+
top = base.clip_y1;
|
| 476 |
+
}
|
| 477 |
+
if right > base.clip_x2 {
|
| 478 |
+
right = base.clip_x2;
|
| 479 |
+
}
|
| 480 |
+
if bottom > base.clip_y2 {
|
| 481 |
+
bottom = base.clip_y2;
|
| 482 |
+
}
|
| 483 |
+
|
| 484 |
+
flush_render_lock(gfx);
|
| 485 |
+
|
| 486 |
+
// DDDisplayWrapper::fill_rect takes (x, y, width, height, color)
|
| 487 |
+
let wrapper = *(rb(va::G_DD_DISPLAY_WRAPPER) as *const *mut DDDisplayWrapper);
|
| 488 |
+
let mut buf = FastcallResult::default();
|
| 489 |
+
DDDisplayWrapper::fill_rect_raw(
|
| 490 |
+
wrapper,
|
| 491 |
+
&mut buf,
|
| 492 |
+
left,
|
| 493 |
+
top,
|
| 494 |
+
right - left,
|
| 495 |
+
bottom - top,
|
| 496 |
+
color,
|
| 497 |
+
);
|
| 498 |
+
}
|
crates/openwa-dll/src/replacements/display.rs
CHANGED
|
@@ -81,10 +81,13 @@ pub fn install() -> Result<(), String> {
|
|
| 81 |
|
| 82 |
// Patch DDDisplay vtable (0x66A218): replace ported methods with Rust.
|
| 83 |
vtable_replace!(DDDisplayVtable, va::DD_DISPLAY_VTABLE, {
|
| 84 |
-
get_dimensions
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
| 86 |
})?;
|
| 87 |
-
let _ = log_line("[Display] DDDisplay: patched
|
| 88 |
}
|
| 89 |
|
| 90 |
Ok(())
|
|
|
|
| 81 |
|
| 82 |
// Patch DDDisplay vtable (0x66A218): replace ported methods with Rust.
|
| 83 |
vtable_replace!(DDDisplayVtable, va::DD_DISPLAY_VTABLE, {
|
| 84 |
+
get_dimensions => dd_display::get_dimensions,
|
| 85 |
+
fill_rect => dd_display::fill_rect,
|
| 86 |
+
flush_render => dd_display::flush_render,
|
| 87 |
+
set_camera_offset => dd_display::set_camera_offset,
|
| 88 |
+
set_clip_rect => dd_display::set_clip_rect,
|
| 89 |
})?;
|
| 90 |
+
let _ = log_line("[Display] DDDisplay: patched 5 methods → Rust");
|
| 91 |
}
|
| 92 |
|
| 93 |
Ok(())
|