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