From 1c34cbdb2be208c50643b1d99fe48e37ecdd83bf Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 19 Jan 2026 07:37:30 +0100 Subject: [PATCH 1/8] Remove DamageOutOfRange error --- CHANGELOG.md | 1 + src/backends/kms.rs | 21 ++++++++++----------- src/backends/wayland/mod.rs | 14 +++++--------- src/backends/win32.rs | 14 +++++--------- src/backends/x11.rs | 19 +++++++------------ src/error.rs | 11 ----------- 6 files changed, 28 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59a21cf3..079ca3f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Added `Buffer::pixels_iter()` for iterating over each pixel with its associated `x`/`y` coordinate. - **Breaking:** Removed generic type parameters `D` and `W` from `Buffer<'_>` struct. - **Breaking:** Removed `Deref[Mut]` implementation on `Buffer<'_>`. Use `Buffer::pixels()` instead. +- **Breaking:** Removed `DamageOutOfRange` error case. If the damage value is greater than the backend supports, it is instead clamped to an appropriate value. # 0.4.7 diff --git a/src/backends/kms.rs b/src/backends/kms.rs index a3a982d1..8dd5f55d 100644 --- a/src/backends/kms.rs +++ b/src/backends/kms.rs @@ -325,24 +325,23 @@ impl BufferInterface for BufferImpl<'_> { #[inline] fn present_with_damage(self, damage: &[crate::Rect]) -> Result<(), SoftBufferError> { - let rectangles = damage + let rectangles: Vec<_> = damage .iter() - .map(|&rect| { - let err = || SoftBufferError::DamageOutOfRange { rect }; - Ok::<_, SoftBufferError>(ClipRect::new( - rect.x.try_into().map_err(|_| err())?, - rect.y.try_into().map_err(|_| err())?, + .map(|rect| { + ClipRect::new( + rect.x.try_into().unwrap_or(u16::MAX), + rect.y.try_into().unwrap_or(u16::MAX), rect.x .checked_add(rect.width.get()) .and_then(|x| x.try_into().ok()) - .ok_or_else(err)?, + .unwrap_or(u16::MAX), rect.y .checked_add(rect.height.get()) - .and_then(|y| y.try_into().ok()) - .ok_or_else(err)?, - )) + .and_then(|x| x.try_into().ok()) + .unwrap_or(u16::MAX), + ) }) - .collect::, _>>()?; + .collect(); // Dirty the framebuffer with out damage rectangles. // diff --git a/src/backends/wayland/mod.rs b/src/backends/wayland/mod.rs index 77b7b876..f47e7715 100644 --- a/src/backends/wayland/mod.rs +++ b/src/backends/wayland/mod.rs @@ -260,16 +260,12 @@ impl BufferInterface for BufferImpl<'_> { self.surface.damage(0, 0, i32::MAX, i32::MAX); } else { for rect in damage { + let x = rect.x.try_into().unwrap_or(i32::MAX); + let y = rect.y.try_into().unwrap_or(i32::MAX); + let width = rect.width.get().try_into().unwrap_or(i32::MAX); + let height = rect.height.get().try_into().unwrap_or(i32::MAX); + // Introduced in version 4, it is an error to use this request in version 3 or lower. - 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: *rect })?; self.surface.damage_buffer(x, y, width, height); } } diff --git a/src/backends/win32.rs b/src/backends/win32.rs index 40bd3bd0..4bd36b4a 100644 --- a/src/backends/win32.rs +++ b/src/backends/win32.rs @@ -285,15 +285,11 @@ impl BufferInterface for BufferImpl<'_> { fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> { 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 })?; + let x = rect.x.try_into().unwrap_or(i32::MAX); + let y = rect.y.try_into().unwrap_or(i32::MAX); + let width = rect.width.get().try_into().unwrap_or(i32::MAX); + let height = rect.height.get().try_into().unwrap_or(i32::MAX); + Gdi::BitBlt( self.dc.0, x, diff --git a/src/backends/x11.rs b/src/backends/x11.rs index 1ea9cecb..36032e98 100644 --- a/src/backends/x11.rs +++ b/src/backends/x11.rs @@ -469,18 +469,13 @@ impl BufferInterface for BufferImpl<'_> { damage .iter() .try_for_each(|rect| { - let (src_x, src_y, dst_x, dst_y, width, height) = (|| { - Some(( - u16::try_from(rect.x).ok()?, - u16::try_from(rect.y).ok()?, - i16::try_from(rect.x).ok()?, - i16::try_from(rect.y).ok()?, - u16::try_from(rect.width.get()).ok()?, - u16::try_from(rect.height.get()).ok()?, - )) - })( - ) - .ok_or(SoftBufferError::DamageOutOfRange { rect: *rect })?; + let src_x = rect.x.try_into().unwrap_or(u16::MAX); + let src_y = rect.y.try_into().unwrap_or(u16::MAX); + let dst_x = rect.x.try_into().unwrap_or(i16::MAX); + let dst_y = rect.y.try_into().unwrap_or(i16::MAX); + let width = rect.width.get().try_into().unwrap_or(u16::MAX); + let height = rect.height.get().try_into().unwrap_or(u16::MAX); + self.connection .shm_put_image( self.window, diff --git a/src/error.rs b/src/error.rs index b79be6b6..9ba62252 100644 --- a/src/error.rs +++ b/src/error.rs @@ -86,12 +86,6 @@ pub enum SoftBufferError { height: NonZeroU32, }, - /// The provided damage rect is outside of the range supported by the backend. - DamageOutOfRange { - /// The damage rect that was out of range. - rect: crate::Rect, - }, - /// A platform-specific backend error occurred. /// /// The first field provides a human-readable description of the error. The second field @@ -133,11 +127,6 @@ impl fmt::Display for SoftBufferError { ), Self::PlatformError(msg, None) => write!(f, "Platform error: {msg:?}"), Self::PlatformError(msg, Some(err)) => write!(f, "Platform error: {msg:?}: {err}"), - Self::DamageOutOfRange { rect } => write!( - f, - "Damage rect {}x{} at ({}, {}) out of range for backend.", - rect.width, rect.height, rect.x, rect.y - ), Self::Unimplemented => write!(f, "This function is unimplemented on this platform."), } } From 6c68049200603acf0eff18a7606227f6edfb900f Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 20 Jan 2026 20:28:35 +0100 Subject: [PATCH 2/8] refactor: Remove internal present, keep only present_with_damage --- src/backend_dispatch.rs | 9 --------- src/backend_interface.rs | 1 - src/backends/android.rs | 19 +++++++++++-------- src/backends/cg.rs | 6 +----- src/backends/kms.rs | 11 ----------- src/backends/orbital.rs | 6 +----- src/backends/wayland/mod.rs | 11 ----------- src/backends/web.rs | 12 ------------ src/backends/win32.rs | 11 ----------- src/backends/x11.rs | 12 ------------ src/lib.rs | 9 ++++++++- 11 files changed, 21 insertions(+), 86 deletions(-) diff --git a/src/backend_dispatch.rs b/src/backend_dispatch.rs index f2a94540..32d61a19 100644 --- a/src/backend_dispatch.rs +++ b/src/backend_dispatch.rs @@ -185,15 +185,6 @@ macro_rules! make_dispatch { } } - fn present(self) -> Result<(), SoftBufferError> { - match self { - $( - $(#[$attr])* - Self::$name(inner) => inner.present(), - )* - } - } - fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> { match self { $( diff --git a/src/backend_interface.rs b/src/backend_interface.rs index 1eafbf82..d6e14682 100644 --- a/src/backend_interface.rs +++ b/src/backend_interface.rs @@ -40,5 +40,4 @@ pub(crate) trait BufferInterface { fn pixels_mut(&mut self) -> &mut [u32]; fn age(&self) -> u8; fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError>; - fn present(self) -> Result<(), SoftBufferError>; } diff --git a/src/backends/android.rs b/src/backends/android.rs index cc23b9c6..c58fa896 100644 --- a/src/backends/android.rs +++ b/src/backends/android.rs @@ -142,7 +142,17 @@ impl BufferInterface for BufferImpl<'_> { } // TODO: This function is pretty slow this way - fn present(mut self) -> Result<(), SoftBufferError> { + fn present_with_damage(mut self, damage: &[Rect]) -> Result<(), SoftBufferError> { + // TODO: Android requires the damage rect _at lock time_ + // Since we're faking the backing buffer _anyway_, we could even fake the surface lock + // and lock it here (if it doesn't influence timings). + // + // Android seems to do this because the region can be expanded by the + // system, requesting the user to actually redraw a larger region. + // It's unclear if/when this is used, or if corruption/artifacts occur + // when the enlarged damage region is not re-rendered? + let _ = damage; + let input_lines = self.buffer.chunks(self.native_window_buffer.width()); for (output, input) in self .native_window_buffer @@ -165,11 +175,4 @@ impl BufferInterface for BufferImpl<'_> { } Ok(()) } - - fn present_with_damage(self, _damage: &[Rect]) -> Result<(), SoftBufferError> { - // TODO: Android requires the damage rect _at lock time_ - // Since we're faking the backing buffer _anyway_, we could even fake the surface lock - // and lock it here (if it doesn't influence timings). - self.present() - } } diff --git a/src/backends/cg.rs b/src/backends/cg.rs index f45779f6..145cb178 100644 --- a/src/backends/cg.rs +++ b/src/backends/cg.rs @@ -296,7 +296,7 @@ impl BufferInterface for BufferImpl<'_> { 0 } - fn present(self) -> Result<(), SoftBufferError> { + fn present_with_damage(self, _damage: &[Rect]) -> Result<(), SoftBufferError> { unsafe extern "C-unwind" fn release( _info: *mut c_void, data: NonNull, @@ -361,10 +361,6 @@ impl BufferInterface for BufferImpl<'_> { CATransaction::commit(); Ok(()) } - - fn present_with_damage(self, _damage: &[Rect]) -> Result<(), SoftBufferError> { - self.present() - } } #[derive(Debug)] diff --git a/src/backends/kms.rs b/src/backends/kms.rs index 8dd5f55d..99ff78f7 100644 --- a/src/backends/kms.rs +++ b/src/backends/kms.rs @@ -377,17 +377,6 @@ impl BufferInterface for BufferImpl<'_> { Ok(()) } - - #[inline] - fn present(self) -> Result<(), SoftBufferError> { - let (width, height) = self.size; - self.present_with_damage(&[crate::Rect { - x: 0, - y: 0, - width, - height, - }]) - } } impl SharedBuffer { diff --git a/src/backends/orbital.rs b/src/backends/orbital.rs index 89a718db..3de1f3cb 100644 --- a/src/backends/orbital.rs +++ b/src/backends/orbital.rs @@ -176,7 +176,7 @@ impl BufferInterface for BufferImpl<'_> { } } - fn present(self) -> Result<(), SoftBufferError> { + fn present_with_damage(self, _damage: &[Rect]) -> Result<(), SoftBufferError> { match self.pixels { Pixels::Mapping(mapping) => { drop(mapping); @@ -190,10 +190,6 @@ impl BufferInterface for BufferImpl<'_> { Ok(()) } - - fn present_with_damage(self, _damage: &[Rect]) -> Result<(), SoftBufferError> { - self.present() - } } // Read the current width and size diff --git a/src/backends/wayland/mod.rs b/src/backends/wayland/mod.rs index f47e7715..8e26ab21 100644 --- a/src/backends/wayland/mod.rs +++ b/src/backends/wayland/mod.rs @@ -280,17 +280,6 @@ impl BufferInterface for BufferImpl<'_> { Ok(()) } - - fn present(self) -> Result<(), SoftBufferError> { - let (width, height) = (self.width, self.height); - self.present_with_damage(&[Rect { - x: 0, - y: 0, - // We know width/height will be non-negative and non-zero. - width: (width as u32).try_into().unwrap(), - height: (height as u32).try_into().unwrap(), - }]) - } } impl Dispatch for State { diff --git a/src/backends/web.rs b/src/backends/web.rs index a829e5b4..5e7d5d0c 100644 --- a/src/backends/web.rs +++ b/src/backends/web.rs @@ -331,18 +331,6 @@ impl BufferInterface for BufferImpl<'_> { } /// Push the buffer to the canvas. - fn present(self) -> Result<(), SoftBufferError> { - let (width, height) = self - .size - .expect("Must set size of surface before calling `present()`"); - self.present_with_damage(&[Rect { - x: 0, - y: 0, - width, - height, - }]) - } - fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> { let (buffer_width, _buffer_height) = self .size diff --git a/src/backends/win32.rs b/src/backends/win32.rs index 4bd36b4a..63be3c8b 100644 --- a/src/backends/win32.rs +++ b/src/backends/win32.rs @@ -271,17 +271,6 @@ impl BufferInterface for BufferImpl<'_> { } } - fn present(self) -> Result<(), SoftBufferError> { - let (width, height) = (self.buffer.width, self.buffer.height); - self.present_with_damage(&[Rect { - x: 0, - y: 0, - // We know width/height will be non-negative - width: width.try_into().unwrap(), - height: height.try_into().unwrap(), - }]) - } - fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> { unsafe { for rect in damage.iter().copied() { diff --git a/src/backends/x11.rs b/src/backends/x11.rs index 36032e98..6e52fc4a 100644 --- a/src/backends/x11.rs +++ b/src/backends/x11.rs @@ -511,18 +511,6 @@ impl BufferInterface for BufferImpl<'_> { Ok(()) } - - fn present(self) -> Result<(), SoftBufferError> { - let (width, height) = self - .size - .expect("Must set size of surface before calling `present()`"); - self.present_with_damage(&[Rect { - x: 0, - y: 0, - width: width.into(), - height: height.into(), - }]) - } } impl Buffer { diff --git a/src/lib.rs b/src/lib.rs index d54d2b9d..4a46068b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -276,8 +276,15 @@ impl Buffer<'_> { /// /// If the caller wishes to synchronize other surface/window changes, such requests must be sent to the /// Wayland compositor before calling this function. + #[inline] pub fn present(self) -> Result<(), SoftBufferError> { - self.buffer_impl.present() + // Damage the entire buffer. + self.present_with_damage(&[Rect { + x: 0, + y: 0, + width: NonZeroU32::MAX, + height: NonZeroU32::MAX, + }]) } /// Presents buffer to the window, with damage regions. From 1ec2833a05e93244464c4884c2ce9d212d12864b Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 19 Jan 2026 18:47:13 +0100 Subject: [PATCH 3/8] fix(windows): Fix damage regions outside the pixel buffer --- CHANGELOG.md | 1 + src/backends/win32.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 079ca3f9..36e0cf97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - **Breaking:** Removed generic type parameters `D` and `W` from `Buffer<'_>` struct. - **Breaking:** Removed `Deref[Mut]` implementation on `Buffer<'_>`. Use `Buffer::pixels()` instead. - **Breaking:** Removed `DamageOutOfRange` error case. If the damage value is greater than the backend supports, it is instead clamped to an appropriate value. +- Fixed `present_with_damage` with bounds out of range on Windows. # 0.4.7 diff --git a/src/backends/win32.rs b/src/backends/win32.rs index 63be3c8b..7e110efb 100644 --- a/src/backends/win32.rs +++ b/src/backends/win32.rs @@ -281,13 +281,13 @@ impl BufferInterface for BufferImpl<'_> { Gdi::BitBlt( self.dc.0, - x, - y, - width, - height, + x.min(self.buffer.width.get()), + y.min(self.buffer.height.get()), + width.min(self.buffer.width.get()), + height.min(self.buffer.height.get()), self.buffer.dc, - x, - y, + x.min(self.buffer.width.get()), + y.min(self.buffer.height.get()), Gdi::SRCCOPY, ); } From ab16475c0b7cc84f94eae2741bb8702a4c75c202 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 19 Jan 2026 18:52:51 +0100 Subject: [PATCH 4/8] fix(web): Fix damage regions outside the pixel buffer --- CHANGELOG.md | 2 +- src/backends/web.rs | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36e0cf97..159b4f3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ - **Breaking:** Removed generic type parameters `D` and `W` from `Buffer<'_>` struct. - **Breaking:** Removed `Deref[Mut]` implementation on `Buffer<'_>`. Use `Buffer::pixels()` instead. - **Breaking:** Removed `DamageOutOfRange` error case. If the damage value is greater than the backend supports, it is instead clamped to an appropriate value. -- Fixed `present_with_damage` with bounds out of range on Windows. +- Fixed `present_with_damage` with bounds out of range on Windows and Web. # 0.4.7 diff --git a/src/backends/web.rs b/src/backends/web.rs index 5e7d5d0c..5b1ae511 100644 --- a/src/backends/web.rs +++ b/src/backends/web.rs @@ -332,7 +332,7 @@ impl BufferInterface for BufferImpl<'_> { /// Push the buffer to the canvas. fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> { - let (buffer_width, _buffer_height) = self + let (buffer_width, buffer_height) = self .size .expect("Must set size of surface before calling `present_with_damage()`"); @@ -358,8 +358,10 @@ impl BufferInterface for BufferImpl<'_> { .collect(); debug_assert_eq!( - bitmap.len() as u32, - union_damage.width.get() * union_damage.height.get() * 4 + bitmap.len(), + union_damage.width.get().min(buffer_width.get()) as usize + * union_damage.height.get().min(buffer_height.get()) as usize + * 4 ); #[cfg(target_feature = "atomics")] @@ -382,14 +384,14 @@ impl BufferInterface for BufferImpl<'_> { let array = Uint8Array::new_with_length(bitmap.len() as u32); array.copy_from(&bitmap); let array = Uint8ClampedArray::new(&array); - ImageDataExt::new(array, union_damage.width.get()) + ImageDataExt::new(array, union_damage.width.get().min(buffer_width.get())) .map(JsValue::from) .map(ImageData::unchecked_from_js) }; #[cfg(not(target_feature = "atomics"))] let result = ImageData::new_with_u8_clamped_array( wasm_bindgen::Clamped(&bitmap), - union_damage.width.get(), + union_damage.width.get().min(buffer_width.get()), ); // This should only throw an error if the buffer we pass's size is incorrect. let image_data = result.unwrap(); @@ -399,12 +401,12 @@ impl BufferInterface for BufferImpl<'_> { self.canvas .put_image_data( &image_data, - union_damage.x.into(), - union_damage.y.into(), + union_damage.x.min(buffer_width.get()).into(), + union_damage.y.min(buffer_height.get()).into(), (rect.x - union_damage.x).into(), (rect.y - union_damage.y).into(), - rect.width.get().into(), - rect.height.get().into(), + rect.width.get().min(buffer_width.get()).into(), + rect.height.get().min(buffer_height.get()).into(), ) .unwrap(); } From bc04c6e19ebb2a2d40814f3678e233f097d7ebfa Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 19 Jan 2026 18:57:55 +0100 Subject: [PATCH 5/8] fix(x11): Fix damage regions outside the pixel buffer --- CHANGELOG.md | 2 +- src/backends/x11.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 159b4f3e..88d8400d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ - **Breaking:** Removed generic type parameters `D` and `W` from `Buffer<'_>` struct. - **Breaking:** Removed `Deref[Mut]` implementation on `Buffer<'_>`. Use `Buffer::pixels()` instead. - **Breaking:** Removed `DamageOutOfRange` error case. If the damage value is greater than the backend supports, it is instead clamped to an appropriate value. -- Fixed `present_with_damage` with bounds out of range on Windows and Web. +- Fixed `present_with_damage` with bounds out of range on Windows, Web and X11. # 0.4.7 diff --git a/src/backends/x11.rs b/src/backends/x11.rs index 6e52fc4a..8d3810db 100644 --- a/src/backends/x11.rs +++ b/src/backends/x11.rs @@ -482,12 +482,12 @@ impl BufferInterface for BufferImpl<'_> { self.gc, surface_width.get(), surface_height.get(), - src_x, - src_y, - width, - height, - dst_x, - dst_y, + src_x.min(surface_width.get()), + src_y.min(surface_height.get()), + width.min(surface_width.get()), + height.min(surface_height.get()), + dst_x.min(surface_width.get() as i16), + dst_y.min(surface_height.get() as i16), self.depth, xproto::ImageFormat::Z_PIXMAP.into(), false, From 55ce8e9d212ae7c141af26aed9f3c4201bd1a8a9 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 20 Jan 2026 20:33:34 +0100 Subject: [PATCH 6/8] doc: Document ignored damage Damage that fall outside the surface is ignored. --- src/backends/wayland/mod.rs | 2 ++ src/lib.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/backends/wayland/mod.rs b/src/backends/wayland/mod.rs index 8e26ab21..45698ef4 100644 --- a/src/backends/wayland/mod.rs +++ b/src/backends/wayland/mod.rs @@ -260,6 +260,8 @@ impl BufferInterface for BufferImpl<'_> { self.surface.damage(0, 0, i32::MAX, i32::MAX); } else { for rect in damage { + // Damage that falls outside the surface is ignored. + // https://wayland.freedesktop.org/docs/html/apa.html#protocol-spec-wl_surface let x = rect.x.try_into().unwrap_or(i32::MAX); let y = rect.y.try_into().unwrap_or(i32::MAX); let width = rect.width.get().try_into().unwrap_or(i32::MAX); diff --git a/src/lib.rs b/src/lib.rs index 4a46068b..23112483 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -289,6 +289,8 @@ impl Buffer<'_> { /// Presents buffer to the window, with damage regions. /// + /// Damage regions that fall outside the surface are ignored. + /// /// # Platform dependent behavior /// /// Supported on: From c049f3251674fd9aad88da43897b86d3994645f4 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 21 Jan 2026 15:57:46 +0100 Subject: [PATCH 7/8] refactor: Add to_X_saturating helpers Makes it clearer what we're actually doing with the damage rectangles. --- src/backends/kms.rs | 19 +++++++++---------- src/backends/wayland/mod.rs | 13 +++++++++---- src/backends/win32.rs | 13 +++++++++---- src/backends/x11.rs | 22 ++++++++++++++++------ 4 files changed, 43 insertions(+), 24 deletions(-) diff --git a/src/backends/kms.rs b/src/backends/kms.rs index 99ff78f7..ff6024b3 100644 --- a/src/backends/kms.rs +++ b/src/backends/kms.rs @@ -329,16 +329,10 @@ impl BufferInterface for BufferImpl<'_> { .iter() .map(|rect| { ClipRect::new( - rect.x.try_into().unwrap_or(u16::MAX), - rect.y.try_into().unwrap_or(u16::MAX), - rect.x - .checked_add(rect.width.get()) - .and_then(|x| x.try_into().ok()) - .unwrap_or(u16::MAX), - rect.y - .checked_add(rect.height.get()) - .and_then(|x| x.try_into().ok()) - .unwrap_or(u16::MAX), + to_u16_saturating(rect.x), + to_u16_saturating(rect.y), + to_u16_saturating(rect.x.checked_add(rect.width.get()).unwrap_or(u32::MAX)), + to_u16_saturating(rect.y.checked_add(rect.height.get()).unwrap_or(u32::MAX)), ) }) .collect(); @@ -414,3 +408,8 @@ impl Buffers { self.buffers[0].size() } } + +/// Convert a `u32` to `u16`, and saturate if it overflows. +fn to_u16_saturating(val: u32) -> u16 { + val.try_into().unwrap_or(u16::MAX) +} diff --git a/src/backends/wayland/mod.rs b/src/backends/wayland/mod.rs index 45698ef4..0490b9e6 100644 --- a/src/backends/wayland/mod.rs +++ b/src/backends/wayland/mod.rs @@ -262,10 +262,10 @@ impl BufferInterface for BufferImpl<'_> { for rect in damage { // Damage that falls outside the surface is ignored. // https://wayland.freedesktop.org/docs/html/apa.html#protocol-spec-wl_surface - let x = rect.x.try_into().unwrap_or(i32::MAX); - let y = rect.y.try_into().unwrap_or(i32::MAX); - let width = rect.width.get().try_into().unwrap_or(i32::MAX); - let height = rect.height.get().try_into().unwrap_or(i32::MAX); + let x = to_i32_saturating(rect.x); + let y = to_i32_saturating(rect.y); + let width = to_i32_saturating(rect.width.get()); + let height = to_i32_saturating(rect.height.get()); // Introduced in version 4, it is an error to use this request in version 3 or lower. self.surface.damage_buffer(x, y, width, height); @@ -308,3 +308,8 @@ impl Dispatch for State { ) { } } + +/// Convert a `u32` to `i32`, and saturate if it overflows. +fn to_i32_saturating(val: u32) -> i32 { + val.try_into().unwrap_or(i32::MAX) +} diff --git a/src/backends/win32.rs b/src/backends/win32.rs index 7e110efb..9c5c5d1f 100644 --- a/src/backends/win32.rs +++ b/src/backends/win32.rs @@ -274,10 +274,10 @@ impl BufferInterface for BufferImpl<'_> { fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> { unsafe { for rect in damage.iter().copied() { - let x = rect.x.try_into().unwrap_or(i32::MAX); - let y = rect.y.try_into().unwrap_or(i32::MAX); - let width = rect.width.get().try_into().unwrap_or(i32::MAX); - let height = rect.height.get().try_into().unwrap_or(i32::MAX); + let x = to_i32_saturating(rect.x); + let y = to_i32_saturating(rect.y); + let width = to_i32_saturating(rect.width.get()); + let height = to_i32_saturating(rect.height.get()); Gdi::BitBlt( self.dc.0, @@ -452,3 +452,8 @@ impl From for OnlyUsedFromOrigin { Self(t) } } + +/// Convert a `u32` to `i32`, and saturate if it overflows. +fn to_i32_saturating(val: u32) -> i32 { + val.try_into().unwrap_or(i32::MAX) +} diff --git a/src/backends/x11.rs b/src/backends/x11.rs index 8d3810db..6c56195c 100644 --- a/src/backends/x11.rs +++ b/src/backends/x11.rs @@ -469,12 +469,12 @@ impl BufferInterface for BufferImpl<'_> { damage .iter() .try_for_each(|rect| { - let src_x = rect.x.try_into().unwrap_or(u16::MAX); - let src_y = rect.y.try_into().unwrap_or(u16::MAX); - let dst_x = rect.x.try_into().unwrap_or(i16::MAX); - let dst_y = rect.y.try_into().unwrap_or(i16::MAX); - let width = rect.width.get().try_into().unwrap_or(u16::MAX); - let height = rect.height.get().try_into().unwrap_or(u16::MAX); + let src_x = to_u16_saturating(rect.x); + let src_y = to_u16_saturating(rect.y); + let dst_x = to_i16_saturating(rect.x); + let dst_y = to_i16_saturating(rect.y); + let width = to_u16_saturating(rect.width.get()); + let height = to_u16_saturating(rect.height.get()); self.connection .shm_put_image( @@ -965,3 +965,13 @@ fn total_len(width: u16, height: u16) -> usize { .and_then(|len| len.checked_mul(4)) .unwrap_or_else(|| panic!("Dimensions are too large: ({} x {})", width, height)) } + +/// Convert a `u32` to `u16`, and saturate if it overflows. +fn to_u16_saturating(val: u32) -> u16 { + val.try_into().unwrap_or(u16::MAX) +} + +/// Convert a `u32` to `i16`, and saturate if it overflows. +fn to_i16_saturating(val: u32) -> i16 { + val.try_into().unwrap_or(i16::MAX) +} From 2d736eaefdec0d2e2d37146af7cb5c2f5b5dadb1 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 21 Jan 2026 15:56:19 +0100 Subject: [PATCH 8/8] refactor: Add clamp_rect utility --- src/backends/wayland/mod.rs | 3 ++- src/backends/web.rs | 20 ++++++++++---------- src/backends/win32.rs | 21 +++++++++++++-------- src/backends/x11.rs | 17 +++++++++++------ src/util.rs | 16 ++++++++++++++++ 5 files changed, 52 insertions(+), 25 deletions(-) diff --git a/src/backends/wayland/mod.rs b/src/backends/wayland/mod.rs index 0490b9e6..36fd67f4 100644 --- a/src/backends/wayland/mod.rs +++ b/src/backends/wayland/mod.rs @@ -260,7 +260,8 @@ impl BufferInterface for BufferImpl<'_> { self.surface.damage(0, 0, i32::MAX, i32::MAX); } else { for rect in damage { - // Damage that falls outside the surface is ignored. + // Damage that falls outside the surface is ignored, so we don't need to clamp the + // rect manually. // https://wayland.freedesktop.org/docs/html/apa.html#protocol-spec-wl_surface let x = to_i32_saturating(rect.x); let y = to_i32_saturating(rect.y); diff --git a/src/backends/web.rs b/src/backends/web.rs index 5b1ae511..e9296b56 100644 --- a/src/backends/web.rs +++ b/src/backends/web.rs @@ -337,7 +337,7 @@ impl BufferInterface for BufferImpl<'_> { .expect("Must set size of surface before calling `present_with_damage()`"); let union_damage = if let Some(rect) = util::union_damage(damage) { - rect + util::clamp_rect(rect, buffer_width, buffer_height) } else { return Ok(()); }; @@ -359,9 +359,7 @@ impl BufferInterface for BufferImpl<'_> { debug_assert_eq!( bitmap.len(), - union_damage.width.get().min(buffer_width.get()) as usize - * union_damage.height.get().min(buffer_height.get()) as usize - * 4 + union_damage.width.get() as usize * union_damage.height.get() as usize * 4 ); #[cfg(target_feature = "atomics")] @@ -384,29 +382,31 @@ impl BufferInterface for BufferImpl<'_> { let array = Uint8Array::new_with_length(bitmap.len() as u32); array.copy_from(&bitmap); let array = Uint8ClampedArray::new(&array); - ImageDataExt::new(array, union_damage.width.get().min(buffer_width.get())) + ImageDataExt::new(array, union_damage.width.get()) .map(JsValue::from) .map(ImageData::unchecked_from_js) }; #[cfg(not(target_feature = "atomics"))] let result = ImageData::new_with_u8_clamped_array( wasm_bindgen::Clamped(&bitmap), - union_damage.width.get().min(buffer_width.get()), + union_damage.width.get(), ); // This should only throw an error if the buffer we pass's size is incorrect. let image_data = result.unwrap(); for rect in damage { + let rect = util::clamp_rect(*rect, buffer_width, buffer_height); + // This can only throw an error if `data` is detached, which is impossible. self.canvas .put_image_data( &image_data, - union_damage.x.min(buffer_width.get()).into(), - union_damage.y.min(buffer_height.get()).into(), + union_damage.x.into(), + union_damage.y.into(), (rect.x - union_damage.x).into(), (rect.y - union_damage.y).into(), - rect.width.get().min(buffer_width.get()).into(), - rect.height.get().min(buffer_height.get()).into(), + rect.width.get().into(), + rect.height.get().into(), ) .unwrap(); } diff --git a/src/backends/win32.rs b/src/backends/win32.rs index 9c5c5d1f..4aaba81c 100644 --- a/src/backends/win32.rs +++ b/src/backends/win32.rs @@ -3,7 +3,7 @@ //! This module converts the input buffer into a bitmap and then stretches it to the window. use crate::backend_interface::*; -use crate::{Rect, SoftBufferError}; +use crate::{util, Rect, SoftBufferError}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle}; use std::io; @@ -273,7 +273,12 @@ impl BufferInterface for BufferImpl<'_> { fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> { unsafe { - for rect in damage.iter().copied() { + for rect in damage { + let rect = util::clamp_rect( + *rect, + self.buffer.width.try_into().unwrap(), + self.buffer.height.try_into().unwrap(), + ); let x = to_i32_saturating(rect.x); let y = to_i32_saturating(rect.y); let width = to_i32_saturating(rect.width.get()); @@ -281,13 +286,13 @@ impl BufferInterface for BufferImpl<'_> { Gdi::BitBlt( self.dc.0, - x.min(self.buffer.width.get()), - y.min(self.buffer.height.get()), - width.min(self.buffer.width.get()), - height.min(self.buffer.height.get()), + x, + y, + width, + height, self.buffer.dc, - x.min(self.buffer.width.get()), - y.min(self.buffer.height.get()), + x, + y, Gdi::SRCCOPY, ); } diff --git a/src/backends/x11.rs b/src/backends/x11.rs index 6c56195c..4be03993 100644 --- a/src/backends/x11.rs +++ b/src/backends/x11.rs @@ -469,6 +469,11 @@ impl BufferInterface for BufferImpl<'_> { damage .iter() .try_for_each(|rect| { + let rect = util::clamp_rect( + *rect, + surface_width.into(), + surface_height.into(), + ); let src_x = to_u16_saturating(rect.x); let src_y = to_u16_saturating(rect.y); let dst_x = to_i16_saturating(rect.x); @@ -482,12 +487,12 @@ impl BufferInterface for BufferImpl<'_> { self.gc, surface_width.get(), surface_height.get(), - src_x.min(surface_width.get()), - src_y.min(surface_height.get()), - width.min(surface_width.get()), - height.min(surface_height.get()), - dst_x.min(surface_width.get() as i16), - dst_y.min(surface_height.get() as i16), + src_x, + src_y, + width, + height, + dst_x, + dst_y, self.depth, xproto::ImageFormat::Z_PIXMAP.into(), false, diff --git a/src/util.rs b/src/util.rs index 5c29071e..67a742dc 100644 --- a/src/util.rs +++ b/src/util.rs @@ -43,6 +43,22 @@ pub(crate) fn union_damage(damage: &[Rect]) -> Option { }) } +/// Clamp the damage rectangle to be within the given bounds. +pub(crate) fn clamp_rect(rect: Rect, width: NonZeroU32, height: NonZeroU32) -> Rect { + // The positions of the edges of the rectangle. + let left = rect.x.min(width.get()); + let top = rect.y.min(height.get()); + let right = rect.x.saturating_add(rect.width.get()).min(width.get()); + let bottom = rect.y.saturating_add(rect.height.get()).min(height.get()); + + Rect { + x: left, + y: top, + width: NonZeroU32::new(right - left).expect("rect ended up being zero-sized"), + height: NonZeroU32::new(bottom - top).expect("rect ended up being zero-sized"), + } +} + /// A wrapper around a `Vec` of pixels that doesn't print the whole buffer on `Debug`. #[derive(PartialEq, Eq, Hash, Clone)] pub(crate) struct PixelBuffer(pub Vec);