Skip to main content

libdqg/renderer/
mod.rs

1pub mod types;
2pub mod texture;
3pub mod sprite;
4pub mod model;
5mod draw_pass;
6pub mod extras;
7
8pub use draw_pass::DrawPass;
9pub use types::*;
10pub use texture::Texture;
11pub use sprite::Sprite;
12pub use model::Model;
13
14use crate::{camera::{Camera, CameraUniform}, types::Color};
15
16// ===== Renderer =====
17
18pub struct Renderer<'a> {
19    camera: Camera,
20    camera_uniform: CameraUniform,
21    pub(crate) camera_buffer: wgpu::Buffer,
22    window: std::sync::Arc<winit::window::Window>,
23    device: wgpu::Device,
24    surface: wgpu::Surface<'a>,
25    queue: wgpu::Queue,
26    config: wgpu::SurfaceConfiguration,
27    size: winit::dpi::PhysicalSize<u32>,
28    immediate_pipeline: wgpu::RenderPipeline,
29    pub(crate) texture_bind_group_layout: wgpu::BindGroupLayout,
30    pub(crate) sampler: wgpu::Sampler,
31    pub(crate) sprite_pipeline: wgpu::RenderPipeline,
32    pub(crate) world_sprite_pipeline: wgpu::RenderPipeline,
33    pub(crate) world_sprite_outline_pipeline: wgpu::RenderPipeline,
34    pub(crate) model_pipeline: wgpu::RenderPipeline,
35    pub(crate) model_outline_pipeline: wgpu::RenderPipeline,
36    model_transform_bind_group_layout: wgpu::BindGroupLayout,
37    pub(crate) camera_bind_group: wgpu::BindGroup,
38    depth_view: wgpu::TextureView,
39}
40
41/// Creates the depth buffer backing the main render pass, sized to the surface.
42fn create_depth_view(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration) -> wgpu::TextureView {
43    let texture = device.create_texture(&wgpu::TextureDescriptor {
44        label: Some("Depth Texture"),
45        size: wgpu::Extent3d {
46            width: config.width.max(1),
47            height: config.height.max(1),
48            depth_or_array_layers: 1,
49        },
50        mip_level_count: 1,
51        sample_count: 1,
52        dimension: wgpu::TextureDimension::D2,
53        format: extras::DEPTH_FORMAT,
54        usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
55        view_formats: &[],
56    });
57    texture.create_view(&wgpu::TextureViewDescriptor::default())
58}
59
60impl<'a> Renderer<'a> {
61    pub async fn new(window: std::sync::Arc<winit::window::Window>) -> Self {
62        let size = window.inner_size();
63
64        let instance = wgpu::Instance::default();
65        let surface = match instance.create_surface(window.clone()) {
66            Ok(surface) => surface,
67            Err(e) => panic!("Failed to create surface: {:?}", e),
68        };
69
70        let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions {
71            power_preference: wgpu::PowerPreference::HighPerformance,
72            compatible_surface: Some(&surface),
73            force_fallback_adapter: false,
74            apply_limit_buckets: false,
75        }).await.expect("Failed to find an appropriate adapter");
76
77        let (device, queue) = adapter.request_device(
78            &wgpu::DeviceDescriptor {
79                label: Some("Device Descriptor"),
80                required_features: wgpu::Features::empty(),
81                required_limits: wgpu::Limits::default(),
82                experimental_features: wgpu::ExperimentalFeatures::default(),
83                memory_hints: wgpu::MemoryHints::default(),
84                trace: wgpu::Trace::default(),
85            }
86        ).await.expect("Failed to create device");
87
88        let surface_caps = surface.get_capabilities(&adapter);
89        let surface_format = surface_caps.formats.iter().copied().find(|f| f.is_srgb()).unwrap_or(surface_caps.formats[0]);
90
91        let config = wgpu::SurfaceConfiguration {
92            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
93            format: surface_format,
94            width: size.width,
95            height: size.height,
96            present_mode: surface_caps.present_modes[0],
97            alpha_mode: surface_caps.alpha_modes[0],
98            view_formats: vec![],
99            desired_maximum_frame_latency: 2,
100            color_space: wgpu::SurfaceColorSpace::Auto,
101        };
102        surface.configure(&device, &config);
103
104        let shape_pipeline = extras::create_shape_pipeline(&device, config.format);
105
106        let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
107            entries: &[
108                wgpu::BindGroupLayoutEntry {
109                    binding: 0,
110                    visibility: wgpu::ShaderStages::FRAGMENT,
111                    ty: wgpu::BindingType::Texture {
112                        multisampled: false,
113                        view_dimension: wgpu::TextureViewDimension::D2,
114                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
115                    },
116                    count: None,
117                },
118                wgpu::BindGroupLayoutEntry {
119                    binding: 1,
120                    visibility: wgpu::ShaderStages::FRAGMENT,
121                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
122                    count: None,
123                },
124            ],
125            label: Some("texture_bind_group_layout"),
126        });
127
128        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
129            address_mode_u: wgpu::AddressMode::ClampToEdge,
130            address_mode_v: wgpu::AddressMode::ClampToEdge,
131            address_mode_w: wgpu::AddressMode::ClampToEdge,
132            mag_filter: wgpu::FilterMode::Linear,
133            min_filter: wgpu::FilterMode::Nearest,
134            mipmap_filter: wgpu::MipmapFilterMode::Nearest,
135            ..Default::default()
136        });
137
138        let sprite_pipeline = extras::create_sprite_pipeline(&device, config.format, &texture_bind_group_layout);
139
140        let mut camera = Camera {
141            position: glam::Vec3::new(0.0, 1.0, 2.0),
142            yaw: 0.0,
143            pitch: 0.0,
144            aspect: config.width as f32 / config.height as f32,
145            fov: 45.0,
146            znear: 0.1,
147            zfar: 100.0,
148        };
149        camera.look_at(glam::Vec3::ZERO);
150        let mut camera_uniform = CameraUniform::new();
151        camera_uniform.update(&camera);
152
153        let camera_buffer = device.create_buffer(&wgpu::BufferDescriptor {
154            label: Some("Camera Buffer"),
155            size: std::mem::size_of::<CameraUniform>() as u64,
156            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
157            mapped_at_creation: false,
158        });
159        queue.write_buffer(&camera_buffer, 0, crate::util::slice_to_bytes(&[camera_uniform]));
160
161        let camera_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
162            entries: &[
163                wgpu::BindGroupLayoutEntry {
164                    binding: 0,
165                    visibility: wgpu::ShaderStages::VERTEX,
166                    ty: wgpu::BindingType::Buffer {
167                        ty: wgpu::BufferBindingType::Uniform,
168                        has_dynamic_offset: false,
169                        min_binding_size: None,
170                    },
171                    count: None,
172                },
173            ],
174            label: Some("camera_bind_group_layout"),
175        });
176
177        let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
178            layout: &camera_bind_group_layout,
179            entries: &[
180                wgpu::BindGroupEntry {
181                    binding: 0,
182                    resource: camera_buffer.as_entire_binding(),
183                },
184            ],
185            label: Some("camera_bind_group"),
186        });
187
188        let world_sprite_pipeline = extras::create_world_sprite_pipeline(
189            &device, config.format, &texture_bind_group_layout, &camera_bind_group_layout,
190        );
191        let world_sprite_outline_pipeline = extras::create_world_sprite_outline_pipeline(
192            &device, config.format, &camera_bind_group_layout,
193        );
194
195        let model_transform_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
196            entries: &[
197                wgpu::BindGroupLayoutEntry {
198                    binding: 0,
199                    // Read by both the model fragment shader (tint mixing) and the outline
200                    // vertex shader (model matrix), in addition to the model vertex shader.
201                    visibility: wgpu::ShaderStages::VERTEX.union(wgpu::ShaderStages::FRAGMENT),
202                    ty: wgpu::BindingType::Buffer {
203                        ty: wgpu::BufferBindingType::Uniform,
204                        has_dynamic_offset: false,
205                        min_binding_size: None,
206                    },
207                    count: None,
208                },
209            ],
210            label: Some("model_transform_bind_group_layout"),
211        });
212
213        let model_pipeline = extras::create_model_pipeline(
214            &device, config.format, &texture_bind_group_layout, &camera_bind_group_layout, &model_transform_bind_group_layout,
215        );
216        let model_outline_pipeline = extras::create_model_outline_pipeline(
217            &device, config.format, &camera_bind_group_layout, &model_transform_bind_group_layout,
218        );
219
220        let depth_view = create_depth_view(&device, &config);
221
222        Self {
223            camera,
224            camera_uniform,
225            camera_buffer,
226            window,
227            device,
228            surface,
229            queue,
230            config,
231            size,
232            immediate_pipeline: shape_pipeline,
233            texture_bind_group_layout,
234            sampler,
235            sprite_pipeline,
236            world_sprite_pipeline,
237            world_sprite_outline_pipeline,
238            model_pipeline,
239            model_outline_pipeline,
240            model_transform_bind_group_layout,
241            camera_bind_group,
242            depth_view,
243        }
244    }
245
246    /// Format of the depth buffer attached to the main render pass. Pipelines created through
247    /// [`Renderer::create_render_pipeline`] must use this format if they specify a depth state.
248    pub const DEPTH_FORMAT: TextureFormat = TextureFormat::Depth32Float;
249
250    pub fn camera(&self) -> &Camera {
251        &self.camera
252    }
253
254    pub fn camera_mut(&mut self) -> &mut Camera {
255        &mut self.camera
256    }
257
258    /// The window this renderer's surface was created from, as a cheap `Arc` clone. Useful for
259    /// consumers that need to integrate other window-aware systems (e.g. `egui-winit`) and cache
260    /// the handle for later (e.g. in a `Scene::on_window_event`, which isn't handed the renderer).
261    pub fn window(&self) -> std::sync::Arc<winit::window::Window> {
262        self.window.clone()
263    }
264
265    pub fn device(&self) -> &wgpu::Device {
266        &self.device
267    }
268
269    pub fn queue(&self) -> &wgpu::Queue {
270        &self.queue
271    }
272
273    /// The raw `wgpu` format of the render surface, e.g. for configuring a third-party renderer
274    /// (like `egui-wgpu`) that needs to target the same surface. See also [`Renderer::surface_format`]
275    /// for the crate's own wrapper type.
276    pub fn wgpu_surface_format(&self) -> wgpu::TextureFormat {
277        self.config.format
278    }
279
280    pub fn size(&self) -> winit::dpi::PhysicalSize<u32> {
281        self.size
282    }
283
284    pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
285        if new_size.width > 0 && new_size.height > 0 {
286            self.size = new_size;
287            self.config.width = new_size.width;
288            self.config.height = new_size.height;
289            self.surface.configure(&self.device, &self.config);
290            self.depth_view = create_depth_view(&self.device, &self.config);
291            self.camera.aspect = new_size.width as f32 / new_size.height as f32;
292        }
293    }
294
295    pub fn surface_format(&self) -> TextureFormat {
296        TextureFormat::from_wgpu(self.config.format)
297    }
298
299    /// Renders one frame: a main pass (color + depth, cleared), an overlay pass (color only,
300    /// loaded not cleared, no depth test, e.g. egui), then a post-overlay pass (same as the
301    /// overlay pass but drawn after it, e.g. a custom titlebar that must stay on top of an
302    /// egui-based editor UI) — all on the same target before it's submitted and presented.
303    ///
304    /// `context` is threaded through to all three closures as an explicit parameter, rather than
305    /// captured from the caller's environment, so the `FnOnce`s can both reference the same
306    /// mutable state (e.g. a `SceneManager`) without the borrow checker seeing two simultaneous
307    /// unique borrows of it — they run one after another, but as closure *values* they'd
308    /// otherwise need to exist at the same time as arguments to this call.
309    pub fn render<T>(
310        &mut self,
311        clear_color: Color,
312        context: &mut T,
313        draw_fn: impl FnOnce(&mut T, &mut DrawPass),
314        overlay_fn: impl FnOnce(&mut T, &wgpu::Device, &wgpu::Queue, &mut wgpu::CommandEncoder, &wgpu::TextureView),
315        post_overlay_fn: impl FnOnce(&mut T, &mut DrawPass),
316    ) {
317        self.camera_uniform.update(&self.camera);
318        self.queue.write_buffer(&self.camera_buffer, 0, crate::util::slice_to_bytes(&[self.camera_uniform]));
319
320        let output = match self.surface.get_current_texture() {
321            wgpu::CurrentSurfaceTexture::Success(surface_texture) => surface_texture,
322            wgpu::CurrentSurfaceTexture::Suboptimal(surface_texture) => surface_texture,
323            wgpu::CurrentSurfaceTexture::Timeout => panic!("Surface timeout"),
324            wgpu::CurrentSurfaceTexture::Occluded => panic!("Surface occluded"),
325            wgpu::CurrentSurfaceTexture::Outdated => panic!("Surface outdated"),
326            wgpu::CurrentSurfaceTexture::Lost => panic!("Surface lost"),
327            wgpu::CurrentSurfaceTexture::Validation => todo!(),
328        };
329        let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
330        let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
331            label: Some("Render Encoder"),
332        });
333
334        {
335            let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
336                label: Some("Render Pass"),
337                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
338                    view: &view,
339                    resolve_target: None,
340                    ops: wgpu::Operations {
341                        load: wgpu::LoadOp::Clear(clear_color.as_wgpu_color()),
342                        store: wgpu::StoreOp::Store,
343                    },
344                    depth_slice: None,
345                })],
346                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
347                    view: &self.depth_view,
348                    depth_ops: Some(wgpu::Operations {
349                        load: wgpu::LoadOp::Clear(1.0),
350                        store: wgpu::StoreOp::Store,
351                    }),
352                    stencil_ops: None,
353                }),
354                timestamp_writes: None,
355                occlusion_query_set: None,
356                multiview_mask: None,
357            });
358            let mut draw_pass = DrawPass {
359                pass: render_pass,
360                device: &self.device,
361                queue: &self.queue,
362                immediate_pipeline: &self.immediate_pipeline,
363                sprite_pipeline: &self.sprite_pipeline,
364                world_sprite_pipeline: &self.world_sprite_pipeline,
365                world_sprite_outline_pipeline: &self.world_sprite_outline_pipeline,
366                model_pipeline: &self.model_pipeline,
367                model_outline_pipeline: &self.model_outline_pipeline,
368                camera_bind_group: &self.camera_bind_group,
369                screen_w: self.size.width,
370                screen_h: self.size.height,
371            };
372            draw_fn(context, &mut draw_pass);
373        }
374
375        overlay_fn(context, &self.device, &self.queue, &mut encoder, &view);
376
377        {
378            let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
379                label: Some("Post-Overlay Pass"),
380                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
381                    view: &view,
382                    resolve_target: None,
383                    ops: wgpu::Operations {
384                        load: wgpu::LoadOp::Load,
385                        store: wgpu::StoreOp::Store,
386                    },
387                    depth_slice: None,
388                })],
389                // The built-in screen-space pipelines (used for e.g. the titlebar's
390                // draw_rect/draw_line calls) declare a Depth32Float depth-stencil state — even
391                // though they ignore it for depth testing — so the pass needs a matching
392                // attachment or wgpu rejects the pipeline as incompatible. Loading (not
393                // clearing) the same depth buffer the main pass just wrote is harmless here
394                // since nothing in this pass depth-tests against it.
395                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
396                    view: &self.depth_view,
397                    depth_ops: Some(wgpu::Operations {
398                        load: wgpu::LoadOp::Load,
399                        store: wgpu::StoreOp::Store,
400                    }),
401                    stencil_ops: None,
402                }),
403                timestamp_writes: None,
404                occlusion_query_set: None,
405                multiview_mask: None,
406            });
407            let mut draw_pass = DrawPass {
408                pass: render_pass,
409                device: &self.device,
410                queue: &self.queue,
411                immediate_pipeline: &self.immediate_pipeline,
412                sprite_pipeline: &self.sprite_pipeline,
413                world_sprite_pipeline: &self.world_sprite_pipeline,
414                world_sprite_outline_pipeline: &self.world_sprite_outline_pipeline,
415                model_pipeline: &self.model_pipeline,
416                model_outline_pipeline: &self.model_outline_pipeline,
417                camera_bind_group: &self.camera_bind_group,
418                screen_w: self.size.width,
419                screen_h: self.size.height,
420            };
421            post_overlay_fn(context, &mut draw_pass);
422        }
423
424        self.queue.submit(std::iter::once(encoder.finish()));
425        self.queue.present(output);
426    }
427
428    pub fn create_shader_module(&self, desc: &ShaderModuleDescriptor) -> wgpu::ShaderModule {
429        let module = self.device.create_shader_module(wgpu::ShaderModuleDescriptor {
430            label: desc.label,
431            source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(desc.source)),
432        });
433        module
434    }
435
436    pub fn create_render_pipeline(&self, desc: &RenderPipelineDescriptor) -> wgpu::RenderPipeline {
437        let attrs_per_buffer: Vec<Vec<wgpu::VertexAttribute>> = desc.vertex.buffers.iter()
438            .map(|vb| {
439                vb.attributes.iter().map(|a| wgpu::VertexAttribute {
440                    format: a.format.to_wgpu(),
441                    offset: a.offset,
442                    shader_location: a.shader_location,
443                }).collect()
444            })
445            .collect();
446
447        let vertex_buffers: Vec<Option<wgpu::VertexBufferLayout>> = desc.vertex.buffers.iter().enumerate()
448            .map(|(i, vb)| Some(wgpu::VertexBufferLayout {
449                array_stride: vb.array_stride,
450                step_mode: vb.step_mode.to_wgpu(),
451                attributes: &attrs_per_buffer[i],
452            }))
453            .collect();
454
455        let primitive = wgpu::PrimitiveState {
456            topology: desc.primitive.topology.to_wgpu(),
457            strip_index_format: desc.primitive.strip_index_format.map(|f| f.to_wgpu()),
458            front_face: desc.primitive.front_face.to_wgpu(),
459            cull_mode: desc.primitive.cull_mode.to_wgpu(),
460            unclipped_depth: desc.primitive.unclipped_depth,
461            polygon_mode: desc.primitive.polygon_mode.to_wgpu(),
462            conservative: false,
463        };
464
465        // The main pass always carries a depth attachment, so a pipeline without a depth state
466        // would fail validation. Fall back to the screen-space state, which neither tests nor
467        // writes depth and so preserves plain draw-order painting.
468        let depth_stencil = Some(desc.depth_stencil.as_ref().map(|ds| wgpu::DepthStencilState {
469            format: ds.format.to_wgpu(),
470            depth_write_enabled: Some(ds.depth_write_enabled),
471            depth_compare: Some(ds.depth_compare.to_wgpu()),
472            stencil: wgpu::StencilState {
473                front: wgpu::StencilFaceState {
474                    compare: ds.stencil.front.compare.to_wgpu(),
475                    fail_op: ds.stencil.front.fail_op.to_wgpu(),
476                    depth_fail_op: ds.stencil.front.depth_fail_op.to_wgpu(),
477                    pass_op: ds.stencil.front.pass_op.to_wgpu(),
478                },
479                back: wgpu::StencilFaceState {
480                    compare: ds.stencil.back.compare.to_wgpu(),
481                    fail_op: ds.stencil.back.fail_op.to_wgpu(),
482                    depth_fail_op: ds.stencil.back.depth_fail_op.to_wgpu(),
483                    pass_op: ds.stencil.back.pass_op.to_wgpu(),
484                },
485                read_mask: ds.stencil.read_mask,
486                write_mask: ds.stencil.write_mask,
487            },
488            bias: wgpu::DepthBiasState {
489                constant: ds.bias.bias,
490                slope_scale: ds.bias.slope_scale,
491                clamp: ds.bias.clamp,
492            },
493        }).unwrap_or_else(extras::overlay_depth_state));
494
495        let multisample = wgpu::MultisampleState {
496            count: desc.multisample.count,
497            mask: desc.multisample.mask,
498            alpha_to_coverage_enabled: desc.multisample.alpha_to_coverage_enabled,
499        };
500
501        let color_targets: Vec<Option<wgpu::ColorTargetState>> = match &desc.fragment {
502            Some(frag) => {
503                frag.targets.iter().map(|t| {
504                    Some(wgpu::ColorTargetState {
505                        format: t.format.to_wgpu(),
506                        blend: t.blend.as_ref().map(|b| wgpu::BlendState {
507                            color: wgpu::BlendComponent {
508                                src_factor: b.color.src_factor.to_wgpu(),
509                                dst_factor: b.color.dst_factor.to_wgpu(),
510                                operation: b.color.operation.to_wgpu(),
511                            },
512                            alpha: wgpu::BlendComponent {
513                                src_factor: b.alpha.src_factor.to_wgpu(),
514                                dst_factor: b.alpha.dst_factor.to_wgpu(),
515                                operation: b.alpha.operation.to_wgpu(),
516                            },
517                        }),
518                        write_mask: t.write_mask.to_wgpu(),
519                    })
520                }).collect()
521            }
522            None => vec![],
523        };
524
525        let wgpu_desc = wgpu::RenderPipelineDescriptor {
526            label: desc.label,
527            layout: None,
528            vertex: wgpu::VertexState {
529                module: &desc.vertex.module,
530                entry_point: Some(desc.vertex.entry_point),
531                buffers: &vertex_buffers,
532                compilation_options: wgpu::PipelineCompilationOptions::default(),
533            },
534            primitive,
535            depth_stencil,
536            multisample,
537            fragment: desc.fragment.as_ref().map(|frag| wgpu::FragmentState {
538                module: &frag.module,
539                entry_point: Some(frag.entry_point),
540                targets: &color_targets,
541                compilation_options: wgpu::PipelineCompilationOptions::default(),
542            }),
543            multiview_mask: None,
544            cache: None,
545        };
546
547        let pipeline = self.device.create_render_pipeline(&wgpu_desc);
548        pipeline
549    }
550
551    pub fn create_buffer_init(&self, desc: &BufferInitDescriptor) -> wgpu::Buffer {
552        let usage = desc.usage.to_wgpu() | wgpu::BufferUsages::COPY_DST;
553        let buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
554            label: desc.label,
555            size: desc.contents.len() as u64,
556            usage,
557            mapped_at_creation: false,
558        });
559        self.queue.write_buffer(&buffer, 0, desc.contents);
560        buffer
561    }
562
563    pub fn create_buffer(&self, desc: &BufferDescriptor) -> wgpu::Buffer {
564        let buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
565            label: desc.label,
566            size: desc.size,
567            usage: desc.usage.to_wgpu(),
568            mapped_at_creation: false,
569        });
570        buffer
571    }
572}