319 lines
8.9 KiB
Rust
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(¶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<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()
|
|
}
|
|
}
|