lemur/src/vram.rs

319 lines
8.9 KiB
Rust

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<Box<dyn VramRendererImpl>>,
}
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<const N: usize, R: VramRenderer<N> + 'static>(
&self,
renderer: R,
) -> ([VramImageHandle; N], VramParams<R::Params>) {
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<Box<dyn VramRendererImpl>>,
renderers: Vec<Box<dyn VramRendererImpl>>,
}
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<u8>,
}
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<Mutex<Option<Arc<ColorImage>>>>,
}
impl VramImageHandle {
fn pull(&mut self) -> Option<Arc<ColorImage>> {
self.data.lock().unwrap().take()
}
}
pub struct VramParams<T> {
value: T,
sink: Arc<Mutex<T>>,
}
impl<T> Deref for VramParams<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl<T: Clone + Eq> VramParams<T> {
pub fn write(&mut self, value: T) {
if self.value != value {
self.value = value.clone();
*self.sink.lock().unwrap() = value;
}
}
}
pub trait VramRenderer<const N: usize>: 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<ColorImage>; 2],
last_buffer: usize,
sink: Arc<Mutex<Option<Arc<ColorImage>>>>,
}
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<const N: usize, R: VramRenderer<N>> {
renderer: R,
params: Weak<Mutex<R::Params>>,
images: [VramImage; N],
states: [VramRenderImageState; N],
}
trait VramRendererImpl: Send {
fn try_update(&mut self) -> Result<(), ()>;
}
impl<const N: usize, R: VramRenderer<N> + Send> VramRendererImpl for VramRendererWrapper<N, R> {
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(&params, &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<HashMap<String, (VramImageHandle, Option<TextureHandle>)>>,
}
impl VramTextureLoader {
pub fn new(renderers: impl IntoIterator<Item = (String, VramImageHandle)>) -> 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<TexturePoll, LoadError> {
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()
}
}