From 6b78ba2ce956d5cbef8de8a32704c77369004946 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 16 Mar 2026 02:06:42 +0100 Subject: [PATCH 1/2] Don't document zero-initializing That the buffer is zeroed when `age == 0` is not guaranteed, e.g. on many platforms (Wayland, X11, Web), when we resize the buffer, we don't also clear it. We could technically guarantee something like "if the buffer's underlying data could not be reused, the buffer is zero-initialized", but that's not really useful to users (at least not unless we expose additional information in `Buffer::age`). This might also make it easier to use zero-copying on Android (might be able to avoid having to clear the buffer). --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 5e24ede2..5aeeaa8a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -197,7 +197,7 @@ impl Surface { /// /// The size must be set with [`Surface::resize`] or [`Surface::configure`] first. /// - /// The contents of the buffer may be zeroed, or may contain a previous frame. Call + /// The contents of the buffer may be garbage, or may contain a previous frame. Call /// [`Buffer::age`] to determine this. /// /// ## Platform Dependent Behavior From ee51937a8434fd9cb46c4a1d922fc1720ac1731a Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 16 Mar 2026 02:09:31 +0100 Subject: [PATCH 2/2] Garbage-initialize buffers with cfg(debug_assertions) To help make it clear that the buffer isn't zero-initialize / catch mistakes where the user was relying on it. For example, drawing like this is incorrect: ``` // Fill with blue. for (_, _, pixel) in buffer.pixels_iter() { pixel.b = 0xff; pixel.a = 0xff; } ``` --- src/backends/android.rs | 2 +- src/backends/cg.rs | 2 +- src/backends/orbital.rs | 2 +- src/backends/web.rs | 2 +- src/backends/x11.rs | 4 ++-- src/pixel.rs | 14 ++++++++++++++ 6 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/backends/android.rs b/src/backends/android.rs index 4404badd..7166c9d0 100644 --- a/src/backends/android.rs +++ b/src/backends/android.rs @@ -114,7 +114,7 @@ impl SurfaceInterface for Android } let buffer = - vec![Pixel::default(); native_window_buffer.stride() * native_window_buffer.height()]; + vec![Pixel::INIT; native_window_buffer.stride() * native_window_buffer.height()]; Ok(BufferImpl { native_window_buffer, diff --git a/src/backends/cg.rs b/src/backends/cg.rs index a84cbd7f..18e096d5 100644 --- a/src/backends/cg.rs +++ b/src/backends/cg.rs @@ -279,7 +279,7 @@ impl SurfaceInterface for CGImpl< fn next_buffer(&mut self, alpha_mode: AlphaMode) -> Result, SoftBufferError> { let buffer_size = util::byte_stride(self.width as u32) as usize * self.height / 4; Ok(BufferImpl { - buffer: util::PixelBuffer(vec![Pixel::default(); buffer_size]), + buffer: util::PixelBuffer(vec![Pixel::INIT; buffer_size]), width: self.width, height: self.height, color_space: &self.color_space, diff --git a/src/backends/orbital.rs b/src/backends/orbital.rs index 23f7b451..c343053e 100644 --- a/src/backends/orbital.rs +++ b/src/backends/orbital.rs @@ -132,7 +132,7 @@ impl SurfaceInterface for Orbital ) } else { Pixels::Buffer(util::PixelBuffer(vec![ - Pixel::default(); + Pixel::INIT; self.width as usize * self.height as usize ])) diff --git a/src/backends/web.rs b/src/backends/web.rs index f747a557..845d5329 100644 --- a/src/backends/web.rs +++ b/src/backends/web.rs @@ -182,7 +182,7 @@ impl SurfaceInterface for WebImpl if self.size != Some((width, height)) { self.buffer_presented = false; self.buffer - .resize(total_len(width.get(), height.get()), Pixel::default()); + .resize(total_len(width.get(), height.get()), Pixel::INIT); self.canvas.set_width(width.get()); self.canvas.set_height(height.get()); self.size = Some((width, height)); diff --git a/src/backends/x11.rs b/src/backends/x11.rs index c784128a..3a6c454b 100644 --- a/src/backends/x11.rs +++ b/src/backends/x11.rs @@ -397,7 +397,7 @@ impl SurfaceInterface fo .swbuf_err("Failed to fetch image from window")?; if reply.depth == self.depth && reply.visual == self.visual_id { - let mut out = vec![Pixel::default(); reply.data.len() / size_of::()]; + let mut out = vec![Pixel::INIT; reply.data.len() / size_of::()]; // SAFETY: `Pixel` can be re-interpreted as `[u8; 4]`. let out_u8s = unsafe { slice::from_raw_parts_mut( @@ -557,7 +557,7 @@ impl Buffer { match self { Buffer::Shm(ref mut shm) => shm.alloc_segment(conn, num_bytes), Buffer::Wire(wire) => { - wire.resize(num_bytes / size_of::(), Pixel::default()); + wire.resize(num_bytes / size_of::(), Pixel::INIT); Ok(()) } } diff --git a/src/pixel.rs b/src/pixel.rs index a6a05907..f0df0d20 100644 --- a/src/pixel.rs +++ b/src/pixel.rs @@ -151,6 +151,20 @@ impl Pixel { pub const fn new_bgra(b: u8, g: u8, r: u8, a: u8) -> Self { Self { r, g, b, a } } + + /// A reasonable value to initialize buffers with. + /// + /// Users should redraw the entire buffer when `buffer.age() == 0`, they shouldn't rely on this. + #[allow(unused)] // Only used on some backends. + pub(crate) const INIT: Self = if cfg!(debug_assertions) { + // Half-transparent mostly-red, this will panic in `Buffer::present` (unless the user + // chose a different alpha mode), which is desirable since we don't want this pixel to ever + // show up. + Self::new_rgba(0xff, 0x11, 0x22, 0x7f) + } else { + // Zero-initialization is often a lot faster. + Self::new_rgba(0x00, 0x00, 0x00, 0x00) + }; } // TODO: Implement `Add`/`Mul`/similar `std::ops` like `rgb` does?