400 lines
11 KiB
Rust
400 lines
11 KiB
Rust
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 <egui::TextureHandle>,
|
|
gui_frames: u64,
|
|
camera_frames: u64,
|
|
gui_fps: u64,
|
|
camera_fps: u64,
|
|
latency: Duration,
|
|
|
|
send_to_ctl: mpsc::SyncSender <MsgToController>,
|
|
recv_at_gui: mpsc::Receiver <MsgToGui>,
|
|
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 <u8>, Instant)),
|
|
}
|
|
|
|
enum MsgToGui
|
|
{
|
|
NewRgbaFrame ((Vec <u8>, Instant)),
|
|
}
|
|
|
|
enum MsgFromController
|
|
{
|
|
RepaintGui ((Vec <u8>, Instant)),
|
|
StartCapture,
|
|
StartJpegDecoder (JpegFrame),
|
|
}
|
|
|
|
#[derive (Clone)]
|
|
struct JpegFrame
|
|
{
|
|
data: Vec <u8>,
|
|
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 <u8>, 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 <u8>, 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 <MsgFromController>
|
|
{
|
|
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 <MsgToController>,
|
|
recv: mpsc::Receiver <MsgToController>,
|
|
|
|
gui_ctx: egui::Context,
|
|
send_to_gui: mpsc::SyncSender <MsgToGui>,
|
|
|
|
capture: Option <capture::Capture>,
|
|
|
|
ctl: Controller,
|
|
}
|
|
|
|
impl Driver
|
|
{
|
|
fn new (
|
|
send: mpsc::SyncSender <MsgToController>,
|
|
recv: mpsc::Receiver <MsgToController>,
|
|
gui_ctx: egui::Context,
|
|
send_to_gui: mpsc::SyncSender <MsgToGui>,
|
|
) -> 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),
|
|
}
|