From 7911980864481ece8d6f03ac37dad51b21b141a4 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Sun, 1 Feb 2026 12:00:05 +0100 Subject: [PATCH 1/2] android: Make the backend zero-copy --- src/backends/android.rs | 54 ++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/src/backends/android.rs b/src/backends/android.rs index 980d570e..3d078513 100644 --- a/src/backends/android.rs +++ b/src/backends/android.rs @@ -1,6 +1,7 @@ //! Implementation of software buffering for Android. use std::marker::PhantomData; +use std::mem::MaybeUninit; use std::num::{NonZeroI32, NonZeroU32}; use ndk::{ @@ -12,7 +13,9 @@ use raw_window_handle::AndroidNdkWindowHandle; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle}; use crate::error::InitError; -use crate::{util, BufferInterface, Pixel, Rect, SoftBufferError, SurfaceInterface}; +use crate::{BufferInterface, Pixel, Rect, SoftBufferError, SurfaceInterface}; + +const PIXEL_SIZE: usize = size_of::(); /// The handle to a window for software buffering. #[derive(Debug)] @@ -52,7 +55,7 @@ impl SurfaceInterface for Android &self.window } - /// Also changes the pixel format to [`HardwareBufferFormat::R8G8B8A8_UNORM`]. + /// Also changes the pixel format to [`HardwareBufferFormat::R8G8B8X8_UNORM`]. fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { let (width, height) = (|| { let width = NonZeroI32::try_from(width).ok()?; @@ -77,7 +80,7 @@ impl SurfaceInterface for Android } fn next_buffer(&mut self) -> Result, SoftBufferError> { - let native_window_buffer = self.native_window.lock(None).map_err(|err| { + let mut native_window_buffer = self.native_window.lock(None).map_err(|err| { SoftBufferError::PlatformError( Some("Failed to lock ANativeWindow".to_owned()), Some(Box::new(err)), @@ -99,12 +102,22 @@ impl SurfaceInterface for Android )); } - let buffer = - vec![Pixel::default(); native_window_buffer.stride() * native_window_buffer.height()]; + assert_eq!( + native_window_buffer.format().bytes_per_pixel(), + Some(PIXEL_SIZE) + ); + let native_buffer = native_window_buffer.bytes().unwrap(); + + // Zero-initialize the buffer, allowing it to be cast away from MaybeUninit and mapped as a Pixel slice + native_buffer.fill(MaybeUninit::new(0)); + + assert_eq!( + native_buffer.len(), + native_window_buffer.stride() * native_window_buffer.height() * PIXEL_SIZE + ); Ok(BufferImpl { native_window_buffer, - buffer: util::PixelBuffer(buffer), }) } @@ -117,7 +130,6 @@ impl SurfaceInterface for Android #[derive(Debug)] pub struct BufferImpl<'surface> { native_window_buffer: NativeWindowBufferLockGuard<'surface>, - buffer: util::PixelBuffer, } // TODO: Move to NativeWindowBufferLockGuard? @@ -125,7 +137,7 @@ unsafe impl Send for BufferImpl<'_> {} impl BufferInterface for BufferImpl<'_> { fn byte_stride(&self) -> NonZeroU32 { - NonZeroU32::new(self.native_window_buffer.stride() as u32 * 4).unwrap() + NonZeroU32::new((self.native_window_buffer.stride() * PIXEL_SIZE) as u32).unwrap() } fn width(&self) -> NonZeroU32 { @@ -138,7 +150,15 @@ impl BufferInterface for BufferImpl<'_> { #[inline] fn pixels_mut(&mut self) -> &mut [Pixel] { - &mut self.buffer + let native_buffer = self.native_window_buffer.bytes().unwrap(); + // SAFETY: The buffer was zero-initialized and its length is always a multiple of the pixel + // size (4 bytes), even when stride is applied + unsafe { + std::slice::from_raw_parts_mut( + native_buffer.as_mut_ptr().cast(), + native_buffer.len() / PIXEL_SIZE, + ) + } } #[inline] @@ -147,7 +167,7 @@ impl BufferInterface for BufferImpl<'_> { } // TODO: This function is pretty slow this way - fn present_with_damage(mut self, damage: &[Rect]) -> Result<(), SoftBufferError> { + 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). @@ -158,18 +178,8 @@ impl BufferInterface for BufferImpl<'_> { // when the enlarged damage region is not re-rendered? let _ = damage; - // Unreachable as we checked before that this is a valid, mappable format - let native_buffer = self.native_window_buffer.bytes().unwrap(); - - // Write RGB(A) to the output. - // TODO: Use `slice::write_copy_of_slice` once stable and in MSRV. - // TODO(madsmtm): Verify that this compiles down to an efficient copy. - for (pixel, output) in self.buffer.iter().zip(native_buffer.chunks_mut(4)) { - output[0].write(pixel.r); - output[1].write(pixel.g); - output[2].write(pixel.b); - output[3].write(pixel.a); - } + // The surface will be presented when it is unlocked, which happens when the owned guard + // is dropped. Ok(()) } From b9f52183b621c254472718798cbc02eb7edd2738 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Tue, 3 Feb 2026 17:08:11 +0100 Subject: [PATCH 2/2] android: Hold the locked buffer in `AndroidImpl` until it is presented via `BufferImpl` --- src/backends/android.rs | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/src/backends/android.rs b/src/backends/android.rs index 3d078513..57e1a3cf 100644 --- a/src/backends/android.rs +++ b/src/backends/android.rs @@ -20,11 +20,16 @@ const PIXEL_SIZE: usize = size_of::(); /// The handle to a window for software buffering. #[derive(Debug)] pub struct AndroidImpl { + // Must be first in the struct to guarantee being dropped and unlocked before the `NativeWindow` reference + in_progress_buffer: Option>, native_window: NativeWindow, window: W, _display: PhantomData, } +// TODO: Move to NativeWindowBufferLockGuard? +unsafe impl Send for AndroidImpl {} + impl SurfaceInterface for AndroidImpl { type Context = D; type Buffer<'surface> @@ -44,6 +49,7 @@ impl SurfaceInterface for Android let native_window = unsafe { NativeWindow::clone_from_ptr(a.a_native_window.cast()) }; Ok(Self { + in_progress_buffer: None, native_window, _display: PhantomData, window, @@ -80,6 +86,12 @@ impl SurfaceInterface for Android } fn next_buffer(&mut self) -> Result, SoftBufferError> { + if self.in_progress_buffer.is_some() { + return Ok(BufferImpl { + native_window_buffer: &mut self.in_progress_buffer, + }); + } + let mut native_window_buffer = self.native_window.lock(None).map_err(|err| { SoftBufferError::PlatformError( Some("Failed to lock ANativeWindow".to_owned()), @@ -116,8 +128,19 @@ impl SurfaceInterface for Android native_window_buffer.stride() * native_window_buffer.height() * PIXEL_SIZE ); + // SAFETY: We guarantee that the guard isn't actually held longer than this owned handle of + // the `NativeWindow` (which is trivially cloneable), by means of having BufferImpl take a + // mutable borrow on AndroidImpl which owns the NativeWindow and LockGuard. + let native_window_buffer = unsafe { + std::mem::transmute::< + NativeWindowBufferLockGuard<'_>, + NativeWindowBufferLockGuard<'static>, + >(native_window_buffer) + }; + self.in_progress_buffer = Some(native_window_buffer); + Ok(BufferImpl { - native_window_buffer, + native_window_buffer: &mut self.in_progress_buffer, }) } @@ -129,7 +152,8 @@ impl SurfaceInterface for Android #[derive(Debug)] pub struct BufferImpl<'surface> { - native_window_buffer: NativeWindowBufferLockGuard<'surface>, + // This Option will always be Some until present_with_damage() is called + native_window_buffer: &'surface mut Option>, } // TODO: Move to NativeWindowBufferLockGuard? @@ -137,20 +161,21 @@ unsafe impl Send for BufferImpl<'_> {} impl BufferInterface for BufferImpl<'_> { fn byte_stride(&self) -> NonZeroU32 { - NonZeroU32::new((self.native_window_buffer.stride() * PIXEL_SIZE) as u32).unwrap() + NonZeroU32::new((self.native_window_buffer.as_ref().unwrap().stride() * PIXEL_SIZE) as u32) + .unwrap() } fn width(&self) -> NonZeroU32 { - NonZeroU32::new(self.native_window_buffer.width() as u32).unwrap() + NonZeroU32::new(self.native_window_buffer.as_ref().unwrap().width() as u32).unwrap() } fn height(&self) -> NonZeroU32 { - NonZeroU32::new(self.native_window_buffer.height() as u32).unwrap() + NonZeroU32::new(self.native_window_buffer.as_ref().unwrap().height() as u32).unwrap() } #[inline] fn pixels_mut(&mut self) -> &mut [Pixel] { - let native_buffer = self.native_window_buffer.bytes().unwrap(); + let native_buffer = self.native_window_buffer.as_mut().unwrap().bytes().unwrap(); // SAFETY: The buffer was zero-initialized and its length is always a multiple of the pixel // size (4 bytes), even when stride is applied unsafe { @@ -180,6 +205,7 @@ impl BufferInterface for BufferImpl<'_> { // The surface will be presented when it is unlocked, which happens when the owned guard // is dropped. + self.native_window_buffer.take(); Ok(()) }