display_srv/
lib.rs

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