Skip to main content

wgpu_3dgs_viewer/
renderer.rs

1use crate::{
2    CameraBuffer, GaussianPod, GaussianTransformBuffer, GaussiansBuffer, IndirectArgsBuffer,
3    IndirectIndicesBuffer, ModelTransformBuffer, RendererCreateError, core::BufferWrapper,
4    wesl_utils,
5};
6
7/// A renderer for Gaussians.
8#[derive(Debug)]
9pub struct Renderer<G: GaussianPod, B = wgpu::BindGroup> {
10    /// The bind group layout.
11    #[allow(dead_code)]
12    bind_group_layout: wgpu::BindGroupLayout,
13    /// The bind group.
14    bind_group: B,
15    /// The render pipeline.
16    pipeline: wgpu::RenderPipeline,
17    /// The marker for the Gaussian POD type.
18    gaussian_pod_marker: std::marker::PhantomData<G>,
19}
20
21impl<G: GaussianPod, B> Renderer<G, B> {
22    /// Create the bind group.
23    #[allow(clippy::too_many_arguments)]
24    pub fn create_bind_group(
25        &self,
26        device: &wgpu::Device,
27        camera: &CameraBuffer,
28        model_transform: &ModelTransformBuffer,
29        gaussian_transform: &GaussianTransformBuffer,
30        gaussians: &GaussiansBuffer<G>,
31        indirect_indices: &IndirectIndicesBuffer,
32    ) -> wgpu::BindGroup {
33        Renderer::create_bind_group_static(
34            device,
35            &self.bind_group_layout,
36            camera,
37            model_transform,
38            gaussian_transform,
39            gaussians,
40            indirect_indices,
41        )
42    }
43}
44
45impl<G: GaussianPod> Renderer<G> {
46    /// The bind group layout descriptor.
47    pub const BIND_GROUP_LAYOUT_DESCRIPTOR: wgpu::BindGroupLayoutDescriptor<'static> =
48        wgpu::BindGroupLayoutDescriptor {
49            label: Some("Renderer Bind Group Layout"),
50            entries: &[
51                // Camera uniform buffer
52                wgpu::BindGroupLayoutEntry {
53                    binding: 0,
54                    visibility: wgpu::ShaderStages::VERTEX,
55                    ty: wgpu::BindingType::Buffer {
56                        ty: wgpu::BufferBindingType::Uniform,
57                        has_dynamic_offset: false,
58                        min_binding_size: None,
59                    },
60                    count: None,
61                },
62                // Model transform uniform buffer
63                wgpu::BindGroupLayoutEntry {
64                    binding: 1,
65                    visibility: wgpu::ShaderStages::VERTEX,
66                    ty: wgpu::BindingType::Buffer {
67                        ty: wgpu::BufferBindingType::Uniform,
68                        has_dynamic_offset: false,
69                        min_binding_size: None,
70                    },
71                    count: None,
72                },
73                // Gaussian transform uniform buffer
74                wgpu::BindGroupLayoutEntry {
75                    binding: 2,
76                    visibility: wgpu::ShaderStages::VERTEX,
77                    ty: wgpu::BindingType::Buffer {
78                        ty: wgpu::BufferBindingType::Uniform,
79                        has_dynamic_offset: false,
80                        min_binding_size: None,
81                    },
82                    count: None,
83                },
84                // Gaussian storage buffer
85                wgpu::BindGroupLayoutEntry {
86                    binding: 3,
87                    visibility: wgpu::ShaderStages::VERTEX,
88                    ty: wgpu::BindingType::Buffer {
89                        ty: wgpu::BufferBindingType::Storage { read_only: true },
90                        has_dynamic_offset: false,
91                        min_binding_size: None,
92                    },
93                    count: None,
94                },
95                // Indirect indices storage buffer
96                wgpu::BindGroupLayoutEntry {
97                    binding: 4,
98                    visibility: wgpu::ShaderStages::VERTEX,
99                    ty: wgpu::BindingType::Buffer {
100                        ty: wgpu::BufferBindingType::Storage { read_only: true },
101                        has_dynamic_offset: false,
102                        min_binding_size: None,
103                    },
104                    count: None,
105                },
106            ],
107        };
108
109    /// Create a new renderer.
110    #[allow(clippy::too_many_arguments)]
111    pub fn new(
112        device: &wgpu::Device,
113        texture_format: wgpu::TextureFormat,
114        depth_stencil: Option<wgpu::DepthStencilState>,
115        camera: &CameraBuffer,
116        model_transform: &ModelTransformBuffer,
117        gaussian_transform: &GaussianTransformBuffer,
118        gaussians: &GaussiansBuffer<G>,
119        indirect_indices: &IndirectIndicesBuffer,
120    ) -> Result<Self, RendererCreateError> {
121        if (device.limits().max_storage_buffer_binding_size as u64) < gaussians.buffer().size() {
122            return Err(RendererCreateError::ModelSizeExceedsDeviceLimit {
123                model_size: gaussians.buffer().size(),
124                device_limit: device.limits().max_storage_buffer_binding_size,
125            });
126        }
127
128        let this = Renderer::new_without_bind_group(device, texture_format, depth_stencil)?;
129
130        log::debug!("Creating renderer bind group");
131        let bind_group = this.create_bind_group(
132            device,
133            camera,
134            model_transform,
135            gaussian_transform,
136            gaussians,
137            indirect_indices,
138        );
139
140        Ok(Self {
141            bind_group_layout: this.bind_group_layout,
142            bind_group,
143            pipeline: this.pipeline,
144            gaussian_pod_marker: std::marker::PhantomData,
145        })
146    }
147
148    /// Render the scene.
149    pub fn render(
150        &self,
151        encoder: &mut wgpu::CommandEncoder,
152        view: &wgpu::TextureView,
153        indirect_args: &IndirectArgsBuffer,
154    ) {
155        let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
156            label: Some("Renderer Render Pass"),
157            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
158                view,
159                resolve_target: None,
160                ops: wgpu::Operations {
161                    load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
162                    store: wgpu::StoreOp::Store,
163                },
164                depth_slice: None,
165            })],
166            ..Default::default()
167        });
168
169        self.render_with_pass(&mut render_pass, indirect_args);
170    }
171
172    /// Render the scene with a [`wgpu::RenderPass`].
173    pub fn render_with_pass(
174        &self,
175        pass: &mut wgpu::RenderPass<'_>,
176        indirect_args: &IndirectArgsBuffer,
177    ) {
178        pass.set_pipeline(&self.pipeline);
179        pass.set_bind_group(0, &self.bind_group, &[]);
180        pass.draw_indirect(indirect_args.buffer(), 0);
181    }
182
183    /// Create the bind group statically.
184    #[allow(clippy::too_many_arguments)]
185    fn create_bind_group_static(
186        device: &wgpu::Device,
187        bind_group_layout: &wgpu::BindGroupLayout,
188        camera: &CameraBuffer,
189        model_transform: &ModelTransformBuffer,
190        gaussian_transform: &GaussianTransformBuffer,
191        gaussians: &GaussiansBuffer<G>,
192        indirect_indices: &IndirectIndicesBuffer,
193    ) -> wgpu::BindGroup {
194        device.create_bind_group(&wgpu::BindGroupDescriptor {
195            label: Some("Renderer Bind Group"),
196            layout: bind_group_layout,
197            entries: &[
198                // Camera uniform buffer
199                wgpu::BindGroupEntry {
200                    binding: 0,
201                    resource: camera.buffer().as_entire_binding(),
202                },
203                // Model transform uniform buffer
204                wgpu::BindGroupEntry {
205                    binding: 1,
206                    resource: model_transform.buffer().as_entire_binding(),
207                },
208                // Gaussian transform uniform buffer
209                wgpu::BindGroupEntry {
210                    binding: 2,
211                    resource: gaussian_transform.buffer().as_entire_binding(),
212                },
213                // Gaussian storage buffer
214                wgpu::BindGroupEntry {
215                    binding: 3,
216                    resource: gaussians.buffer().as_entire_binding(),
217                },
218                // Indirect indices storage buffer
219                wgpu::BindGroupEntry {
220                    binding: 4,
221                    resource: indirect_indices.buffer().as_entire_binding(),
222                },
223            ],
224        })
225    }
226}
227
228impl<G: GaussianPod> Renderer<G, ()> {
229    /// Create a new renderer without internally managed bind group.
230    ///
231    /// To create a bind group with layout matched to this renderer, use the
232    /// [`Renderer::create_bind_group`] method.
233    pub fn new_without_bind_group(
234        device: &wgpu::Device,
235        texture_format: wgpu::TextureFormat,
236        depth_stencil: Option<wgpu::DepthStencilState>,
237    ) -> Result<Self, RendererCreateError> {
238        log::debug!("Creating renderer bind group layout");
239        let bind_group_layout =
240            device.create_bind_group_layout(&Renderer::<G>::BIND_GROUP_LAYOUT_DESCRIPTOR);
241
242        log::debug!("Creating renderer pipeline layout");
243        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
244            label: Some("Renderer Pipeline Layout"),
245            bind_group_layouts: &[&bind_group_layout],
246            ..Default::default()
247        });
248
249        log::debug!("Creating renderer shader");
250        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
251            label: Some("Renderer Shader"),
252            source: wgpu::ShaderSource::Wgsl(
253                wesl::compile_sourcemap(
254                    &"wgpu_3dgs_viewer::render"
255                        .parse()
256                        .expect("render module path"),
257                    &wesl_utils::resolver(),
258                    &wesl::NoMangler,
259                    &wesl::CompileOptions {
260                        features: G::wesl_features(),
261                        ..Default::default()
262                    },
263                )?
264                .to_string()
265                .into(),
266            ),
267        });
268
269        log::debug!("Creating renderer pipeline");
270        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
271            label: Some("Renderer Pipeline"),
272            layout: Some(&pipeline_layout),
273            vertex: wgpu::VertexState {
274                module: &shader,
275                entry_point: Some("vert_main"),
276                buffers: &[],
277                compilation_options: Default::default(),
278            },
279            fragment: Some(wgpu::FragmentState {
280                module: &shader,
281                entry_point: Some("frag_main"),
282                targets: &[Some(wgpu::ColorTargetState {
283                    format: texture_format,
284                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
285                    write_mask: wgpu::ColorWrites::ALL,
286                })],
287                compilation_options: Default::default(),
288            }),
289            primitive: wgpu::PrimitiveState::default(),
290            depth_stencil,
291            multisample: wgpu::MultisampleState::default(),
292            multiview_mask: None,
293            cache: None,
294        });
295
296        log::info!("Renderer created");
297
298        Ok(Self {
299            bind_group_layout,
300            bind_group: (),
301            pipeline,
302            gaussian_pod_marker: std::marker::PhantomData,
303        })
304    }
305
306    /// Render the scene.
307    pub fn render(
308        &self,
309        encoder: &mut wgpu::CommandEncoder,
310        view: &wgpu::TextureView,
311        bind_group: &wgpu::BindGroup,
312        indirect_args: &IndirectArgsBuffer,
313    ) {
314        let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
315            label: Some("Renderer Render Pass"),
316            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
317                view,
318                resolve_target: None,
319                ops: wgpu::Operations {
320                    load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
321                    store: wgpu::StoreOp::Store,
322                },
323                depth_slice: None,
324            })],
325            ..Default::default()
326        });
327
328        self.render_with_pass(&mut render_pass, bind_group, indirect_args);
329    }
330
331    /// Render the scene with a [`wgpu::RenderPass`].
332    pub fn render_with_pass(
333        &self,
334        pass: &mut wgpu::RenderPass<'_>,
335        bind_group: &wgpu::BindGroup,
336        indirect_args: &IndirectArgsBuffer,
337    ) {
338        pass.set_pipeline(&self.pipeline);
339        pass.set_bind_group(0, bind_group, &[]);
340        pass.draw_indirect(indirect_args.buffer(), 0);
341    }
342}