Skip to content

Commit

Permalink
new bitblt-like primitive for more correct blts
Browse files Browse the repository at this point in the history
Because it uses a callback this ought to accommodate stretching as well.
  • Loading branch information
evmar committed Oct 7, 2024
1 parent 4c8798e commit af32dd4
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 126 deletions.
9 changes: 9 additions & 0 deletions win32/src/winapi/bitmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,15 @@ pub struct BitmapRGBA32 {
}

impl BitmapRGBA32 {
pub fn to_rect(&self) -> RECT {
RECT {
left: 0,
right: self.width as i32,
top: 0,
bottom: self.height as i32,
}
}

pub fn pixels_slice<'a>(&'a self, mem: Mem<'a>) -> &'a [[u8; 4]] {
self.pixels.as_slice(mem)
}
Expand Down
12 changes: 6 additions & 6 deletions win32/src/winapi/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2811,8 +2811,8 @@ pub mod gdi32 {
let hdc = <HDC>::from_stack(mem, stack_args + 0u32);
let x = <i32>::from_stack(mem, stack_args + 4u32);
let y = <i32>::from_stack(mem, stack_args + 8u32);
let cx = <u32>::from_stack(mem, stack_args + 12u32);
let cy = <u32>::from_stack(mem, stack_args + 16u32);
let cx = <i32>::from_stack(mem, stack_args + 12u32);
let cy = <i32>::from_stack(mem, stack_args + 16u32);
let hdcSrc = <HDC>::from_stack(mem, stack_args + 20u32);
let x1 = <i32>::from_stack(mem, stack_args + 24u32);
let y1 = <i32>::from_stack(mem, stack_args + 28u32);
Expand Down Expand Up @@ -3825,13 +3825,13 @@ pub mod gdi32 {
let hdcDest = <HDC>::from_stack(mem, stack_args + 0u32);
let xDest = <i32>::from_stack(mem, stack_args + 4u32);
let yDest = <i32>::from_stack(mem, stack_args + 8u32);
let wDest = <u32>::from_stack(mem, stack_args + 12u32);
let hDest = <u32>::from_stack(mem, stack_args + 16u32);
let wDest = <i32>::from_stack(mem, stack_args + 12u32);
let hDest = <i32>::from_stack(mem, stack_args + 16u32);
let hdcSrc = <HDC>::from_stack(mem, stack_args + 20u32);
let xSrc = <i32>::from_stack(mem, stack_args + 24u32);
let ySrc = <i32>::from_stack(mem, stack_args + 28u32);
let wSrc = <u32>::from_stack(mem, stack_args + 32u32);
let hSrc = <u32>::from_stack(mem, stack_args + 36u32);
let wSrc = <i32>::from_stack(mem, stack_args + 32u32);
let hSrc = <i32>::from_stack(mem, stack_args + 36u32);
let rop = <Result<RasterOp, u32>>::from_stack(mem, stack_args + 40u32);
let __trace_context = if crate::trace::enabled("gdi32/bitmap") {
Some(crate::trace::trace_begin(
Expand Down
194 changes: 75 additions & 119 deletions win32/src/winapi/gdi32/bitmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{
winapi::{
bitmap::{BitmapMono, BitmapRGBA32, PixelData, BI},
kernel32,
types::{POINT, RECT},
},
};
use std::cmp::min;
Expand All @@ -20,8 +21,21 @@ pub struct BITMAP {
}
unsafe impl memory::Pod for BITMAP {}

/// For the subset of dst within r, write pixels from op(x, y).
/// Performs no clipping; caller must have already clipped.
fn fill_pixels(dst: &mut BitmapRGBA32, r: &RECT, mut op: impl FnMut(i32, i32) -> [u8; 4]) {
let pixels = dst.pixels.as_slice_mut();
let stride = dst.width as i32;
for y in r.top..r.bottom {
for x in r.left..r.right {
pixels[(y * stride + x) as usize] = op(x, y);
}
}
}

/// Copy pixels from src to dst. Asserts that everything has been appropriately clipped.
/// flush_alpha is true when the output drops alpha channel (e.g. Window backing store).
/// TODO: use fill_pixels instead, delete this.
fn bit_blt(
dst: &mut [[u8; 4]],
mut dx: isize,
Expand Down Expand Up @@ -92,45 +106,6 @@ fn bit_blt(
}
}

fn pat_blt(
dst: &mut [[u8; 4]],
mut x: isize,
mut y: isize,
stride: usize,
mut w: isize,
mut h: isize,
color: [u8; 4],
rop: RasterOp,
) {
if x < 0 {
w += x;
x = 0;
}
if y < 0 {
h += y;
y = 0;
}
if w <= 0 || h <= 0 {
return;
}
for row in 0..h {
let dst_off = ((y + row) * stride as isize) + x;
if dst_off + w > dst.len() as isize {
break;
}
let dst_row = &mut dst[dst_off as usize..][..w as usize];
match rop {
RasterOp::PATCOPY => {
dst_row.fill(color);
}
RasterOp::BLACKNESS => {
dst_row.fill([0, 0, 0, 0xFF]);
}
_ => todo!("unimplemented PatBlt with rop={rop:?}"),
}
}
}

#[derive(Debug, win32_derive::TryFromEnum, PartialEq, Eq)]
pub enum RasterOp {
SRCCOPY = 0xcc0020,
Expand All @@ -146,8 +121,8 @@ pub fn BitBlt(
hdc: HDC,
x: i32,
y: i32,
cx: u32,
cy: u32,
cx: i32,
cy: i32,
hdcSrc: HDC,
x1: i32,
y1: i32,
Expand All @@ -156,15 +131,7 @@ pub fn BitBlt(
let rop = rop.unwrap();
if rop == RasterOp::BLACKNESS {
// It seems like passing null as `hdcSrc` when using BLACKNESS is supported on Windows.
return PatBlt(
machine,
hdc,
x,
y,
cx as i32,
cy as i32,
Ok(RasterOp::BLACKNESS),
);
return PatBlt(machine, hdc, x, y, cx, cy, Ok(RasterOp::BLACKNESS));
}

let src_dc = machine.state.gdi32.dcs.get(hdcSrc).unwrap();
Expand All @@ -185,55 +152,46 @@ pub fn BitBlt(
let src = src_bitmap.pixels_slice(machine.emu.memory.mem());

let dst_dc = machine.state.gdi32.dcs.get(hdc).unwrap();

let copy_rect = RECT {
left: 0,
top: 0,
right: cx,
bottom: cy,
}; // 0-based rect of copy region
let dst_rect = copy_rect.add(POINT { x, y }); // destination rect
let src_rect = copy_rect
.add(POINT { x: x1, y: y1 })
.clip(&src_bitmap.to_rect()); // copy source rect, in source bitmap coordinates
let copy_rect = dst_rect.clip(&src_rect.add(POINT {
x: x - x1,
y: y - y1,
})); // clipped copy region

match dst_dc.target {
DCTarget::Memory(obj) => {
let dst = match machine.state.gdi32.objects.get_mut(obj).unwrap() {
Object::Bitmap(BitmapType::RGBA32(bmp)) => bmp,
_ => unimplemented!("{:?}", obj),
};

bit_blt(
dst.pixels.as_slice_mut(),
x as isize,
y as isize,
dst.width as usize,
cx as isize,
cy as isize,
src,
x1 as isize,
y1 as isize,
src_bitmap.width as usize,
true,
rop,
);
fill_pixels(dst, &copy_rect.clip(&dst.to_rect()), |dx, dy| {
let x = x1 + dx - x;
let y = y1 + dy - y;
src[(y * src_bitmap.width as i32 + x) as usize]
});
}
DCTarget::Window(hwnd) => {
let window = machine.state.user32.windows.get_mut(hwnd).unwrap();
let dst = window.bitmap_mut();

// Clip to src/dst regions.
if x >= dst.width as i32
|| x1 >= src_bitmap.width as i32
|| y >= dst.height as i32
|| y1 >= src_bitmap.height as i32
{
return true;
}

bit_blt(
dst.pixels.as_slice_mut(),
x as isize,
y as isize,
dst.width as usize,
cx as isize,
cy as isize,
src,
x1 as isize,
y1 as isize,
src_bitmap.width as usize,
true,
rop,
);
fill_pixels(dst, &copy_rect.clip(&dst.to_rect()), |dx, dy| {
let x = x1 + dx - x;
let y = y1 + dy - y;
let mut px = src[(y * src_bitmap.width as i32 + x) as usize];
px[3] = 0xFF; // clear alpha
px
});

window
.expect_toplevel_mut()
Expand All @@ -243,7 +201,7 @@ pub fn BitBlt(
let surface = machine.state.ddraw.surfaces.get_mut(&ptr).unwrap();

assert!(x == 0 && y == 0 && x1 == 0 && y1 == 0);
assert!(cx == surface.width && cy == surface.height);
assert!(cx as u32 == surface.width && cy as u32 == surface.height);
assert!(surface.width == src_bitmap.width && surface.height == src_bitmap.height);

surface.host.write_pixels(src);
Expand All @@ -258,13 +216,13 @@ pub fn StretchBlt(
hdcDest: HDC,
xDest: i32,
yDest: i32,
wDest: u32,
hDest: u32,
wDest: i32,
hDest: i32,
hdcSrc: HDC,
xSrc: i32,
ySrc: i32,
wSrc: u32,
hSrc: u32,
wSrc: i32,
hSrc: i32,
rop: Result<RasterOp, u32>,
) -> bool {
if wDest != wSrc || hDest != hSrc {
Expand Down Expand Up @@ -292,13 +250,27 @@ pub fn PatBlt(
};

const DEFAULT_COLOR: [u8; 4] = [255, 255, 255, 255];
// get brush color
let color = match machine.state.gdi32.objects.get(dc.brush) {
Some(Object::Brush(brush)) => match brush.color {
Some(color) => color.to_pixel(),
None => DEFAULT_COLOR,
},
_ => DEFAULT_COLOR,

let color = match rop {
RasterOp::PATCOPY => {
// get brush color
match machine.state.gdi32.objects.get(dc.brush) {
Some(Object::Brush(brush)) => match brush.color {
Some(color) => color.to_pixel(),
None => DEFAULT_COLOR,
},
_ => DEFAULT_COLOR,
}
}
RasterOp::BLACKNESS => [0, 0, 0, 0xFF],
_ => todo!("unimplemented PatBlt with rop={rop:?}"),
};

let dst_rect = RECT {
left: x,
top: y,
right: x + w,
bottom: y + h,
};

match dc.target {
Expand All @@ -307,16 +279,8 @@ pub fn PatBlt(
Object::Bitmap(BitmapType::RGBA32(bmp)) => bmp,
_ => unimplemented!(),
};
pat_blt(
bitmap.pixels.as_slice_mut(),
x as isize,
y as isize,
bitmap.width as usize,
w as isize,
h as isize,
color,
rop,
);

fill_pixels(bitmap, &dst_rect.clip(&bitmap.to_rect()), |_, _| color);
}
DCTarget::Window(hwnd) => {
if hwnd.to_raw() != 1 {
Expand All @@ -325,16 +289,8 @@ pub fn PatBlt(
}
let window = machine.state.user32.windows.get_mut(hwnd).unwrap();
let bitmap = window.bitmap_mut();
pat_blt(
bitmap.pixels.as_slice_mut(),
x as isize,
y as isize,
bitmap.width as usize,
w as isize,
h as isize,
color,
rop,
);
fill_pixels(bitmap, &dst_rect.clip(&bitmap.to_rect()), |_, _| color);

window
.expect_toplevel_mut()
.flush_pixels(machine.emu.memory.mem());
Expand Down
26 changes: 25 additions & 1 deletion win32/src/winapi/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub struct HWNDT;
pub type HWND = HANDLE<HWNDT>;

#[repr(C, packed)]
#[derive(Debug, Default)]
#[derive(Debug, Default, Eq, PartialEq)]
pub struct RECT {
pub left: i32,
pub top: i32,
Expand All @@ -39,6 +39,30 @@ pub struct RECT {
}
unsafe impl memory::Pod for RECT {}

impl RECT {
pub fn clip(&self, other: &RECT) -> RECT {
RECT {
left: self.left.max(other.left),
top: self.top.max(other.top),
right: self.right.min(other.right),
bottom: self.bottom.min(other.bottom),
}
}

pub fn contains(&self, point: POINT) -> bool {
point.x >= self.left && point.x < self.right && point.y >= self.top && point.y < self.bottom
}

pub fn add(&self, delta: POINT) -> RECT {
RECT {
left: self.left + delta.x,
top: self.top + delta.y,
right: self.right + delta.x,
bottom: self.bottom + delta.y,
}
}
}

#[repr(C, packed)]
#[derive(Copy, Clone, Debug, Default)]
pub struct POINT {
Expand Down

0 comments on commit af32dd4

Please sign in to comment.