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 VramProcessor { sender: mpsc::UnboundedSender>, } impl VramProcessor { 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 = VramProcessorWorker { receiver, renderers: vec![], }; worker.run().await }) }); Self { sender } } pub fn add + 'static>( &self, renderer: R, ) -> ([VramImageHandle; N], VramParams) { let states = renderer.sizes().map(VramRenderImageState::new); let handles = states.clone().map(|state| VramImageHandle { size: state.size.map(|i| i as f32), data: state.sink, }); let images = renderer .sizes() .map(|[width, height]| VramImage::new(width, height)); let sink = Arc::new(Mutex::new(R::Params::default())); let _ = self.sender.send(Box::new(VramRendererWrapper { renderer, params: Arc::downgrade(&sink), images, states, })); let params = VramParams { value: R::Params::default(), sink, }; (handles, params) } } struct VramProcessorWorker { receiver: mpsc::UnboundedReceiver>, renderers: Vec>, } impl VramProcessorWorker { 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 VramImage { size: [usize; 2], shades: Vec, } impl VramImage { pub fn new(width: usize, height: usize) -> Self { Self { size: [width, height], shades: vec![0; width * height], } } pub fn write(&mut self, coords: (usize, usize), shade: u8) { self.shades[coords.1 * self.size[0] + coords.0] = shade; } pub fn changed(&self, image: &ColorImage) -> bool { image .pixels .iter() .map(|p| p.r()) .zip(&self.shades) .any(|(a, b)| a != *b) } pub fn read(&self, image: &mut ColorImage) { for (pixel, shade) in image.pixels.iter_mut().zip(&self.shades) { *pixel = Color32::from_gray(*shade); } } } #[derive(Clone)] pub struct VramImageHandle { size: [f32; 2], data: Arc>>>, } impl VramImageHandle { fn pull(&mut self) -> Option> { self.data.lock().unwrap().take() } } pub struct VramParams { value: T, sink: Arc>, } impl Deref for VramParams { type Target = T; fn deref(&self) -> &Self::Target { &self.value } } impl VramParams { pub fn write(&mut self, value: T) { if self.value != value { self.value = value.clone(); *self.sink.lock().unwrap() = value; } } } pub trait VramRenderer: Send { type Params: Clone + Default + Send; fn sizes(&self) -> [[usize; 2]; N]; fn render(&mut self, params: &Self::Params, images: &mut [VramImage; N]); } #[derive(Clone)] struct VramRenderImageState { size: [usize; 2], buffers: [Arc; 2], last_buffer: usize, sink: Arc>>>, } impl VramRenderImageState { 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: &VramImage) { 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 VramRendererWrapper> { renderer: R, params: Weak>, images: [VramImage; N], states: [VramRenderImageState; N], } trait VramRendererImpl: Send { fn try_update(&mut self) -> Result<(), ()>; } impl + Send> VramRendererImpl for VramRendererWrapper { 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 VramTextureLoader { cache: Mutex)>>, } impl VramTextureLoader { 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 VramTextureLoader { fn id(&self) -> &str { concat!(module_path!(), "VramTextureLoader") } 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() } }