1use std::sync::Arc;
9use std::time::{Duration, Instant};
10
11use cotis::renders::{CotisRenderer, CotisRendererAsync, RenderCompatibleWith};
12use cotis_defaults::render_commands::render_t_list::{IsRenderList, RenderTList};
13use cotis_utils::interactivity::keyboard::KeyboardKey;
14use cotis_utils::interactivity::mouse::MouseButton;
15use cotis_utils::math::{Dimensions, Vector2};
16use winit::application::ApplicationHandler;
17use winit::dpi::LogicalSize;
18use winit::error::EventLoopError;
19use winit::event::{ElementState, MouseScrollDelta, WindowEvent};
20use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
21use winit::keyboard::{KeyCode, PhysicalKey};
22use winit::platform::pump_events::{EventLoopExtPumpEvents, PumpStatus};
23use winit::window::{Window, WindowId};
24
25use crate::device::GpuDevice;
26use crate::drawable::{WgpuDrawContext, WgpuDrawable};
27use crate::error::WgpuBackendError;
28use crate::input::InputSnapshot;
29use crate::pipeline::GeometryBatch;
30use crate::surface::SurfaceState;
31use crate::text::TextState;
32
33#[derive(Debug, Clone)]
35pub struct WindowConfig {
36 pub title: String,
38 pub width: u32,
40 pub height: u32,
42}
43
44impl Default for WindowConfig {
45 fn default() -> Self {
58 Self {
59 title: "cotis-wgpu".to_string(),
60 width: 1000,
61 height: 800,
62 }
63 }
64}
65
66struct WgpuRendererInner {
67 window: Arc<Window>,
68 gpu: GpuDevice,
69 surface: SurfaceState,
70 geometry: GeometryBatch,
71 pub(crate) text: TextState,
72 background: wgpu::Color,
73 size: (u32, u32),
74 pub(crate) dpi_scale: f32,
75 pub(crate) input: InputSnapshot,
76 last_frame: Instant,
77 close_requested: bool,
78}
79
80pub struct CotisWgpuRenderer {
86 event_loop: EventLoop<()>,
87 inner: WgpuRendererInner,
88}
89
90impl CotisWgpuRenderer {
91 pub fn auto_start() -> Self {
100 Self::new(wgpu::Color {
101 r: 45.0 / 255.0,
102 g: 45.0 / 255.0,
103 b: 55.0 / 255.0,
104 a: 1.0,
105 })
106 .expect("failed to start cotis-wgpu renderer")
107 }
108
109 pub fn new(background: wgpu::Color) -> Result<Self, WgpuBackendError> {
118 Self::new_with_config(WindowConfig::default(), background)
119 }
120
121 pub fn new_with_config(
129 config: WindowConfig,
130 background: wgpu::Color,
131 ) -> Result<Self, WgpuBackendError> {
132 let mut event_loop = EventLoop::new()?;
133 event_loop.set_control_flow(ControlFlow::Poll);
134
135 let mut bootstrap = BootstrapHandler {
136 config,
137 background,
138 inner: None,
139 error: None,
140 close_requested: false,
141 };
142
143 loop {
144 let status = event_loop.pump_app_events(Some(Duration::ZERO), &mut bootstrap);
145
146 if let Some(inner) = bootstrap.inner.take() {
147 return Ok(Self { event_loop, inner });
148 }
149
150 if bootstrap.close_requested || matches!(status, PumpStatus::Exit(_)) {
151 return Err(bootstrap.error.unwrap_or(WgpuBackendError::Init(
152 "window closed during startup".into(),
153 )));
154 }
155 }
156 }
157
158 pub fn window_should_close(&mut self) -> bool {
163 self.pump_events();
164 self.inner.close_requested
165 }
166
167 pub fn window(&self) -> &Arc<Window> {
171 &self.inner.window
172 }
173
174 pub fn set_background(&mut self, background: wgpu::Color) {
179 self.inner.background = background;
180 }
181
182 pub(crate) fn window_dimensions(&self) -> Dimensions {
183 self.inner.window_dimensions()
184 }
185
186 pub(crate) fn delta_time(&self) -> f32 {
187 self.inner.delta_time()
188 }
189
190 pub(crate) fn dpi_scale(&self) -> f32 {
191 self.inner.dpi_scale
192 }
193
194 pub(crate) fn input(&self) -> &InputSnapshot {
195 &self.inner.input
196 }
197
198 pub(crate) fn text(&self) -> &TextState {
199 &self.inner.text
200 }
201
202 fn pump_events(&mut self) {
203 let mut handler = RuntimeHandler {
204 inner: &mut self.inner,
205 };
206 let status = self
207 .event_loop
208 .pump_app_events(Some(Duration::ZERO), &mut handler);
209
210 if matches!(status, PumpStatus::Exit(_)) {
211 self.inner.close_requested = true;
212 }
213 }
214}
215
216impl WgpuRendererInner {
217 fn from_window(window: Arc<Window>, background: wgpu::Color) -> Result<Self, WgpuBackendError> {
218 let size = window.inner_size();
219 let dpi_scale = window.scale_factor() as f32;
220 let gpu = GpuDevice::new(&window)?;
221 let surface = SurfaceState::new(&gpu, &window, size.width, size.height)?;
222 let geometry = GeometryBatch::new(
223 &gpu.device,
224 surface.format(),
225 (size.width as i32, size.height as i32),
226 );
227 let text = TextState::new(
228 &gpu.device,
229 &gpu.queue,
230 surface.format(),
231 (size.width, size.height),
232 dpi_scale,
233 );
234
235 Ok(WgpuRendererInner {
236 window,
237 gpu,
238 surface,
239 geometry,
240 text,
241 background,
242 size: (size.width, size.height),
243 dpi_scale,
244 input: InputSnapshot::default(),
245 last_frame: Instant::now(),
246 close_requested: false,
247 })
248 }
249
250 fn resize(&mut self, width: u32, height: u32) {
251 if width == 0 || height == 0 {
252 return;
253 }
254
255 self.size = (width, height);
256 self.surface.resize(&self.gpu, width, height);
257 self.geometry.resize_vertices((width as i32, height as i32));
258 }
259
260 fn set_dpi_scale(&mut self, dpi_scale: f32) {
261 self.dpi_scale = dpi_scale;
262 self.text.set_dpi_scale(dpi_scale);
263 }
264
265 fn present_frame(
266 &mut self,
267 draw: impl FnOnce(&mut WgpuDrawContext<'_>),
268 ) -> Result<(), WgpuBackendError> {
269 let drawable = self.surface.acquire_frame(&self.gpu)?;
270 let view = drawable
271 .texture
272 .create_view(&wgpu::TextureViewDescriptor::default());
273
274 let mut encoder = self
275 .gpu
276 .device
277 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
278 label: Some("cotis-wgpu frame encoder"),
279 });
280
281 {
282 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
283 label: Some("cotis-wgpu frame pass"),
284 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
285 view: &view,
286 resolve_target: None,
287 ops: wgpu::Operations {
288 load: wgpu::LoadOp::Clear(self.background),
289 store: wgpu::StoreOp::Store,
290 },
291 })],
292 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
293 view: &self.surface.depth.view,
294 depth_ops: Some(wgpu::Operations {
295 load: wgpu::LoadOp::Clear(1.0),
296 store: wgpu::StoreOp::Store,
297 }),
298 stencil_ops: None,
299 }),
300 timestamp_writes: None,
301 occlusion_query_set: None,
302 });
303
304 let mut depth = 0.1_f32;
305 let mut draw_context = WgpuDrawContext {
306 geometry: &mut self.geometry,
307 text: &mut self.text,
308 scissor_active: false,
309 scissor_position: crate::pipeline::UIPosition::new(),
310 scissor_bounds: crate::pipeline::UIPosition::new(),
311 depth: &mut depth,
312 dpi_scale: self.dpi_scale,
313 };
314
315 draw(&mut draw_context);
316
317 self.geometry.flush(&mut render_pass, &self.gpu.queue);
318 self.text.flush(
319 &self.gpu.device,
320 &self.gpu.queue,
321 &mut render_pass,
322 &self.surface.config,
323 );
324 }
325
326 self.gpu.queue.submit(std::iter::once(encoder.finish()));
327 drawable.present();
328 self.last_frame = Instant::now();
329 Ok(())
330 }
331
332 fn draw_commands<'a, T: WgpuDrawable + 'a>(
333 &mut self,
334 render_commands: impl Iterator<Item = T>,
335 ) -> Result<(), WgpuBackendError> {
336 self.present_frame(|draw_context| {
337 for command in render_commands {
338 Box::new(command).draw(draw_context);
339 }
340 })
341 }
342
343 fn draw_boxed_commands<'b>(
344 &mut self,
345 render_commands: impl Iterator<Item = Box<dyn WgpuDrawable + 'b>>,
346 ) -> Result<(), WgpuBackendError> {
347 self.present_frame(|draw_context| {
348 for command in render_commands {
349 command.draw(draw_context);
350 }
351 })
352 }
353
354 pub(crate) fn window_dimensions(&self) -> Dimensions {
355 Dimensions::new(self.size.0 as f32, self.size.1 as f32)
356 }
357
358 pub(crate) fn delta_time(&self) -> f32 {
359 self.last_frame.elapsed().as_secs_f32()
360 }
361}
362
363impl<'b> CotisRenderer<Box<dyn WgpuDrawable + 'b>> for CotisWgpuRenderer {
364 fn draw_frame(&mut self, render_commands: impl Iterator<Item = Box<dyn WgpuDrawable + 'b>>) {
365 self.pump_events();
366 if let Err(error) = self.inner.draw_boxed_commands(render_commands) {
367 log::warn!("frame render failed: {error}");
368 }
369 }
370}
371
372impl<T: WgpuDrawable, L: WgpuDrawable + IsRenderList> CotisRenderer<RenderTList<T, L>>
373 for CotisWgpuRenderer
374{
375 fn draw_frame(&mut self, render_commands: impl Iterator<Item = RenderTList<T, L>>) {
376 self.pump_events();
377 if let Err(error) = self.inner.draw_commands(render_commands) {
378 log::warn!("frame render failed: {error}");
379 }
380 }
381}
382
383impl<T: WgpuDrawable> CotisRenderer<RenderTList<T, ()>> for CotisWgpuRenderer {
384 fn draw_frame(&mut self, render_commands: impl Iterator<Item = RenderTList<T, ()>>) {
385 self.pump_events();
386 if let Err(error) = self.inner.draw_commands(render_commands) {
387 log::warn!("frame render failed: {error}");
388 }
389 }
390}
391
392impl<'b> CotisRendererAsync<Box<dyn WgpuDrawable + 'b>> for CotisWgpuRenderer {
393 async fn draw_frame(
394 &mut self,
395 render_commands: impl Iterator<Item = Box<dyn WgpuDrawable + 'b>>,
396 ) {
397 CotisRenderer::draw_frame(self, render_commands);
398 }
399}
400
401impl<T: WgpuDrawable, L: WgpuDrawable + IsRenderList> CotisRendererAsync<RenderTList<T, L>>
402 for CotisWgpuRenderer
403{
404 async fn draw_frame(&mut self, render_commands: impl Iterator<Item = RenderTList<T, L>>) {
405 CotisRenderer::draw_frame(self, render_commands);
406 }
407}
408
409impl<T: WgpuDrawable> CotisRendererAsync<RenderTList<T, ()>> for CotisWgpuRenderer {
410 async fn draw_frame(&mut self, render_commands: impl Iterator<Item = RenderTList<T, ()>>) {
411 CotisRenderer::draw_frame(self, render_commands);
412 }
413}
414
415impl<Manager> RenderCompatibleWith<Manager> for CotisWgpuRenderer {
416 fn init(&mut self, _layout_manager: &mut Manager) {}
417}
418
419struct BootstrapHandler {
420 config: WindowConfig,
421 background: wgpu::Color,
422 inner: Option<WgpuRendererInner>,
423 error: Option<WgpuBackendError>,
424 close_requested: bool,
425}
426
427impl BootstrapHandler {
428 fn try_create(&mut self, event_loop: &ActiveEventLoop) {
429 if self.inner.is_some() {
430 return;
431 }
432
433 let window_attributes = Window::default_attributes()
434 .with_title(self.config.title.clone())
435 .with_inner_size(LogicalSize::new(self.config.width, self.config.height));
436
437 let window = match event_loop.create_window(window_attributes) {
438 Ok(window) => Arc::new(window),
439 Err(error) => {
440 self.error = Some(WgpuBackendError::Init(error.to_string()));
441 event_loop.exit();
442 return;
443 }
444 };
445
446 match WgpuRendererInner::from_window(window, self.background) {
447 Ok(inner) => {
448 self.inner = Some(inner);
449 }
450 Err(error) => {
451 self.error = Some(error);
452 event_loop.exit();
453 }
454 }
455 }
456}
457
458impl ApplicationHandler for BootstrapHandler {
459 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
460 self.try_create(event_loop);
461 }
462
463 fn window_event(
464 &mut self,
465 event_loop: &ActiveEventLoop,
466 _window_id: WindowId,
467 event: WindowEvent,
468 ) {
469 if matches!(event, WindowEvent::CloseRequested) {
470 self.close_requested = true;
471 event_loop.exit();
472 }
473 }
474}
475
476struct RuntimeHandler<'a> {
477 inner: &'a mut WgpuRendererInner,
478}
479
480impl ApplicationHandler for RuntimeHandler<'_> {
481 fn resumed(&mut self, _event_loop: &ActiveEventLoop) {}
482
483 fn window_event(
484 &mut self,
485 _event_loop: &ActiveEventLoop,
486 _window_id: WindowId,
487 event: WindowEvent,
488 ) {
489 match event {
490 WindowEvent::CloseRequested => self.inner.close_requested = true,
491 WindowEvent::Resized(size) => {
492 self.inner.resize(size.width, size.height);
493 }
494 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
495 self.inner.set_dpi_scale(scale_factor as f32);
496 }
497 WindowEvent::CursorMoved { position, .. } => {
498 self.inner.input.mouse_position =
499 Vector2::new(position.x as f32, position.y as f32);
500 }
501 WindowEvent::MouseInput { state, button, .. } => {
502 if let Some(mapped) = map_mouse_button(button) {
503 if state == ElementState::Pressed {
504 self.inner.input.mouse_buttons.insert(mapped);
505 } else {
506 self.inner.input.mouse_buttons.swap_remove(&mapped);
507 }
508 }
509 }
510 WindowEvent::MouseWheel { delta, .. } => {
511 self.inner.input.scroll_delta = match delta {
512 MouseScrollDelta::LineDelta(x, y) => Vector2::new(x, y),
513 MouseScrollDelta::PixelDelta(position) => {
514 Vector2::new(position.x as f32, position.y as f32)
515 }
516 };
517 }
518 WindowEvent::KeyboardInput { event, .. } => {
519 if let PhysicalKey::Code(code) = event.physical_key
520 && let Some(key) = map_keyboard_key(code)
521 {
522 if event.state == ElementState::Pressed {
523 self.inner.input.pressed_keys.insert(key);
524 } else {
525 self.inner.input.pressed_keys.swap_remove(&key);
526 }
527 }
528 }
529 WindowEvent::Ime(winit::event::Ime::Preedit(text, _)) => {
530 self.inner.input.pressed_chars.extend(text.chars());
531 }
532 _ => {}
533 }
534 }
535}
536
537impl From<EventLoopError> for WgpuBackendError {
538 fn from(value: EventLoopError) -> Self {
539 Self::Init(value.to_string())
540 }
541}
542
543fn map_mouse_button(button: winit::event::MouseButton) -> Option<MouseButton> {
544 match button {
545 winit::event::MouseButton::Left => Some(MouseButton::MouseButtonLeft),
546 winit::event::MouseButton::Right => Some(MouseButton::MouseButtonRight),
547 winit::event::MouseButton::Middle => Some(MouseButton::MouseButtonMiddle),
548 winit::event::MouseButton::Back => Some(MouseButton::MouseButtonBack),
549 winit::event::MouseButton::Forward => Some(MouseButton::MouseButtonForward),
550 _ => None,
551 }
552}
553
554fn map_keyboard_key(code: KeyCode) -> Option<KeyboardKey> {
555 let value = match code {
556 KeyCode::Space => KeyboardKey::KeySpace,
557 KeyCode::Escape => KeyboardKey::KeyEscape,
558 KeyCode::Enter => KeyboardKey::KeyEnter,
559 KeyCode::Tab => KeyboardKey::KeyTab,
560 KeyCode::Backspace => KeyboardKey::KeyBackspace,
561 KeyCode::Insert => KeyboardKey::KeyInsert,
562 KeyCode::Delete => KeyboardKey::KeyDelete,
563 KeyCode::ArrowRight => KeyboardKey::KeyRight,
564 KeyCode::ArrowLeft => KeyboardKey::KeyLeft,
565 KeyCode::ArrowDown => KeyboardKey::KeyDown,
566 KeyCode::ArrowUp => KeyboardKey::KeyUp,
567 KeyCode::PageUp => KeyboardKey::KeyPageUp,
568 KeyCode::PageDown => KeyboardKey::KeyPageDown,
569 KeyCode::Home => KeyboardKey::KeyHome,
570 KeyCode::End => KeyboardKey::KeyEnd,
571 KeyCode::KeyA => KeyboardKey::KeyA,
572 KeyCode::KeyB => KeyboardKey::KeyB,
573 KeyCode::KeyC => KeyboardKey::KeyC,
574 KeyCode::KeyD => KeyboardKey::KeyD,
575 KeyCode::KeyE => KeyboardKey::KeyE,
576 KeyCode::KeyF => KeyboardKey::KeyF,
577 KeyCode::KeyG => KeyboardKey::KeyG,
578 KeyCode::KeyH => KeyboardKey::KeyH,
579 KeyCode::KeyI => KeyboardKey::KeyI,
580 KeyCode::KeyJ => KeyboardKey::KeyJ,
581 KeyCode::KeyK => KeyboardKey::KeyK,
582 KeyCode::KeyL => KeyboardKey::KeyL,
583 KeyCode::KeyM => KeyboardKey::KeyM,
584 KeyCode::KeyN => KeyboardKey::KeyN,
585 KeyCode::KeyO => KeyboardKey::KeyO,
586 KeyCode::KeyP => KeyboardKey::KeyP,
587 KeyCode::KeyQ => KeyboardKey::KeyQ,
588 KeyCode::KeyR => KeyboardKey::KeyR,
589 KeyCode::KeyS => KeyboardKey::KeyS,
590 KeyCode::KeyT => KeyboardKey::KeyT,
591 KeyCode::KeyU => KeyboardKey::KeyU,
592 KeyCode::KeyV => KeyboardKey::KeyV,
593 KeyCode::KeyW => KeyboardKey::KeyW,
594 KeyCode::KeyX => KeyboardKey::KeyX,
595 KeyCode::KeyY => KeyboardKey::KeyY,
596 KeyCode::KeyZ => KeyboardKey::KeyZ,
597 KeyCode::Digit0 => KeyboardKey::KeyZero,
598 KeyCode::Digit1 => KeyboardKey::KeyOne,
599 KeyCode::Digit2 => KeyboardKey::KeyTwo,
600 KeyCode::Digit3 => KeyboardKey::KeyThree,
601 KeyCode::Digit4 => KeyboardKey::KeyFour,
602 KeyCode::Digit5 => KeyboardKey::KeyFive,
603 KeyCode::Digit6 => KeyboardKey::KeySix,
604 KeyCode::Digit7 => KeyboardKey::KeySeven,
605 KeyCode::Digit8 => KeyboardKey::KeyEight,
606 KeyCode::Digit9 => KeyboardKey::KeyNine,
607 _ => return None,
608 };
609 Some(value)
610}