use std::{ collections::HashMap, ops::Deref, sync::{Arc, Mutex, Weak}, thread, time::Duration, }; use egui::{ epaint::ImageDelta, load::{LoadError, SizedTexture, TextureLoader, TexturePoll}, Color32, ColorImage, TextureHandle, TextureOptions, }; use tokio::{sync::mpsc, time::timeout}; pub struct ImageProcessor { sender: mpsc::UnboundedSender>, } impl ImageProcessor { pub fn new() -> Self { let (sender, receiver) = mpsc::unbounded_channel(); thread::spawn(move || { tokio::runtime::Builder::new_current_thread() .enable_all() .build() .unwrap() .block_on(async move { let mut worker = ImageProcessorWorker { receiver, renderers: vec![], }; worker.run().await }) }); Self { sender } } pub fn add + 'static>( &self, renderer: R, params: R::Params, ) -> ([ImageHandle; N], ImageParams) { let states = renderer.sizes().map(ImageState::new); let handles = states.clone().map(|state| ImageHandle { size: state.size.map(|i| i as f32), data: state.sink, }); let images = renderer .sizes() .map(|[width, height]| ImageBuffer::new(width, height)); let sink = Arc::new(Mutex::new(params.clone())); let _ = self.sender.send(Box::new(ImageRendererWrapper { renderer, params: Arc::downgrade(&sink), images, states, })); let params = ImageParams { value: params, sink, }; (handles, params) } } struct ImageProcessorWorker { receiver: mpsc::UnboundedReceiver>, renderers: Vec>, } impl ImageProcessorWorker { async fn run(&mut self) { loop { if self.renderers.is_empty() { // if we have nothing to do, block until we have something to do if self.receiver.recv_many(&mut self.renderers, 64).await == 0 { // shutdown return; } while let Ok(renderer) = self.receiver.try_recv() { self.renderers.push(renderer); } } self.renderers .retain_mut(|renderer| renderer.try_update().is_ok()); // wait up to 10 ms for more renderers if timeout( Duration::from_millis(10), self.receiver.recv_many(&mut self.renderers, 64), ) .await .is_ok() { while let Ok(renderer) = self.receiver.try_recv() { self.renderers.push(renderer); } } } } } pub struct ImageBuffer { pub size: [usize; 2], pub pixels: Vec, } impl ImageBuffer { pub fn new(width: usize, height: usize) -> Self { Self { size: [width, height], pixels: vec![Color32::BLACK; width * height], } } pub fn clear(&mut self) { for pixel in self.pixels.iter_mut() { *pixel = Color32::BLACK; } } pub fn write(&mut self, coords: (usize, usize), pixel: Color32) { self.pixels[coords.1 * self.size[0] + coords.0] = pixel; } pub fn add(&mut self, coords: (usize, usize), pixel: Color32) { let index = coords.1 * self.size[0] + coords.0; let old = self.pixels[index]; self.pixels[index] = Color32::from_rgb( old.r() + pixel.r(), old.g() + pixel.g(), old.b() + pixel.b(), ); } pub fn changed(&self, image: &ColorImage) -> bool { image.pixels.iter().zip(&self.pixels).any(|(a, b)| a != b) } pub fn read(&self, image: &mut ColorImage) { image.pixels.copy_from_slice(&self.pixels); } } #[derive(Clone)] pub struct ImageHandle { size: [f32; 2], data: Arc>>>, } impl ImageHandle { fn pull(&mut self) -> Option> { self.data.lock().unwrap().take() } } pub struct ImageParams { value: T, sink: Arc>, } impl Deref for ImageParams { type Target = T; fn deref(&self) -> &Self::Target { &self.value } } impl ImageParams { pub fn write(&mut self, value: T) { if self.value != value { self.value = value.clone(); *self.sink.lock().unwrap() = value; } } } pub trait ImageRenderer: Send { type Params: Clone + Send; fn sizes(&self) -> [[usize; 2]; N]; fn render(&mut self, params: &Self::Params, images: &mut [ImageBuffer; N]); } #[derive(Clone)] struct ImageState { size: [usize; 2], buffers: [Arc; 2], last_buffer: usize, sink: Arc>>>, } impl ImageState { fn new(size: [usize; 2]) -> Self { let buffers = [ Arc::new(ColorImage::new(size, Color32::BLACK)), Arc::new(ColorImage::new(size, Color32::BLACK)), ]; let sink = buffers[0].clone(); Self { size, buffers, last_buffer: 0, sink: Arc::new(Mutex::new(Some(sink))), } } fn try_send_update(&mut self, image: &ImageBuffer) { let last = &self.buffers[self.last_buffer]; if !image.changed(last) { return; } let next_buffer = (self.last_buffer + 1) % self.buffers.len(); let next = &mut self.buffers[next_buffer]; image.read(Arc::make_mut(next)); self.last_buffer = next_buffer; self.sink.lock().unwrap().replace(next.clone()); } } struct ImageRendererWrapper> { renderer: R, params: Weak>, images: [ImageBuffer; N], states: [ImageState; N], } trait ImageRendererImpl: Send { fn try_update(&mut self) -> Result<(), ()>; } impl + Send> ImageRendererImpl for ImageRendererWrapper { fn try_update(&mut self) -> Result<(), ()> { let params = match self.params.upgrade() { Some(params) => params.lock().unwrap().clone(), None => { // the UI isn't using this anymore return Err(()); } }; self.renderer.render(¶ms, &mut self.images); for (state, image) in self.states.iter_mut().zip(&self.images) { state.try_send_update(image); } Ok(()) } } pub struct ImageTextureLoader { cache: Mutex)>>, } impl ImageTextureLoader { pub fn new(renderers: impl IntoIterator) -> Self { let mut cache = HashMap::new(); for (key, image) in renderers { cache.insert(key, (image, None)); } Self { cache: Mutex::new(cache), } } } impl TextureLoader for ImageTextureLoader { fn id(&self) -> &str { concat!(module_path!(), "ImageTextureLoader") } fn load( &self, ctx: &egui::Context, uri: &str, texture_options: TextureOptions, _size_hint: egui::SizeHint, ) -> Result { let mut cache = self.cache.lock().unwrap(); let Some((image, maybe_handle)) = cache.get_mut(uri) else { return Err(LoadError::NotSupported); }; if texture_options != TextureOptions::NEAREST { return Err(LoadError::Loading( "Only TextureOptions::NEAREST are supported".into(), )); } match (image.pull(), maybe_handle.as_ref()) { (Some(update), Some(handle)) => { let delta = ImageDelta::full(update, texture_options); ctx.tex_manager().write().set(handle.id(), delta); let texture = SizedTexture::new(handle, image.size); Ok(TexturePoll::Ready { texture }) } (Some(update), None) => { let handle = ctx.load_texture(uri, update, texture_options); let texture = SizedTexture::new(&handle, image.size); maybe_handle.replace(handle); Ok(TexturePoll::Ready { texture }) } (None, Some(handle)) => { let texture = SizedTexture::new(handle, image.size); Ok(TexturePoll::Ready { texture }) } (None, None) => { let size = image.size.into(); Ok(TexturePoll::Pending { size: Some(size) }) } } } fn forget(&self, uri: &str) { let _ = uri; } fn forget_all(&self) {} fn byte_size(&self) -> usize { self.cache .lock() .unwrap() .values() .map(|(image, _)| { let [width, height] = image.size; width as usize * height as usize * 4 }) .sum() } }