1use crate::{
2 CameraBuffer, GaussianPod, GaussianTransformBuffer, GaussiansBuffer, IndirectArgsBuffer,
3 IndirectIndicesBuffer, ModelTransformBuffer, RendererCreateError, core::BufferWrapper,
4 wesl_utils,
5};
6
7#[derive(Debug)]
9pub struct Renderer<G: GaussianPod, B = wgpu::BindGroup> {
10 #[allow(dead_code)]
12 bind_group_layout: wgpu::BindGroupLayout,
13 bind_group: B,
15 pipeline: wgpu::RenderPipeline,
17 gaussian_pod_marker: std::marker::PhantomData<G>,
19}
20
21impl<G: GaussianPod, B> Renderer<G, B> {
22 #[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 pub const BIND_GROUP_LAYOUT_DESCRIPTOR: wgpu::BindGroupLayoutDescriptor<'static> =
48 wgpu::BindGroupLayoutDescriptor {
49 label: Some("Renderer Bind Group Layout"),
50 entries: &[
51 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 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 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 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 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 #[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 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 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 #[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 wgpu::BindGroupEntry {
200 binding: 0,
201 resource: camera.buffer().as_entire_binding(),
202 },
203 wgpu::BindGroupEntry {
205 binding: 1,
206 resource: model_transform.buffer().as_entire_binding(),
207 },
208 wgpu::BindGroupEntry {
210 binding: 2,
211 resource: gaussian_transform.buffer().as_entire_binding(),
212 },
213 wgpu::BindGroupEntry {
215 binding: 3,
216 resource: gaussians.buffer().as_entire_binding(),
217 },
218 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 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 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 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}