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