yakui_app/
lib.rs

1#![doc = include_str!("../README.md")]
2
3mod multisampling;
4
5use winit::{dpi::PhysicalSize, event::WindowEvent, event_loop::ActiveEventLoop, window::Window};
6
7use multisampling::Multisampling;
8
9/// A helper for setting up rendering with winit and wgpu
10pub struct Graphics {
11    pub device: wgpu::Device,
12    pub queue: wgpu::Queue,
13
14    format: wgpu::TextureFormat,
15    surface: wgpu::Surface<'static>,
16    surface_config: wgpu::SurfaceConfiguration,
17    size: PhysicalSize<u32>,
18    sample_count: u32,
19    multisampling: Multisampling,
20
21    window: yakui_winit::YakuiWinit,
22    pub renderer: yakui_wgpu::YakuiWgpu,
23    /// Tracks whether winit is still initializing
24    pub is_init: bool,
25}
26
27impl Graphics {
28    pub async fn new(window: &Window, sample_count: u32) -> Self {
29        let mut size = window.inner_size();
30
31        // FIXME: On web, we're receiving (0, 0) as the initial size of the
32        // window, which makes wgpu upset. If we hit that case, let's just make
33        // up a size and let it get fixed later.
34        if size == PhysicalSize::new(0, 0) {
35            size = PhysicalSize::new(800, 600);
36        }
37
38        let instance = wgpu::Instance::default();
39        let surface = unsafe {
40            instance.create_surface_unsafe(
41                wgpu::SurfaceTargetUnsafe::from_window(&window)
42                    .expect("Could not create wgpu surface from window"),
43            )
44        }
45        .expect("Could not create wgpu surface");
46
47        let adapter = instance
48            .request_adapter(&wgpu::RequestAdapterOptions {
49                power_preference: wgpu::PowerPreference::default(),
50                compatible_surface: Some(&surface),
51                force_fallback_adapter: false,
52            })
53            .await
54            .unwrap();
55
56        let (device, queue) = adapter
57            .request_device(
58                &wgpu::DeviceDescriptor {
59                    required_features: wgpu::Features::empty(),
60                    // WebGL doesn't support all of wgpu's features, so if
61                    // we're building for the web we'll have to disable some.
62                    required_limits: if cfg!(target_arch = "wasm32") {
63                        wgpu::Limits::downlevel_webgl2_defaults()
64                    } else {
65                        wgpu::Limits::default()
66                    },
67                    memory_hints: Default::default(),
68                    label: None,
69                },
70                None, // Trace path
71            )
72            .await
73            .unwrap();
74
75        let capabilities = surface.get_capabilities(&adapter);
76        let format = capabilities.formats[0];
77        let surface_config = wgpu::SurfaceConfiguration {
78            alpha_mode: wgpu::CompositeAlphaMode::Auto,
79            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
80            format,
81            view_formats: Vec::new(),
82            width: size.width,
83            height: size.height,
84            present_mode: wgpu::PresentMode::Fifo,
85            desired_maximum_frame_latency: 2,
86        };
87        surface.configure(&device, &surface_config);
88
89        // yakui_wgpu takes paint output from yakui and renders it for us using
90        // wgpu.
91        let renderer = yakui_wgpu::YakuiWgpu::new(&device, &queue);
92
93        // yakui_winit processes winit events and applies them to our yakui
94        // state.
95        let window = yakui_winit::YakuiWinit::new(window);
96
97        Self {
98            device,
99            queue,
100
101            format,
102            surface,
103            surface_config,
104            size,
105            sample_count,
106            multisampling: Multisampling::new(),
107
108            renderer,
109            window,
110            is_init: true,
111        }
112    }
113
114    pub fn renderer_mut(&mut self) -> &mut yakui_wgpu::YakuiWgpu {
115        &mut self.renderer
116    }
117
118    pub fn window_mut(&mut self) -> &mut yakui_winit::YakuiWinit {
119        &mut self.window
120    }
121
122    pub fn surface_format(&self) -> wgpu::TextureFormat {
123        self.surface_config.format
124    }
125
126    pub fn resize(&mut self, new_size: PhysicalSize<u32>) {
127        if new_size.width > 0 && new_size.height > 0 && new_size != self.size {
128            self.size = new_size;
129            self.surface_config.width = new_size.width;
130            self.surface_config.height = new_size.height;
131            self.surface.configure(&self.device, &self.surface_config);
132        }
133    }
134
135    #[cfg_attr(feature = "profiling", profiling::function)]
136    pub fn paint(&mut self, yak: &mut yakui_core::Yakui, bg: wgpu::Color) {
137        let output = match self.surface.get_current_texture() {
138            Ok(output) => output,
139            Err(_) => return,
140        };
141
142        let view = output
143            .texture
144            .create_view(&wgpu::TextureViewDescriptor::default());
145
146        let surface = self.multisampling.surface_info(
147            &self.device,
148            &view,
149            self.size,
150            self.format,
151            self.sample_count,
152        );
153
154        let mut encoder = self
155            .device
156            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
157                label: Some("Render Encoder"),
158            });
159
160        {
161            let _render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
162                label: Some("Render Pass"),
163                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
164                    view: surface.color_attachment,
165                    resolve_target: surface.resolve_target,
166                    ops: wgpu::Operations {
167                        load: wgpu::LoadOp::Clear(bg),
168                        store: wgpu::StoreOp::Store,
169                    },
170                })],
171                ..Default::default()
172            });
173        }
174
175        let clear = encoder.finish();
176
177        let paint_yak = self.renderer.paint(yak, &self.device, &self.queue, surface);
178
179        self.queue.submit([clear, paint_yak]);
180        output.present();
181    }
182
183    pub fn handle_window_event(
184        &mut self,
185        yak: &mut yakui::Yakui,
186        event: &WindowEvent,
187        event_loop: &ActiveEventLoop,
188    ) -> bool {
189        // yakui_winit will return whether it handled an event. This means that
190        // yakui believes it should handle that event exclusively, like if a
191        // button in the UI was clicked.
192        if self.window.handle_window_event(yak, event) {
193            return true;
194        }
195
196        match event {
197            WindowEvent::CloseRequested => {
198                event_loop.exit();
199            }
200
201            WindowEvent::Resized(size) => {
202                // Ignore any resize events that happen during Winit's
203                // initialization in order to avoid racing the wgpu swapchain
204                // and causing issues.
205                //
206                // https://github.com/rust-windowing/winit/issues/2094
207                if self.is_init {
208                    return false;
209                }
210
211                self.resize(*size);
212            }
213
214            _ => (),
215        }
216
217        false
218    }
219}