1use std::sync::{Arc, Mutex, OnceLock, mpsc};
5
6use winit::event::WindowEvent;
7use winit::window::Window;
8
9use accesskit::ActionRequest;
10use teksilo_render::Renderer;
11
12#[derive(Debug, thiserror::Error)]
14#[error("Surface error: {0}")]
15pub struct SurfaceRenderError(pub String);
16
17#[derive(Debug)]
22pub enum FrameOutcome {
23 Rendered,
25 Skipped,
32 NeedsReconfigure,
35 Error(SurfaceRenderError),
37}
38
39pub struct PlatformWindow {
42 window: Arc<Window>,
43 surface: wgpu::Surface<'static>,
44 surface_config: wgpu::SurfaceConfiguration,
45 renderer: Renderer,
46 scale_factor: f64,
47 a11y_adapter: Option<accesskit_winit::Adapter>,
48 a11y_action_rx: mpsc::Receiver<ActionRequest>,
50}
51
52#[derive(Clone)]
57struct SharedGpu {
58 adapter: wgpu::Adapter,
59 device: wgpu::Device,
60 queue: wgpu::Queue,
61}
62
63fn shared_instance() -> &'static wgpu::Instance {
70 static INSTANCE: OnceLock<wgpu::Instance> = OnceLock::new();
71 INSTANCE
72 .get_or_init(|| wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle()))
73}
74
75async fn shared_gpu_for(surface: &wgpu::Surface<'static>) -> SharedGpu {
97 static SHARED: Mutex<Option<SharedGpu>> = Mutex::new(None);
98
99 let cached = SHARED.lock().unwrap_or_else(|e| e.into_inner()).clone();
101 if let Some(gpu) = cached {
102 if !surface.get_capabilities(&gpu.adapter).formats.is_empty() {
105 return gpu;
106 }
107 }
108
109 let adapter = shared_instance()
110 .request_adapter(&wgpu::RequestAdapterOptions {
111 power_preference: wgpu::PowerPreference::default(),
112 compatible_surface: Some(surface),
113 force_fallback_adapter: false,
114 ..Default::default()
115 })
116 .await
117 .expect("no compatible wgpu adapter available");
118
119 let (device, queue) = adapter
120 .request_device(&wgpu::DeviceDescriptor {
121 label: Some("teksilo_device"),
122 required_features: wgpu::Features::empty(),
123 required_limits: wgpu::Limits::default(),
124 ..Default::default()
125 })
126 .await
127 .expect("wgpu device request failed");
128
129 let gpu = SharedGpu {
130 adapter,
131 device,
132 queue,
133 };
134 let mut slot = SHARED.lock().unwrap_or_else(|e| e.into_inner());
139 if slot.is_none() {
140 *slot = Some(gpu.clone());
141 }
142 gpu
143}
144
145impl PlatformWindow {
146 async fn surface_and_renderer(
151 window: &Arc<Window>,
152 ) -> (wgpu::Surface<'static>, wgpu::SurfaceConfiguration, Renderer) {
153 let size = window.inner_size();
154 let surface = shared_instance()
155 .create_surface(window.clone())
156 .expect("wgpu surface creation failed for the platform window");
157
158 let gpu = shared_gpu_for(&surface).await;
159
160 let surface_caps = surface.get_capabilities(&gpu.adapter);
161 let surface_format = surface_caps
165 .formats
166 .iter()
167 .find(|f| f.is_srgb())
168 .copied()
169 .or_else(|| surface_caps.formats.first().copied())
170 .unwrap_or(wgpu::TextureFormat::Rgba8UnormSrgb);
171
172 let surface_config = wgpu::SurfaceConfiguration {
173 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
174 format: surface_format,
175 width: size.width.max(1),
176 height: size.height.max(1),
177 present_mode: wgpu::PresentMode::Fifo,
178 alpha_mode: surface_caps
179 .alpha_modes
180 .first()
181 .copied()
182 .unwrap_or(wgpu::CompositeAlphaMode::Auto),
183 view_formats: vec![],
184 desired_maximum_frame_latency: 2,
185 color_space: wgpu::SurfaceColorSpace::Auto,
188 };
189 surface.configure(&gpu.device, &surface_config);
190
191 let renderer = Renderer::new(gpu.device, gpu.queue, surface_format);
194 (surface, surface_config, renderer)
195 }
196
197 pub async fn new_with_a11y(
200 window: Window,
201 event_loop: &winit::event_loop::ActiveEventLoop,
202 ) -> Self {
203 let window = Arc::new(window);
204 let scale_factor = window.scale_factor();
205 let (surface, surface_config, renderer) = Self::surface_and_renderer(&window).await;
206
207 let (action_tx, action_rx) = mpsc::channel();
209
210 let a11y_adapter = accesskit_winit::Adapter::with_direct_handlers(
211 event_loop,
212 &window,
213 TeksiloActivationHandler,
214 TeksiloActionHandler { tx: action_tx },
215 TeksiloDeactivationHandler,
216 );
217
218 window.set_visible(true);
220
221 Self {
222 window,
223 surface,
224 surface_config,
225 renderer,
226 scale_factor,
227 a11y_adapter: Some(a11y_adapter),
228 a11y_action_rx: action_rx,
229 }
230 }
231
232 pub async fn new(window: Window) -> Self {
234 let window = Arc::new(window);
235 let scale_factor = window.scale_factor();
236 let (surface, surface_config, renderer) = Self::surface_and_renderer(&window).await;
237 let (_action_tx, action_rx) = mpsc::channel();
238
239 Self {
240 window,
241 surface,
242 surface_config,
243 renderer,
244 scale_factor,
245 a11y_adapter: None,
246 a11y_action_rx: action_rx,
247 }
248 }
249
250 pub fn window(&self) -> &Window {
251 &self.window
252 }
253
254 pub fn window_arc(&self) -> Arc<Window> {
258 self.window.clone()
259 }
260
261 pub fn renderer(&self) -> &Renderer {
262 &self.renderer
263 }
264
265 pub fn renderer_mut(&mut self) -> &mut Renderer {
266 &mut self.renderer
267 }
268
269 pub fn scale_factor(&self) -> f64 {
270 self.scale_factor
271 }
272
273 pub fn set_scale_factor(&mut self, factor: f64) {
274 self.scale_factor = factor;
275 }
276
277 pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
279 if new_size.width > 0 && new_size.height > 0 {
280 self.surface_config.width = new_size.width;
281 self.surface_config.height = new_size.height;
282 self.surface
283 .configure(self.renderer.device(), &self.surface_config);
284 }
285 }
286
287 pub fn surface_size(&self) -> (u32, u32) {
289 (self.surface_config.width, self.surface_config.height)
290 }
291
292 pub fn reconfigure_surface(&mut self) {
295 self.surface
296 .configure(self.renderer.device(), &self.surface_config);
297 }
298
299 pub fn render_frame(
301 &mut self,
302 frame: &teksilo_canvas::RenderFrame,
303 clear_color: [f32; 4],
304 ) -> FrameOutcome {
305 let current = self.surface.get_current_texture();
306 let output = match current {
307 wgpu::CurrentSurfaceTexture::Success(tex)
308 | wgpu::CurrentSurfaceTexture::Suboptimal(tex) => tex,
309 wgpu::CurrentSurfaceTexture::Occluded | wgpu::CurrentSurfaceTexture::Timeout => {
310 return FrameOutcome::Skipped;
311 }
312 wgpu::CurrentSurfaceTexture::Outdated | wgpu::CurrentSurfaceTexture::Lost => {
313 return FrameOutcome::NeedsReconfigure;
314 }
315 other => return FrameOutcome::Error(SurfaceRenderError(format!("{other:?}"))),
316 };
317
318 let view = output
319 .texture
320 .create_view(&wgpu::TextureViewDescriptor::default());
321
322 let (w, h) = self.surface_size();
323 self.renderer
324 .render(frame, &view, self.scale_factor as f32, w, h, clear_color);
325
326 self.renderer.queue().present(output);
327 FrameOutcome::Rendered
328 }
329
330 pub fn capture_offscreen(
346 &mut self,
347 frame: &teksilo_canvas::RenderFrame,
348 clear_color: [f32; 4],
349 crop: Option<teksilo_canvas::Rect>,
350 ) -> (Vec<u8>, u32, u32) {
351 fn crop_rgba(
352 src: &[u8],
353 w: u32,
354 h: u32,
355 rect: teksilo_canvas::Rect,
356 ) -> (Vec<u8>, u32, u32) {
357 let x0 = (rect.x.floor().max(0.0) as u32).min(w);
358 let y0 = (rect.y.floor().max(0.0) as u32).min(h);
359 let x1 = ((rect.x + rect.width).ceil().max(0.0) as u32).min(w);
360 let y1 = ((rect.y + rect.height).ceil().max(0.0) as u32).min(h);
361 if x1 <= x0 || y1 <= y0 {
362 return (Vec::new(), 0, 0);
363 }
364 let cw = x1 - x0;
365 let ch = y1 - y0;
366 let mut out = Vec::with_capacity((cw * ch * 4) as usize);
367 for y in y0..y1 {
368 let row_start = ((y * w + x0) * 4) as usize;
369 let row_end = row_start + (cw * 4) as usize;
370 out.extend_from_slice(&src[row_start..row_end]);
371 }
372 (out, cw, ch)
373 }
374
375 let (w, h) = self.surface_size();
376 let format = self.surface_config.format;
377 debug_assert!(
383 matches!(
384 format,
385 wgpu::TextureFormat::Rgba8Unorm
386 | wgpu::TextureFormat::Rgba8UnormSrgb
387 | wgpu::TextureFormat::Bgra8Unorm
388 | wgpu::TextureFormat::Bgra8UnormSrgb
389 ),
390 "capture_offscreen: unsupported surface format {format:?} (expected 8-bit RGBA/BGRA)"
391 );
392 let texture = self
393 .renderer
394 .device()
395 .create_texture(&wgpu::TextureDescriptor {
396 label: Some("teksilo-automation capture"),
397 size: wgpu::Extent3d {
398 width: w,
399 height: h,
400 depth_or_array_layers: 1,
401 },
402 mip_level_count: 1,
403 sample_count: 1,
404 dimension: wgpu::TextureDimension::D2,
405 format,
406 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
407 view_formats: &[],
408 });
409 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
410 self.renderer
411 .render(frame, &view, self.scale_factor as f32, w, h, clear_color);
412 let mut bytes = teksilo_render::test_support::read_texture_rgba(
413 self.renderer.device(),
414 self.renderer.queue(),
415 &texture,
416 w,
417 h,
418 );
419 if matches!(
422 format,
423 wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb
424 ) {
425 for px in bytes.as_chunks_mut::<4>().0 {
426 px.swap(0, 2);
427 }
428 }
429 match crop {
430 Some(rect) => crop_rgba(&bytes, w, h, rect),
431 None => (bytes, w, h),
432 }
433 }
434
435 pub fn request_redraw(&self) {
436 self.window.request_redraw();
437 }
438
439 pub fn update_accessibility(&mut self, update: accesskit::TreeUpdate) {
441 if let Some(adapter) = &mut self.a11y_adapter {
442 adapter.update_if_active(|| update);
443 }
444 }
445
446 pub fn process_accessibility_event(&mut self, event: &WindowEvent) {
448 if let Some(adapter) = &mut self.a11y_adapter {
449 adapter.process_event(&self.window, event);
450 }
451 }
452
453 pub fn drain_accessibility_actions(&self) -> Vec<ActionRequest> {
455 let mut actions = Vec::new();
456 while let Ok(req) = self.a11y_action_rx.try_recv() {
457 actions.push(req);
458 }
459 actions
460 }
461}
462
463struct TeksiloActivationHandler;
468
469impl accesskit::ActivationHandler for TeksiloActivationHandler {
470 fn request_initial_tree(&mut self) -> Option<accesskit::TreeUpdate> {
471 let root = accesskit::Node::new(accesskit::Role::Window);
473 Some(accesskit::TreeUpdate {
474 nodes: vec![(accesskit::NodeId(0), root)],
475 tree: Some(accesskit::TreeInfo::new(accesskit::NodeId(0))),
476 tree_id: accesskit::TreeId::ROOT,
477 focus: accesskit::NodeId(0),
478 })
479 }
480}
481
482struct TeksiloActionHandler {
484 tx: mpsc::Sender<ActionRequest>,
485}
486
487impl accesskit::ActionHandler for TeksiloActionHandler {
488 fn do_action(&mut self, request: ActionRequest) {
489 let _ = self.tx.send(request);
490 }
491}
492
493struct TeksiloDeactivationHandler;
495
496impl accesskit::DeactivationHandler for TeksiloDeactivationHandler {
497 fn deactivate_accessibility(&mut self) {
498 }
500}