use std::{ fmt, marker::Send, sync::mpsc, thread, time::{ Duration, Instant, }, }; use eframe::egui; mod capture; mod task; fn main() -> Result <(), Error> { let mut args = std::env::args (); let _exe_name = args.next (); match args.next ().as_deref () { None => main_egui (), Some ("capture") => { let mut capture = capture::Capture::new ().unwrap (); let mut buf = vec! [0u8; capture.size_image ()]; for _ in 0..10 { capture.wait_for_frame (&mut buf).unwrap (); } println! ("Measuring..."); let start = Instant::now (); for i in 0..30 { dbg! (i); capture.wait_for_frame (&mut buf).unwrap (); } let stop = Instant::now (); println! ("{} ms", (stop - start).as_millis ()); }, Some (_) => eprintln! ("Unknown subcommand"), } Ok (()) } struct App { img: egui::ColorImage, texture: Option , gui_frames: u64, camera_frames: u64, gui_fps: u64, camera_fps: u64, latency: Duration, send_to_ctl: mpsc::SyncSender , recv_at_gui: mpsc::Receiver , driver_thread: thread::JoinHandle <()>, requesting_frames: bool, next_stat_refresh: Instant, } impl App { fn new ( cc: &eframe::CreationContext<'_>, ) -> Self { let (send_to_ctl, recv_at_ctl) = mpsc::sync_channel (8); let (send_to_gui, recv_at_gui) = mpsc::sync_channel (8); let gui_ctx = cc.egui_ctx.clone (); let mut driver = Driver::new ( send_to_ctl.clone (), recv_at_ctl, gui_ctx, send_to_gui, ); let driver_thread = thread::spawn (move || driver.run ()); let img = egui::ColorImage::new ([1280,720], egui::Color32::TEMPORARY_COLOR); Self { img, texture: None, gui_frames: 0, camera_frames: 0, gui_fps: 0, camera_fps: 0, latency: Duration::from_millis(0), send_to_ctl, recv_at_gui, driver_thread, requesting_frames: true, next_stat_refresh: Instant::now (), } } } impl eframe::App for App { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { let texture = self.texture.get_or_insert_with(|| { ui.ctx ().load_texture ("my_image", self.img.clone (), Default::default()) }); if self.requesting_frames { self.send_to_ctl.send (MsgToController::GuiNeedsRgbaFrame).unwrap (); } while let Ok (msg) = self.recv_at_gui.try_recv() { match msg { MsgToGui::NewRgbaFrame ((data, gen)) => { self.latency = Instant::now () - gen; self.camera_frames += 1; texture.set (egui::ColorImage::from_rgba_premultiplied([1280,720], &data), Default::default ()); }, } } let texture: &egui::TextureHandle = texture; let now = Instant::now (); if now >= self.next_stat_refresh { self.gui_fps = self.gui_frames; self.camera_fps = self.camera_frames; self.gui_frames = 0; self.camera_frames = 0; self.next_stat_refresh += Duration::from_secs(1); } ui.heading (format! ("{0}, {1}, {2}", self.camera_fps, self.gui_fps, self.latency.as_millis ())); let available = ui.available_size() - egui::Vec2::new (0.0, 20.0); let tex_size = egui::Vec2::new (texture.size()[0] as f32, texture.size()[1] as f32); let scaled_width = available.y * tex_size.x / tex_size.y; let scaled_height = available.x * tex_size.y / tex_size.x; let size = if scaled_width <= available.x { egui::Vec2::new (scaled_width, available.y) } else { egui::Vec2::new (available.x, scaled_height) }; ui.image (texture, size); self.gui_frames += 1; ui.checkbox(&mut self.requesting_frames, "Run"); }); } } enum MsgToController { GuiNeedsRgbaFrame, GotCapture ((capture::Capture, JpegFrame)), DecodedJpegToRgba ((Vec , Instant)), } enum MsgToGui { NewRgbaFrame ((Vec , Instant)), } enum MsgFromController { RepaintGui ((Vec , Instant)), StartCapture, StartJpegDecoder (JpegFrame), } #[derive (Clone)] struct JpegFrame { data: Vec , time: Instant, } struct Controller { gui_rgba_gen: Instant, gui_needs_frame: bool, capture_is_running: bool, jpeg_decoder_is_running: bool, jpeg_needs_frame: bool, rgba_frame: (Vec , Instant), jpeg_frame: JpegFrame, } impl Controller { fn new (now: Instant) -> Self { Self { gui_needs_frame: false, gui_rgba_gen: now, capture_is_running: false, jpeg_decoder_is_running: false, jpeg_needs_frame: false, rgba_frame: (Default::default (), now), jpeg_frame: JpegFrame { data: Default::default (), time: now, }, } } fn handle_rgba_frame (&mut self, data: Vec , gen: Instant) { self.jpeg_decoder_is_running = false; self.rgba_frame = (data, gen); } fn handle_capture (&mut self, jpeg: JpegFrame) { self.capture_is_running = false; self.jpeg_frame = jpeg; } fn handle_gui_needs_frame (&mut self) { self.gui_needs_frame = true; } fn poll (&mut self) -> Option { if self.gui_needs_frame && self.rgba_frame.1 > self.gui_rgba_gen { self.gui_needs_frame = false; self.gui_rgba_gen = self.rgba_frame.1; Some (MsgFromController::RepaintGui (self.rgba_frame.clone ())) } else if self.gui_needs_frame && ! self.jpeg_decoder_is_running && self.jpeg_frame.time > self.rgba_frame.1 { self.jpeg_decoder_is_running = true; self.jpeg_needs_frame = false; Some (MsgFromController::StartJpegDecoder (self.jpeg_frame.clone ())) } else if self.jpeg_needs_frame && ! self.capture_is_running { self.capture_is_running = true; Some (MsgFromController::StartCapture) } else if self.gui_needs_frame && self.jpeg_frame.time == self.rgba_frame.1 { self.jpeg_needs_frame = true; None } else { None } } } struct Driver { send: mpsc::SyncSender , recv: mpsc::Receiver , gui_ctx: egui::Context, send_to_gui: mpsc::SyncSender , capture: Option , ctl: Controller, } impl Driver { fn new ( send: mpsc::SyncSender , recv: mpsc::Receiver , gui_ctx: egui::Context, send_to_gui: mpsc::SyncSender , ) -> Self { Self { send, recv, gui_ctx, send_to_gui, capture: Some (capture::Capture::new ().unwrap ()), ctl: Controller::new (Instant::now ()), } } fn run (&mut self) { let pool = rayon::ThreadPoolBuilder::new().build().unwrap (); loop { match self.recv.recv().unwrap () { MsgToController::DecodedJpegToRgba ((data, gen)) => { self.ctl.handle_rgba_frame (data, gen); }, MsgToController::GotCapture ((capture, jpeg)) => { self.ctl.handle_capture (jpeg); self.capture = Some (capture); }, MsgToController::GuiNeedsRgbaFrame => { self.ctl.handle_gui_needs_frame (); }, } while let Some (msg) = self.ctl.poll () { match msg { MsgFromController::RepaintGui (rgba_frame) => { self.send_to_gui.send (MsgToGui::NewRgbaFrame (rgba_frame)).unwrap (); self.gui_ctx.request_repaint(); }, MsgFromController::StartJpegDecoder (jpeg_frame) => { let send = self.send.clone (); pool.spawn (move || { let mut decoder = zune_jpeg::JpegDecoder::new_with_options (&jpeg_frame.data, zune_core::options::DecoderOptions::new_fast().jpeg_set_out_colorspace(zune_core::colorspace::ColorSpace::RGBA)); decoder.decode_headers().unwrap (); let mut rgba = vec![0u8;decoder.output_buffer_size().unwrap ()]; decoder.decode_into(&mut rgba).unwrap (); send.send (MsgToController::DecodedJpegToRgba ((rgba, jpeg_frame.time))).unwrap (); }); }, MsgFromController::StartCapture => { let mut capture = self.capture.take ().unwrap (); let send = self.send.clone (); pool.spawn (move || { let mut data = vec! [0u8; capture.size_image()]; capture.wait_for_frame(&mut data).unwrap (); let frame = JpegFrame { data, time: Instant::now (), }; send.send (MsgToController::GotCapture ((capture, frame))).unwrap (); }); } } } } } } fn main_egui () { let native_options = eframe::NativeOptions::default(); eframe::run_native("Five Five Five", native_options, Box::new(|cc| Box::new(App::new(cc)))).unwrap (); } #[derive (Debug, thiserror::Error)] enum Error { #[error ("capture")] Capture (#[from] capture::Error), #[error ("task")] Task (#[from] task::Error), }