fj_viewer/graphics/
renderer.rs

1use std::{io, mem::size_of, vec};
2
3use thiserror::Error;
4use tracing::{debug, error, trace};
5use wgpu::util::DeviceExt as _;
6
7use crate::{
8    camera::Camera,
9    screen::{Screen, ScreenSize},
10};
11
12use super::{
13    device::Device, draw_config::DrawConfig, drawables::Drawables,
14    geometries::Geometries, navigation_cube::NavigationCubeRenderer,
15    pipelines::Pipelines, transform::Transform, uniforms::Uniforms,
16    vertices::Vertices, DeviceError, DEPTH_FORMAT, SAMPLE_COUNT,
17};
18
19/// Graphics rendering state and target abstraction
20#[derive(Debug)]
21pub struct Renderer {
22    surface: wgpu::Surface<'static>,
23    device: Device,
24
25    surface_config: wgpu::SurfaceConfiguration,
26    frame_buffer: wgpu::TextureView,
27    depth_view: wgpu::TextureView,
28
29    uniform_buffer: wgpu::Buffer,
30    bind_group: wgpu::BindGroup,
31
32    geometries: Geometries,
33    pipelines: Pipelines,
34
35    navigation_cube_renderer: NavigationCubeRenderer,
36}
37
38impl Renderer {
39    /// Returns a new `Renderer`.
40    pub async fn new(screen: &impl Screen) -> Result<Self, RendererInitError> {
41        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
42            backends: wgpu::Backends::all(),
43            ..Default::default()
44        });
45
46        // This is sound, as `window` is an object to create a surface upon.
47        let surface = instance.create_surface(screen.window())?;
48
49        for adapter in instance.enumerate_adapters(wgpu::Backends::all()) {
50            debug!("Available adapter: {:?}", adapter.get_info());
51        }
52
53        let result = Device::from_preferred_adapter(&instance, &surface).await;
54        let (device, adapter, features) = match result {
55            Ok((device, adapter, features)) => (device, adapter, features),
56            Err(_) => {
57                error!("Failed to acquire device from preferred adapter");
58
59                match Device::try_from_all_adapters(&instance).await {
60                    Ok((device, adapter, features)) => {
61                        (device, adapter, features)
62                    }
63                    Err(err) => {
64                        error!("Prepend `RUST_LOG=fj_viewer=debug` and re-run");
65                        error!("Then open an issue and post your output");
66                        error!(
67                            "https://github.com/hannobraun/fornjot/issues/new"
68                        );
69
70                        return Err(err.into());
71                    }
72                }
73            }
74        };
75
76        let color_format = 'color_format: {
77            let capabilities = surface.get_capabilities(&adapter);
78            let supported_formats = capabilities.formats;
79
80            // We don't really care which color format we use, as long as we
81            // find one that's supported. `egui_wgpu` prints a warning though,
82            // unless we choose one of the following ones.
83            let preferred_formats = [
84                wgpu::TextureFormat::Rgba8Unorm,
85                wgpu::TextureFormat::Bgra8Unorm,
86            ];
87
88            for format in preferred_formats {
89                if supported_formats.contains(&format) {
90                    break 'color_format format;
91                }
92            }
93
94            // None of the preferred color formats are supported. Just use one
95            // of the supported ones then.
96            supported_formats
97                .into_iter()
98                .next()
99                .expect("No color formats supported")
100        };
101
102        let ScreenSize { width, height } = screen.size();
103        let surface_config = wgpu::SurfaceConfiguration {
104            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
105            format: color_format,
106            width,
107            height,
108            present_mode: wgpu::PresentMode::AutoVsync,
109            desired_maximum_frame_latency: 2,
110            // I don't understand what this option does. It was introduced with
111            // wgpu 0.14, but we had already been using premultiplied alpha
112            // blending before that. See the `BlendState` configuration of the
113            // render pipelines.
114            //
115            // For that reason, I tried to set this to `PreMultiplied`, but that
116            // failed on Linux/Wayland (with in integrated AMD GPU). Setting it
117            // to `Auto` seems to just work.
118            //
119            // @hannobraun
120            alpha_mode: wgpu::CompositeAlphaMode::Auto,
121            view_formats: vec![],
122        };
123        surface.configure(&device.device, &surface_config);
124
125        let frame_buffer =
126            Self::create_frame_buffer(&device.device, &surface_config);
127        let depth_view =
128            Self::create_depth_buffer(&device.device, &surface_config);
129
130        let uniform_buffer = device.device.create_buffer_init(
131            &wgpu::util::BufferInitDescriptor {
132                label: None,
133                contents: bytemuck::cast_slice(&[Uniforms::default()]),
134                usage: wgpu::BufferUsages::UNIFORM
135                    | wgpu::BufferUsages::COPY_DST,
136            },
137        );
138        let bind_group_layout = device.device.create_bind_group_layout(
139            &wgpu::BindGroupLayoutDescriptor {
140                entries: &[wgpu::BindGroupLayoutEntry {
141                    binding: 0,
142                    visibility: wgpu::ShaderStages::all(),
143                    ty: wgpu::BindingType::Buffer {
144                        ty: wgpu::BufferBindingType::Uniform,
145                        has_dynamic_offset: false,
146                        min_binding_size: wgpu::BufferSize::new(size_of::<
147                            Uniforms,
148                        >(
149                        )
150                            as u64),
151                    },
152                    count: None,
153                }],
154                label: None,
155            },
156        );
157        let bind_group =
158            device.device.create_bind_group(&wgpu::BindGroupDescriptor {
159                layout: &bind_group_layout,
160                entries: &[wgpu::BindGroupEntry {
161                    binding: 0,
162                    resource: wgpu::BindingResource::Buffer(
163                        wgpu::BufferBinding {
164                            buffer: &uniform_buffer,
165                            offset: 0,
166                            size: None,
167                        },
168                    ),
169                }],
170                label: None,
171            });
172
173        let geometries = Geometries::new(&device.device, &Vertices::empty());
174        let pipelines = Pipelines::new(
175            &device.device,
176            &bind_group_layout,
177            color_format,
178            features,
179        );
180
181        let navigation_cube_renderer = NavigationCubeRenderer::new(
182            &device.device,
183            &device.queue,
184            &surface_config,
185        );
186
187        Ok(Self {
188            surface,
189            device,
190
191            surface_config,
192            frame_buffer,
193            depth_view,
194
195            uniform_buffer,
196            bind_group,
197
198            geometries,
199            pipelines,
200
201            navigation_cube_renderer,
202        })
203    }
204
205    /// Updates the geometry of the model being rendered.
206    pub fn update_geometry(&mut self, mesh: Vertices) {
207        self.geometries = Geometries::new(&self.device.device, &mesh);
208    }
209
210    /// Resizes the render surface.
211    ///
212    /// # Arguments
213    /// - `size`: The target size for the render surface.
214    pub fn handle_resize(&mut self, size: ScreenSize) {
215        self.surface_config.width = size.width;
216        self.surface_config.height = size.height;
217
218        self.surface
219            .configure(&self.device.device, &self.surface_config);
220
221        self.frame_buffer = Self::create_frame_buffer(
222            &self.device.device,
223            &self.surface_config,
224        );
225        self.depth_view = Self::create_depth_buffer(
226            &self.device.device,
227            &self.surface_config,
228        );
229    }
230
231    /// Draws the renderer, camera, and config state to the window.
232    pub fn draw(
233        &mut self,
234        camera: &Camera,
235        config: &DrawConfig,
236    ) -> Result<(), DrawError> {
237        let aspect_ratio = f64::from(self.surface_config.width)
238            / f64::from(self.surface_config.height);
239        let uniforms = Uniforms {
240            transform: Transform::for_vertices(camera, aspect_ratio),
241            transform_normals: Transform::for_normals(camera),
242        };
243
244        self.device.queue.write_buffer(
245            &self.uniform_buffer,
246            0,
247            bytemuck::cast_slice(&[uniforms]),
248        );
249
250        let surface_texture = match self.surface.get_current_texture() {
251            Ok(surface_texture) => surface_texture,
252            Err(wgpu::SurfaceError::Timeout) => {
253                // I'm seeing this all the time now (as in, multiple times per
254                // microsecond), with `PresentMode::AutoVsync`. Not sure what's
255                // going on, but for now, it works to just ignore it.
256                //
257                // Issues for reference:
258                // - https://github.com/gfx-rs/wgpu/issues/1218
259                // - https://github.com/gfx-rs/wgpu/issues/1565
260                return Ok(());
261            }
262            result => result?,
263        };
264        let color_view = surface_texture
265            .texture
266            .create_view(&wgpu::TextureViewDescriptor::default());
267
268        let mut encoder = self.device.device.create_command_encoder(
269            &wgpu::CommandEncoderDescriptor { label: None },
270        );
271
272        // Need this block here, as a render pass only takes effect once it's
273        // dropped.
274        {
275            let mut render_pass =
276                encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
277                    color_attachments: &[Some(
278                        wgpu::RenderPassColorAttachment {
279                            view: &self.frame_buffer,
280                            resolve_target: Some(&color_view),
281                            ops: wgpu::Operations {
282                                load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
283                                // Not necessary, due to MSAA being enabled.
284                                store: wgpu::StoreOp::Discard,
285                            },
286                        },
287                    )],
288                    depth_stencil_attachment: Some(
289                        wgpu::RenderPassDepthStencilAttachment {
290                            view: &self.depth_view,
291                            depth_ops: Some(wgpu::Operations {
292                                load: wgpu::LoadOp::Clear(1.0),
293                                store: wgpu::StoreOp::Store,
294                            }),
295                            stencil_ops: None,
296                        },
297                    ),
298                    ..Default::default()
299                });
300            render_pass.set_bind_group(0, &self.bind_group, &[]);
301
302            let drawables = Drawables::new(&self.geometries, &self.pipelines);
303
304            if config.draw_model {
305                drawables.model.draw(&mut render_pass);
306            }
307
308            if let Some(drawable) = drawables.mesh {
309                if config.draw_mesh {
310                    drawable.draw(&mut render_pass);
311                }
312            }
313        }
314
315        self.navigation_cube_renderer.draw(
316            &color_view,
317            &mut encoder,
318            &self.device.queue,
319            aspect_ratio,
320            camera.rotation,
321        );
322
323        let command_buffer = encoder.finish();
324        self.device.queue.submit(Some(command_buffer));
325
326        trace!("Presenting...");
327        surface_texture.present();
328
329        trace!("Finished drawing.");
330        Ok(())
331    }
332
333    fn create_frame_buffer(
334        device: &wgpu::Device,
335        surface_config: &wgpu::SurfaceConfiguration,
336    ) -> wgpu::TextureView {
337        let texture = device.create_texture(&wgpu::TextureDescriptor {
338            label: None,
339            size: wgpu::Extent3d {
340                width: surface_config.width,
341                height: surface_config.height,
342                depth_or_array_layers: 1,
343            },
344            mip_level_count: 1,
345            sample_count: SAMPLE_COUNT,
346            dimension: wgpu::TextureDimension::D2,
347            format: surface_config.format,
348            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
349            view_formats: &[],
350        });
351        texture.create_view(&wgpu::TextureViewDescriptor::default())
352    }
353
354    fn create_depth_buffer(
355        device: &wgpu::Device,
356        surface_config: &wgpu::SurfaceConfiguration,
357    ) -> wgpu::TextureView {
358        let texture = device.create_texture(&wgpu::TextureDescriptor {
359            label: None,
360            size: wgpu::Extent3d {
361                width: surface_config.width,
362                height: surface_config.height,
363                depth_or_array_layers: 1,
364            },
365            mip_level_count: 1,
366            sample_count: SAMPLE_COUNT,
367            dimension: wgpu::TextureDimension::D2,
368            format: DEPTH_FORMAT,
369            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
370            view_formats: &[],
371        });
372
373        texture.create_view(&wgpu::TextureViewDescriptor::default())
374    }
375}
376
377/// Error describing the set of render surface initialization errors
378#[derive(Error, Debug)]
379pub enum RendererInitError {
380    /// General IO error
381    #[error("I/O error")]
382    Io(#[from] io::Error),
383
384    /// Surface creating error
385    #[error("Error creating surface")]
386    CreateSurface(#[from] wgpu::CreateSurfaceError),
387
388    /// Device error
389    #[error(transparent)]
390    Device(#[from] DeviceError),
391}
392
393/// Draw error
394///
395/// Returned by [`Renderer::draw`].
396#[derive(Error, Debug)]
397#[error("Error acquiring output surface: {0}")]
398pub struct DrawError(#[from] wgpu::SurfaceError);