softbuffer/src/win32.rs

270 lines
7.4 KiB
Rust
Raw Normal View History

2022-12-21 17:26:09 -08:00
//! Implementation of software buffering for Windows.
2022-12-22 10:09:47 -08:00
//!
2022-12-21 17:26:09 -08:00
//! This module converts the input buffer into a bitmap and then stretches it to the window.
use crate::{Rect, SoftBufferError};
use raw_window_handle::Win32WindowHandle;
2022-12-21 17:26:09 -08:00
use std::io;
2022-12-22 10:09:47 -08:00
use std::mem;
use std::num::{NonZeroI32, NonZeroU32};
use std::ptr::{self, NonNull};
use std::slice;
2022-12-20 08:08:46 -08:00
use windows_sys::Win32::Foundation::HWND;
use windows_sys::Win32::Graphics::Gdi;
const ZERO_QUAD: Gdi::RGBQUAD = Gdi::RGBQUAD {
rgbBlue: 0,
rgbGreen: 0,
rgbRed: 0,
rgbReserved: 0,
2022-12-20 08:08:46 -08:00
};
struct Buffer {
dc: Gdi::HDC,
bitmap: Gdi::HBITMAP,
pixels: NonNull<u32>,
width: NonZeroI32,
height: NonZeroI32,
presented: bool,
}
impl Drop for Buffer {
fn drop(&mut self) {
unsafe {
Gdi::DeleteDC(self.dc);
Gdi::DeleteObject(self.bitmap);
}
}
}
impl Buffer {
fn new(window_dc: Gdi::HDC, width: NonZeroI32, height: NonZeroI32) -> Self {
let dc = unsafe { Gdi::CreateCompatibleDC(window_dc) };
assert!(dc != 0);
// Create a new bitmap info struct.
let bitmap_info = BitmapInfo {
bmi_header: Gdi::BITMAPINFOHEADER {
biSize: mem::size_of::<Gdi::BITMAPINFOHEADER>() as u32,
biWidth: width.get(),
biHeight: -height.get(),
biPlanes: 1,
biBitCount: 32,
2023-04-06 08:05:18 -07:00
biCompression: Gdi::BI_BITFIELDS as u32,
biSizeImage: 0,
biXPelsPerMeter: 0,
biYPelsPerMeter: 0,
biClrUsed: 0,
biClrImportant: 0,
},
bmi_colors: [
Gdi::RGBQUAD {
rgbRed: 0xff,
..ZERO_QUAD
},
Gdi::RGBQUAD {
rgbGreen: 0xff,
..ZERO_QUAD
},
Gdi::RGBQUAD {
rgbBlue: 0xff,
..ZERO_QUAD
},
],
};
// XXX alignment?
// XXX better to use CreateFileMapping, and pass hSection?
// XXX test return value?
let mut pixels: *mut u32 = ptr::null_mut();
let bitmap = unsafe {
Gdi::CreateDIBSection(
dc,
&bitmap_info as *const BitmapInfo as *const _,
Gdi::DIB_RGB_COLORS,
&mut pixels as *mut *mut u32 as _,
0,
0,
)
};
assert!(bitmap != 0);
let pixels = NonNull::new(pixels).unwrap();
unsafe {
Gdi::SelectObject(dc, bitmap);
}
Self {
dc,
bitmap,
width,
height,
pixels,
presented: false,
}
}
#[inline]
fn pixels(&self) -> &[u32] {
unsafe {
slice::from_raw_parts(
self.pixels.as_ptr(),
i32::from(self.width) as usize * i32::from(self.height) as usize,
)
}
}
#[inline]
fn pixels_mut(&mut self) -> &mut [u32] {
unsafe {
slice::from_raw_parts_mut(
self.pixels.as_ptr(),
i32::from(self.width) as usize * i32::from(self.height) as usize,
)
}
}
}
2022-12-21 17:26:09 -08:00
/// The handle to a window for software buffering.
2022-01-16 08:59:29 -06:00
pub struct Win32Impl {
2022-12-21 17:26:09 -08:00
/// The window handle.
window: HWND,
2022-12-21 17:26:09 -08:00
/// The device context for the window.
dc: Gdi::HDC,
/// The buffer used to hold the image.
buffer: Option<Buffer>,
}
2022-12-21 17:26:09 -08:00
/// The Win32-compatible bitmap information.
#[repr(C)]
struct BitmapInfo {
bmi_header: Gdi::BITMAPINFOHEADER,
bmi_colors: [Gdi::RGBQUAD; 3],
}
impl Win32Impl {
2022-12-21 17:26:09 -08:00
/// Create a new `Win32Impl` from a `Win32WindowHandle`.
2022-12-22 10:09:47 -08:00
///
2022-12-21 17:26:09 -08:00
/// # Safety
2022-12-22 10:09:47 -08:00
///
2022-12-21 17:26:09 -08:00
/// The `Win32WindowHandle` must be a valid window handle.
pub unsafe fn new(handle: &Win32WindowHandle) -> Result<Self, crate::SoftBufferError> {
2022-12-21 17:26:09 -08:00
// It is valid for the window handle to be null here. Error out if it is.
if handle.hwnd.is_null() {
return Err(SoftBufferError::IncompleteWindowHandle);
2022-12-21 17:26:09 -08:00
}
// Get the handle to the device context.
// SAFETY: We have confirmed that the window handle is valid.
let hwnd = handle.hwnd as HWND;
let dc = unsafe { Gdi::GetDC(hwnd) };
2022-01-16 08:59:29 -06:00
2022-12-21 17:26:09 -08:00
// GetDC returns null if there is a platform error.
2022-12-20 08:08:46 -08:00
if dc == 0 {
return Err(SoftBufferError::PlatformError(
2022-12-21 17:26:09 -08:00
Some("Device Context is null".into()),
Some(Box::new(io::Error::last_os_error())),
));
}
Ok(Self {
dc,
window: hwnd,
buffer: None,
})
2022-01-16 08:59:29 -06:00
}
pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {
let (width, height) = (|| {
let width = NonZeroI32::new(i32::try_from(width.get()).ok()?)?;
let height = NonZeroI32::new(i32::try_from(height.get()).ok()?)?;
Some((width, height))
})()
.ok_or(SoftBufferError::SizeOutOfRange { width, height })?;
if let Some(buffer) = self.buffer.as_ref() {
if buffer.width == width && buffer.height == height {
return Ok(());
}
}
self.buffer = Some(Buffer::new(self.dc, width, height));
Ok(())
}
pub fn buffer_mut(&mut self) -> Result<BufferImpl, SoftBufferError> {
if self.buffer.is_none() {
panic!("Must set size of surface before calling `buffer_mut()`");
}
Ok(BufferImpl(self))
}
fn present_with_damage(&mut self, damage: &[Rect]) -> Result<(), SoftBufferError> {
let buffer = self.buffer.as_mut().unwrap();
unsafe {
for rect in damage.iter().copied() {
let (x, y, width, height) = (|| {
Some((
i32::try_from(rect.x).ok()?,
i32::try_from(rect.y).ok()?,
i32::try_from(rect.width.get()).ok()?,
i32::try_from(rect.height.get()).ok()?,
))
})()
.ok_or(SoftBufferError::DamageOutOfRange { rect })?;
Gdi::BitBlt(self.dc, x, y, width, height, buffer.dc, x, y, Gdi::SRCCOPY);
}
// Validate the window.
Gdi::ValidateRect(self.window, ptr::null_mut());
}
buffer.presented = true;
Ok(())
}
}
pub struct BufferImpl<'a>(&'a mut Win32Impl);
impl<'a> BufferImpl<'a> {
#[inline]
pub fn pixels(&self) -> &[u32] {
self.0.buffer.as_ref().unwrap().pixels()
}
#[inline]
pub fn pixels_mut(&mut self) -> &mut [u32] {
self.0.buffer.as_mut().unwrap().pixels_mut()
}
pub fn age(&self) -> u8 {
match self.0.buffer.as_ref() {
Some(buffer) if buffer.presented => 1,
_ => 0,
}
}
pub fn present(self) -> Result<(), SoftBufferError> {
let imp = self.0;
let buffer = imp.buffer.as_ref().unwrap();
imp.present_with_damage(&[Rect {
x: 0,
y: 0,
// We know width/height will be non-negative
width: buffer.width.try_into().unwrap(),
height: buffer.height.try_into().unwrap(),
}])
}
pub fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> {
let imp = self.0;
imp.present_with_damage(damage)
}
2022-01-16 08:59:29 -06:00
}