1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
/* Copyright (C) 2024 DorotaC
* SPDX-License-Identifier: MIT OR Apache-2.0
*/
/*! Real-life example: using multiple threads with the manual API.
*
* This example demonstrates using the GPU to draw camera frames with zero copies, using the API with manual buffer management.
*
* Compared to glium_2, this example has more straightforward structure where buffers just go where there are needed. This freedom, however, makes it possible to create deadlocks or resource leaks.
*/
use std::io;
use std::sync::mpsc;
use std::thread;
use std::time::Instant;
use glium::Surface;
use vidi::{Config, FourCC, StreamManual};
use vidi_examples::{LoopHandler, build_egl_window, import_dmabuf_glutin};
#[derive(Debug, Clone, Copy)]
enum UserEvent {
WakeUp,
}
fn main() -> io::Result<()> {
// Setup the GL display stuff
let event_loop = winit::event_loop::EventLoop::<UserEvent>::with_user_event().build()
.map_err(io::Error::other)?;
let event_loop_proxy = event_loop.create_proxy();
let (_window, display, egl_display) = build_egl_window(&event_loop);
let (tx, rx) = mpsc::channel();
let (donetx, donerx) = mpsc::channel();
let cameras_list = vidi::actors::camera_list::spawn()?;
let cameras = cameras_list.cameras();
// TODO: don't crash on zero cameras, show info instead
let camera = cameras_list.create(&cameras[0].info.id)
.expect("No camera found")
.expect("Failed to get camera");
dbg!(camera.get_id());
// Allocate 4 buffers by default
let buffer_count = 4;
let camera = camera.acquire();
if let Ok(mut camera) = camera {
thread::spawn(move || {
let mut stream: StreamManual = camera.start_manual(
Config{fourcc: FourCC::new(b"YUYV"), width: 640, height: 480},
buffer_count,
).unwrap();
let mut buf = stream.start().unwrap();
// The .clone() here is needed because without it, the buffer would have been moved and the `buf` name invalidated outside of the loop. Then there would be no buffer to use to finish() the capture.
while let Ok((b, meta)) = stream.cycle_buffer(buf.clone()) {
println!(
"Buffer seq: {}, timestamp: {}",
meta.sequence,
meta.timestamp,
);
let _ = event_loop_proxy.send_event(UserEvent::WakeUp);
tx.send(b.clone()).unwrap();
// Wait for the buffer to be processed before returning it.
// If you try to return it (its clone) immediately, it will get locked and processing typically has to wait for all the in-between frames.
// This also provides automatic frame limiting: if the processing can't keep up, buffers will be enqueued later.
donerx.recv().unwrap();
buf = b;
}
match stream.finish(buf) {
Ok(()) => {},
Err((e, _buf, _stream)) => {println!("Error stopping stream: {:?}", e)},
};
});
}
let shader = crispy::shaders::yuv::YuyvToRgba::new(
&display,
(640, 480),
).unwrap();
event_loop.run_app(&mut LoopHandler {
user_event: move |_event| {
let t0 = Instant::now();
let mut target = display.draw();
let buf = rx.recv().unwrap();
let buf = buf.read(); // This is way too slow, over 100ms per frame.
let tex = import_dmabuf_glutin(
&display,
&egl_display,
&*buf,
// The buffer has two of R8 bytes per pixel.
(640*2, 480),
// This re-interprets the texture from YUYV to R8.
// GPUs don't seem to like YUYV textures, and crispy shaders aren't written with anything but R8 in mind.
// When in doubt, check shader documentation.
crispy::Format::R8,
).unwrap();
let t1 = Instant::now();
target.clear_color(0.0, 0.0, 0.0, 255.0);
shader.convert(
&display,
tex.get_texture(),
&mut target,
crispy::shaders::yuv::ColorSpace::BT709,
crispy::shaders::yuv::Gamma::Identity,
).unwrap();
target.finish().unwrap();
donetx.send(()).unwrap();
println!(
"ms: {}\t (buffer) + {}\t (UI)",
t1.duration_since(t0).as_millis(),
t1.elapsed().as_millis()
);
}
})
.map_err(io::Error::other)
}