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)
}