display_srv/
lib.rs

1#![feature(portable_simd)]
2#![feature(lock_value_accessors)]
3
4use std::{
5    sync::{Mutex, OnceLock, RwLock},
6    time::{Duration, Instant},
7};
8
9use secgate::util::HandleMgr;
10use tracing::Level;
11use twizzler_abi::object::ObjID;
12use twizzler_display::{BufferObject, Rect, WindowConfig};
13use twizzler_rt_abi::{error::TwzError, Result};
14use virtio_gpu::{DeviceWrapper, TwizzlerTransport};
15
16static DISPLAY_INFO: OnceLock<DisplayInfo> = OnceLock::new();
17
18struct DisplayClient {
19    window: BufferObject,
20    config: RwLock<WindowConfig>,
21    new_config: RwLock<Option<WindowConfig>>,
22}
23
24#[allow(dead_code)]
25struct DisplayInfo {
26    gpu: DeviceWrapper<TwizzlerTransport>,
27    fb: (*mut u32, usize),
28    width: u32,
29    height: u32,
30    handles: Mutex<HandleMgr<DisplayClient>>,
31    buffer: BufferObject,
32}
33
34unsafe impl Send for DisplayInfo {}
35unsafe impl Sync for DisplayInfo {}
36
37#[secgate::entry(lib = "twizzler-display")]
38pub fn start_display() -> Result<()> {
39    tracing::subscriber::set_global_default(
40        tracing_subscriber::fmt()
41            .with_max_level(Level::INFO)
42            .without_time()
43            .finish(),
44    )
45    .unwrap();
46
47    if DISPLAY_INFO.get().is_some() {
48        tracing::info!("cannot call start_display more than once");
49        return Err(TwzError::NOT_SUPPORTED);
50    }
51
52    let (send, _) = std::sync::mpsc::channel();
53    let Ok(gpu) = virtio_gpu::get_device(send) else {
54        tracing::error!("failed to setup display, no supported display found");
55        return Err(TwzError::NOT_SUPPORTED);
56    };
57
58    let Ok(current_info) = gpu.with_device(|g| g.resolution()) else {
59        tracing::error!("failed to get display resolution");
60        return Err(TwzError::NOT_SUPPORTED);
61    };
62
63    tracing::info!(
64        "setting up display with resolution {}x{}",
65        current_info.0,
66        current_info.1
67    );
68
69    let Ok(fb) = gpu.with_device(|g| g.setup_framebuffer().map(|p| (p.as_mut_ptr(), p.len())))
70    else {
71        tracing::error!("failed to setup framebuffer");
72        return Err(TwzError::NOT_SUPPORTED);
73    };
74    let fb = (fb.0.cast(), fb.1 / 4);
75
76    let _ = DISPLAY_INFO.set(DisplayInfo {
77        gpu,
78        fb,
79        width: current_info.0,
80        height: current_info.1,
81        handles: Mutex::new(HandleMgr::new(None)),
82        buffer: BufferObject::create_new(current_info.0, current_info.1)?,
83    });
84
85    std::thread::spawn(compositor_thread);
86    std::thread::spawn(render_thread);
87
88    Ok(())
89}
90
91impl DisplayClient {
92    fn compute_client_damage(
93        &self,
94        config: WindowConfig,
95        client_damage: &mut Vec<Rect>,
96        damage: &[Rect],
97    ) {
98        for damage in damage {
99            if damage.x >= config.x
100                && damage.x < config.x + config.w
101                && damage.y >= config.y
102                && damage.y < config.y + config.h
103            {
104                client_damage.push(Rect::new(
105                    damage.x - config.x,
106                    damage.y - config.y,
107                    damage.w.min(config.w - (damage.x - config.x)),
108                    damage.h.min(config.h - (damage.y - config.y)),
109                ));
110            }
111        }
112    }
113}
114
115fn render_thread() {
116    tracing::debug!("render thread started");
117    let info = DISPLAY_INFO.get().unwrap();
118    let fb = unsafe { core::slice::from_raw_parts_mut(info.fb.0, info.fb.1) };
119    loop {
120        let start = Instant::now();
121
122        // We're the "compositor" here
123        if info.buffer.has_data_for_read() {
124            info.buffer.read_buffer(|buf, w, h| {
125                let len = (w * h) as usize;
126                assert_eq!(len, buf.len());
127                assert!(fb.len() >= len, "{} {}, {} {}", fb.len(), len, w, h);
128                for (i, damage) in buf.damage_rects().iter().enumerate() {
129                    let damage = Rect::new(
130                        damage.x,
131                        damage.y,
132                        damage.w.min(w - damage.x),
133                        damage.h.min(h - damage.y),
134                    );
135                    tracing::debug!("render screen damage ({}): {:?}", i, damage);
136                    for y in damage.y..(damage.y + damage.h) {
137                        let start = (y * w + damage.x) as usize;
138                        let src = &buf.as_slice()[start..(start + damage.w as usize)];
139                        let dst = &mut fb[start..(start + damage.w as usize)];
140                        dst.copy_from_slice(src);
141                    }
142                }
143            });
144            info.buffer.read_done(info.width, info.height);
145            info.gpu.with_device(|g| g.flush().unwrap());
146        }
147
148        let elapsed = start.elapsed();
149        let remaining = Duration::from_millis(16).saturating_sub(elapsed);
150        if elapsed.as_millis() > 0 {
151            tracing::trace!(
152                "render took {}ms, sleep for {}ms",
153                elapsed.as_millis(),
154                remaining.as_millis()
155            );
156        }
157        std::thread::sleep(remaining);
158    }
159}
160
161fn compositor_thread() {
162    tracing::debug!("compositor thread started");
163    let info = DISPLAY_INFO.get().unwrap();
164    let mut last_window_count = 0;
165    let mut done_fill = Instant::now();
166    let mut done_comp = Instant::now();
167    let mut damage = Vec::new();
168    let mut client_damage = Vec::new();
169    loop {
170        damage.clear();
171        let start = Instant::now();
172
173        let mut clients = Vec::new();
174        let handles = info.handles.lock().unwrap();
175        for h in handles.handles() {
176            if let Some(new_config) = h.2.new_config.replace(None).unwrap() {
177                let old_config = h.2.config.replace(new_config).unwrap();
178                tracing::debug!("window changed: {:?} => {:?}", old_config, new_config);
179                damage.push(Rect::from(new_config));
180                damage.push(Rect::from(old_config));
181            }
182            let config = *h.2.config.read().unwrap();
183            if h.2.window.has_data_for_read() {
184                h.2.window.read_buffer(|b, _, _| {
185                    for dmg in b.damage_rects() {
186                        damage.push(Rect::new(
187                            config.x + dmg.x,
188                            config.y + dmg.y,
189                            dmg.w.min(config.w - dmg.x),
190                            dmg.h.min(config.h - dmg.y),
191                        ));
192                    }
193                });
194            }
195            clients.push((h.2, config));
196        }
197        let done_count = Instant::now();
198
199        clients.sort_by_key(|c| c.1.z);
200
201        if clients.len() != last_window_count {
202            tracing::debug!(
203                "window count changed from {} to {}",
204                last_window_count,
205                clients.len()
206            );
207            damage.clear();
208            damage.push(Rect::full());
209        }
210
211        if !damage.is_empty() {
212            tracing::debug!("damage = {:?}", damage);
213            info.buffer.update_buffer(|mut fbbuf, fbw, fbh| {
214                if clients.len() != last_window_count {
215                    fbbuf.as_slice_mut().fill(0);
216                    fbbuf.damage(Rect::full());
217                } else {
218                    for dmg in damage.drain(..) {
219                        fbbuf.damage(dmg);
220                    }
221                }
222                done_fill = Instant::now();
223
224                for client in &clients {
225                    client_damage.clear();
226                    client.0.compute_client_damage(
227                        client.1,
228                        &mut client_damage,
229                        fbbuf.damage_rects(),
230                    );
231                    if !client_damage.is_empty() {
232                        client.0.window.read_buffer(|buf, bufw, bufh| {
233                            for damage in &client_damage {
234                                let mut damage = Rect::new(
235                                    damage.x,
236                                    damage.y,
237                                    damage.w.min(bufw - damage.x),
238                                    damage.h.min(bufh - damage.y),
239                                );
240                                tracing::debug!("client damage {:?}", damage);
241
242                                if client.1.y + damage.y >= fbh {
243                                    continue;
244                                }
245                                if client.1.x + damage.x >= fbw {
246                                    continue;
247                                }
248                                if client.1.y + damage.h >= fbh {
249                                    damage.h = fbh - client.1.y;
250                                }
251                                if client.1.x + damage.w >= fbw {
252                                    damage.w = fbw - client.1.x;
253                                }
254
255                                // Copy each line. In the future, we can do alpha blending.
256                                for y in damage.y..(damage.y + damage.h) {
257                                    let src = &buf.as_slice()[(y * bufw) as usize..];
258                                    let dst = &mut fbbuf.as_slice_mut()
259                                        [((y + client.1.y) * fbw + client.1.x) as usize..];
260                                    (&mut dst[0..(damage.w as usize)])
261                                        .copy_from_slice(&src[0..(damage.w as usize)]);
262                                }
263                            }
264                        });
265                    }
266
267                    if client.0.window.has_data_for_read() {
268                        client.0.window.read_done(client.1.w, client.1.h);
269                    }
270                }
271
272                done_comp = Instant::now();
273            });
274            info.buffer.flip();
275        }
276        last_window_count = clients.len();
277        drop(clients);
278        drop(handles);
279
280        let elapsed = start.elapsed();
281        let remaining = Duration::from_millis(16).saturating_sub(elapsed);
282        if elapsed.as_millis() > 0 {
283            tracing::trace!(
284                "composite took {}ms, sleep for {}ms : {} {} {}",
285                elapsed.as_millis(),
286                remaining.as_millis(),
287                (done_count - start).as_micros(),
288                (done_fill - done_count).as_micros(),
289                (done_comp - done_fill).as_micros(),
290            );
291        }
292        std::thread::sleep(remaining);
293    }
294}
295
296#[secgate::entry(lib = "twizzler-display")]
297pub fn create_window(winfo: WindowConfig) -> Result<(ObjID, u32)> {
298    let call_info = secgate::get_caller().ok_or(TwzError::INVALID_ARGUMENT)?;
299    let info = DISPLAY_INFO.get().unwrap();
300    let bo = BufferObject::create_new(winfo.w, winfo.h)?;
301    let mut handles = info.handles.lock().unwrap();
302    let handle = handles
303        .insert(
304            call_info.source_context().unwrap_or(0.into()),
305            DisplayClient {
306                window: bo.clone(),
307                config: RwLock::new(winfo),
308                new_config: RwLock::new(None),
309            },
310        )
311        .ok_or(TwzError::INVALID_ARGUMENT)?;
312    tracing::debug!("created window {:?} as handle {}", winfo, handle);
313    Ok((bo.id(), handle))
314}
315
316#[secgate::entry(lib = "twizzler-display")]
317pub fn drop_window(handle: u32) -> Result<()> {
318    tracing::debug!("dropping window {}", handle);
319    let call_info = secgate::get_caller().ok_or(TwzError::INVALID_ARGUMENT)?;
320    let info = DISPLAY_INFO.get().unwrap();
321    let mut handles = info.handles.lock().unwrap();
322    handles
323        .remove(call_info.source_context().unwrap_or(0.into()), handle)
324        .ok_or(TwzError::INVALID_ARGUMENT)?;
325    Ok(())
326}
327
328#[secgate::entry(lib = "twizzler-display")]
329pub fn reconfigure_window(handle: u32, wconfig: WindowConfig) -> Result<()> {
330    tracing::debug!("reconfiguring window {} => {:?}", handle, wconfig);
331    let call_info = secgate::get_caller().ok_or(TwzError::INVALID_ARGUMENT)?;
332    let info = DISPLAY_INFO.get().unwrap();
333    let handles = info.handles.lock().unwrap();
334    let client = handles
335        .lookup(call_info.source_context().unwrap_or(0.into()), handle)
336        .ok_or(TwzError::INVALID_ARGUMENT)?;
337    *client.new_config.write().unwrap() = Some(wconfig);
338    Ok(())
339}
340
341#[secgate::entry(lib = "twizzler-display")]
342pub fn get_window_config(handle: u32) -> Result<WindowConfig> {
343    let info = DISPLAY_INFO.get().unwrap();
344    let call_info = secgate::get_caller().ok_or(TwzError::INVALID_ARGUMENT)?;
345    let handles = info.handles.lock().unwrap();
346    let client = handles
347        .lookup(call_info.source_context().unwrap_or(0.into()), handle)
348        .ok_or(TwzError::INVALID_ARGUMENT)?;
349    let w = client.config.read().unwrap();
350    Ok(*w)
351}
352
353#[secgate::entry(lib = "twizzler-display")]
354pub fn get_display_info() -> Result<WindowConfig> {
355    let info = DISPLAY_INFO.get().unwrap();
356    Ok(WindowConfig {
357        w: info.width,
358        h: info.height,
359        x: 0,
360        y: 0,
361        z: 0,
362    })
363}