1use std::sync::Arc;
5use std::sync::mpsc;
6
7use winit::event::WindowEvent;
8use winit::window::Window;
9
10use accesskit::ActionRequest;
11use teksilo_render::Renderer;
12
13#[derive(Debug, thiserror::Error)]
15#[error("Surface error: {0}")]
16pub struct SurfaceRenderError(pub String);
17
18#[derive(Debug)]
23pub enum FrameOutcome {
24 Rendered,
26 Skipped,
33 NeedsReconfigure,
36 Error(SurfaceRenderError),
38}
39
40pub struct PlatformWindow {
43 window: Arc<Window>,
44 surface: wgpu::Surface<'static>,
45 surface_config: wgpu::SurfaceConfiguration,
46 renderer: Renderer,
47 scale_factor: f64,
48 a11y_adapter: Option<accesskit_winit::Adapter>,
49 a11y_action_rx: mpsc::Receiver<ActionRequest>,
51}
52
53impl PlatformWindow {
54 pub async fn new_with_a11y(
57 window: Window,
58 event_loop: &winit::event_loop::ActiveEventLoop,
59 ) -> Self {
60 let window = Arc::new(window);
61 let size = window.inner_size();
62 let scale_factor = window.scale_factor();
63
64 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
65
66 let surface = instance
67 .create_surface(window.clone())
68 .expect("wgpu surface creation failed for the platform window");
69
70 let adapter = instance
71 .request_adapter(&wgpu::RequestAdapterOptions {
72 power_preference: wgpu::PowerPreference::default(),
73 compatible_surface: Some(&surface),
74 force_fallback_adapter: false,
75 ..Default::default()
76 })
77 .await
78 .expect("no compatible wgpu adapter available");
79
80 let (device, queue) = adapter
81 .request_device(&wgpu::DeviceDescriptor {
82 label: Some("teksilo_device"),
83 required_features: wgpu::Features::empty(),
84 required_limits: wgpu::Limits::default(),
85 ..Default::default()
86 })
87 .await
88 .expect("wgpu device request failed");
89
90 let surface_caps = surface.get_capabilities(&adapter);
91 let surface_format = surface_caps
95 .formats
96 .iter()
97 .find(|f| f.is_srgb())
98 .copied()
99 .or_else(|| surface_caps.formats.first().copied())
100 .unwrap_or(wgpu::TextureFormat::Rgba8UnormSrgb);
101
102 let surface_config = wgpu::SurfaceConfiguration {
103 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
104 format: surface_format,
105 width: size.width.max(1),
106 height: size.height.max(1),
107 present_mode: wgpu::PresentMode::Fifo,
108 alpha_mode: surface_caps
109 .alpha_modes
110 .first()
111 .copied()
112 .unwrap_or(wgpu::CompositeAlphaMode::Auto),
113 view_formats: vec![],
114 desired_maximum_frame_latency: 2,
115 color_space: wgpu::SurfaceColorSpace::Auto,
118 };
119 surface.configure(&device, &surface_config);
120
121 let renderer = Renderer::new(device, queue, surface_format);
122
123 let (action_tx, action_rx) = mpsc::channel();
125
126 let a11y_adapter = accesskit_winit::Adapter::with_direct_handlers(
127 event_loop,
128 &window,
129 TeksiloActivationHandler,
130 TeksiloActionHandler { tx: action_tx },
131 TeksiloDeactivationHandler,
132 );
133
134 window.set_visible(true);
136
137 Self {
138 window,
139 surface,
140 surface_config,
141 renderer,
142 scale_factor,
143 a11y_adapter: Some(a11y_adapter),
144 a11y_action_rx: action_rx,
145 }
146 }
147
148 pub async fn new(window: Window) -> Self {
150 let window = Arc::new(window);
151 let size = window.inner_size();
152 let scale_factor = window.scale_factor();
153
154 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
155 let surface = instance
156 .create_surface(window.clone())
157 .expect("wgpu surface creation failed for the platform window");
158
159 let gpu_adapter = instance
160 .request_adapter(&wgpu::RequestAdapterOptions {
161 power_preference: wgpu::PowerPreference::default(),
162 compatible_surface: Some(&surface),
163 force_fallback_adapter: false,
164 ..Default::default()
165 })
166 .await
167 .expect("no compatible wgpu adapter available");
168
169 let (device, queue) = gpu_adapter
170 .request_device(&wgpu::DeviceDescriptor {
171 label: Some("teksilo_device"),
172 required_features: wgpu::Features::empty(),
173 required_limits: wgpu::Limits::default(),
174 ..Default::default()
175 })
176 .await
177 .expect("wgpu device request failed");
178
179 let surface_caps = surface.get_capabilities(&gpu_adapter);
180 let surface_format = surface_caps
183 .formats
184 .iter()
185 .find(|f| f.is_srgb())
186 .copied()
187 .or_else(|| surface_caps.formats.first().copied())
188 .unwrap_or(wgpu::TextureFormat::Rgba8UnormSrgb);
189
190 let surface_config = wgpu::SurfaceConfiguration {
191 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
192 format: surface_format,
193 width: size.width.max(1),
194 height: size.height.max(1),
195 present_mode: wgpu::PresentMode::Fifo,
196 alpha_mode: surface_caps
197 .alpha_modes
198 .first()
199 .copied()
200 .unwrap_or(wgpu::CompositeAlphaMode::Auto),
201 view_formats: vec![],
202 desired_maximum_frame_latency: 2,
203 color_space: wgpu::SurfaceColorSpace::Auto,
206 };
207 surface.configure(&device, &surface_config);
208
209 let renderer = Renderer::new(device, queue, surface_format);
210 let (_action_tx, action_rx) = mpsc::channel();
211
212 Self {
213 window,
214 surface,
215 surface_config,
216 renderer,
217 scale_factor,
218 a11y_adapter: None,
219 a11y_action_rx: action_rx,
220 }
221 }
222
223 pub fn window(&self) -> &Window {
224 &self.window
225 }
226
227 pub fn window_arc(&self) -> Arc<Window> {
231 self.window.clone()
232 }
233
234 pub fn renderer(&self) -> &Renderer {
235 &self.renderer
236 }
237
238 pub fn renderer_mut(&mut self) -> &mut Renderer {
239 &mut self.renderer
240 }
241
242 pub fn scale_factor(&self) -> f64 {
243 self.scale_factor
244 }
245
246 pub fn set_scale_factor(&mut self, factor: f64) {
247 self.scale_factor = factor;
248 }
249
250 pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
252 if new_size.width > 0 && new_size.height > 0 {
253 self.surface_config.width = new_size.width;
254 self.surface_config.height = new_size.height;
255 self.surface
256 .configure(self.renderer.device(), &self.surface_config);
257 }
258 }
259
260 pub fn surface_size(&self) -> (u32, u32) {
262 (self.surface_config.width, self.surface_config.height)
263 }
264
265 pub fn reconfigure_surface(&mut self) {
268 self.surface
269 .configure(self.renderer.device(), &self.surface_config);
270 }
271
272 pub fn render_frame(
274 &mut self,
275 frame: &teksilo_canvas::RenderFrame,
276 clear_color: [f32; 4],
277 ) -> FrameOutcome {
278 let current = self.surface.get_current_texture();
279 let output = match current {
280 wgpu::CurrentSurfaceTexture::Success(tex)
281 | wgpu::CurrentSurfaceTexture::Suboptimal(tex) => tex,
282 wgpu::CurrentSurfaceTexture::Occluded | wgpu::CurrentSurfaceTexture::Timeout => {
283 return FrameOutcome::Skipped;
284 }
285 wgpu::CurrentSurfaceTexture::Outdated | wgpu::CurrentSurfaceTexture::Lost => {
286 return FrameOutcome::NeedsReconfigure;
287 }
288 other => return FrameOutcome::Error(SurfaceRenderError(format!("{other:?}"))),
289 };
290
291 let view = output
292 .texture
293 .create_view(&wgpu::TextureViewDescriptor::default());
294
295 let (w, h) = self.surface_size();
296 self.renderer
297 .render(frame, &view, self.scale_factor as f32, w, h, clear_color);
298
299 self.renderer.queue().present(output);
300 FrameOutcome::Rendered
301 }
302
303 pub fn capture_offscreen(
319 &mut self,
320 frame: &teksilo_canvas::RenderFrame,
321 clear_color: [f32; 4],
322 crop: Option<teksilo_canvas::Rect>,
323 ) -> (Vec<u8>, u32, u32) {
324 fn crop_rgba(
325 src: &[u8],
326 w: u32,
327 h: u32,
328 rect: teksilo_canvas::Rect,
329 ) -> (Vec<u8>, u32, u32) {
330 let x0 = (rect.x.floor().max(0.0) as u32).min(w);
331 let y0 = (rect.y.floor().max(0.0) as u32).min(h);
332 let x1 = ((rect.x + rect.width).ceil().max(0.0) as u32).min(w);
333 let y1 = ((rect.y + rect.height).ceil().max(0.0) as u32).min(h);
334 if x1 <= x0 || y1 <= y0 {
335 return (Vec::new(), 0, 0);
336 }
337 let cw = x1 - x0;
338 let ch = y1 - y0;
339 let mut out = Vec::with_capacity((cw * ch * 4) as usize);
340 for y in y0..y1 {
341 let row_start = ((y * w + x0) * 4) as usize;
342 let row_end = row_start + (cw * 4) as usize;
343 out.extend_from_slice(&src[row_start..row_end]);
344 }
345 (out, cw, ch)
346 }
347
348 let (w, h) = self.surface_size();
349 let format = self.surface_config.format;
350 debug_assert!(
356 matches!(
357 format,
358 wgpu::TextureFormat::Rgba8Unorm
359 | wgpu::TextureFormat::Rgba8UnormSrgb
360 | wgpu::TextureFormat::Bgra8Unorm
361 | wgpu::TextureFormat::Bgra8UnormSrgb
362 ),
363 "capture_offscreen: unsupported surface format {format:?} (expected 8-bit RGBA/BGRA)"
364 );
365 let texture = self
366 .renderer
367 .device()
368 .create_texture(&wgpu::TextureDescriptor {
369 label: Some("teksilo-automation capture"),
370 size: wgpu::Extent3d {
371 width: w,
372 height: h,
373 depth_or_array_layers: 1,
374 },
375 mip_level_count: 1,
376 sample_count: 1,
377 dimension: wgpu::TextureDimension::D2,
378 format,
379 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
380 view_formats: &[],
381 });
382 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
383 self.renderer
384 .render(frame, &view, self.scale_factor as f32, w, h, clear_color);
385 let mut bytes = teksilo_render::test_support::read_texture_rgba(
386 self.renderer.device(),
387 self.renderer.queue(),
388 &texture,
389 w,
390 h,
391 );
392 if matches!(
395 format,
396 wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb
397 ) {
398 for px in bytes.as_chunks_mut::<4>().0 {
399 px.swap(0, 2);
400 }
401 }
402 match crop {
403 Some(rect) => crop_rgba(&bytes, w, h, rect),
404 None => (bytes, w, h),
405 }
406 }
407
408 pub fn request_redraw(&self) {
409 self.window.request_redraw();
410 }
411
412 pub fn update_accessibility(&mut self, update: accesskit::TreeUpdate) {
414 if let Some(adapter) = &mut self.a11y_adapter {
415 adapter.update_if_active(|| update);
416 }
417 }
418
419 pub fn process_accessibility_event(&mut self, event: &WindowEvent) {
421 if let Some(adapter) = &mut self.a11y_adapter {
422 adapter.process_event(&self.window, event);
423 }
424 }
425
426 pub fn drain_accessibility_actions(&self) -> Vec<ActionRequest> {
428 let mut actions = Vec::new();
429 while let Ok(req) = self.a11y_action_rx.try_recv() {
430 actions.push(req);
431 }
432 actions
433 }
434}
435
436struct TeksiloActivationHandler;
441
442impl accesskit::ActivationHandler for TeksiloActivationHandler {
443 fn request_initial_tree(&mut self) -> Option<accesskit::TreeUpdate> {
444 let root = accesskit::Node::new(accesskit::Role::Window);
446 Some(accesskit::TreeUpdate {
447 nodes: vec![(accesskit::NodeId(0), root)],
448 tree: Some(accesskit::TreeInfo::new(accesskit::NodeId(0))),
449 tree_id: accesskit::TreeId::ROOT,
450 focus: accesskit::NodeId(0),
451 })
452 }
453}
454
455struct TeksiloActionHandler {
457 tx: mpsc::Sender<ActionRequest>,
458}
459
460impl accesskit::ActionHandler for TeksiloActionHandler {
461 fn do_action(&mut self, request: ActionRequest) {
462 let _ = self.tx.send(request);
463 }
464}
465
466struct TeksiloDeactivationHandler;
468
469impl accesskit::DeactivationHandler for TeksiloDeactivationHandler {
470 fn deactivate_accessibility(&mut self) {
471 }
473}