1use bytemuck::{Pod, Zeroable};
11use wgpu::util::DeviceExt as _;
12
13use crate::{
14 geometry::marching_cubes::{TRI_TABLE, VolumeData},
15 renderer::GpuMarchingCubesJob,
16 resources::{DeviceResources, DualPipeline},
17};
18
19#[derive(Default)]
26pub(crate) struct McResources {
27 pub(crate) classify_pipeline: Option<wgpu::ComputePipeline>,
28 pub(crate) prefix_sum_pipeline: Option<wgpu::ComputePipeline>,
29 pub(crate) generate_pipeline: Option<wgpu::ComputePipeline>,
30 pub(crate) surface_pipeline: Option<DualPipeline>,
31 pub(crate) wireframe_pipeline: Option<DualPipeline>,
32 pub(crate) wireframe_render_bgl: Option<wgpu::BindGroupLayout>,
33 pub(crate) classify_bgl: Option<wgpu::BindGroupLayout>,
34 pub(crate) prefix_sum_bgl: Option<wgpu::BindGroupLayout>,
35 pub(crate) generate_bgl: Option<wgpu::BindGroupLayout>,
36 pub(crate) render_bgl: Option<wgpu::BindGroupLayout>,
37 pub(crate) case_count_buf: Option<wgpu::Buffer>,
38 pub(crate) case_table_buf: Option<wgpu::Buffer>,
39 pub(crate) volumes: Vec<McVolumeGpuData>,
40 pub(crate) outline_mask_pipeline: Option<wgpu::RenderPipeline>,
42}
43
44crate::resources::handle::slot_handle! {
49 pub struct McVolumeId;
59}
60
61pub(crate) struct McSlabGpuData {
71 pub scalar_buf: wgpu::Buffer, pub counts_buf: wgpu::Buffer, pub case_idx_buf: wgpu::Buffer, pub offsets_buf: wgpu::Buffer, pub block_sums_buf: wgpu::Buffer, pub vertex_buf: wgpu::Buffer, pub indirect_buf: wgpu::Buffer, pub wire_indirect_buf: wgpu::Buffer, pub dims: [u32; 3], pub origin: [f32; 3], pub spacing: [f32; 3],
82 pub cell_count: u32,
83 pub block_count: u32,
84}
85
86pub(crate) struct McVolumeGpuData {
91 pub slabs: Vec<McSlabGpuData>,
92 pub alive: bool,
94 pub generation: u32,
97}
98
99impl McVolumeGpuData {
100 pub fn gpu_bytes(&self) -> u64 {
103 self.slabs
104 .iter()
105 .map(|s| {
106 s.scalar_buf.size()
107 + s.counts_buf.size()
108 + s.case_idx_buf.size()
109 + s.offsets_buf.size()
110 + s.block_sums_buf.size()
111 + s.vertex_buf.size()
112 + s.indirect_buf.size()
113 + s.wire_indirect_buf.size()
114 })
115 .sum()
116 }
117}
118
119pub(crate) struct McFrameData {
121 pub volume_idx: usize,
122 pub render_bg: wgpu::BindGroup,
123 pub wireframe: bool,
125 pub wire_slab_bgs: Vec<wgpu::BindGroup>,
127}
128
129pub(crate) struct McOutlineItem {
131 pub mc_gpu_idx: usize,
133 pub _uniform_buf: wgpu::Buffer,
134 pub mask_bind_group: wgpu::BindGroup,
135}
136
137#[repr(C)]
142#[derive(Clone, Copy, Pod, Zeroable)]
143struct ClassifyParams {
144 nx: u32,
145 ny: u32,
146 nz: u32,
147 isovalue: f32,
148}
149
150#[repr(C)]
151#[derive(Clone, Copy, Pod, Zeroable)]
152struct PrefixSumParams {
153 cell_count: u32,
154 block_count: u32,
155 level: u32,
156 _pad: u32,
157}
158
159#[repr(C)]
160#[derive(Clone, Copy, Pod, Zeroable)]
161struct GenerateParams {
162 nx: u32,
163 ny: u32,
164 nz: u32,
165 isovalue: f32,
166 origin_x: f32,
167 origin_y: f32,
168 origin_z: f32,
169 _pad0: f32,
170 spacing_x: f32,
171 spacing_y: f32,
172 spacing_z: f32,
173 _pad1: f32,
174}
175
176#[repr(C)]
177#[derive(Clone, Copy, Pod, Zeroable)]
178struct McSurfaceRaw {
179 base_colour: [f32; 3],
180 roughness: f32,
181 unlit: u32,
182 opacity: f32,
183 ambient: f32,
189 _pad: u32,
190}
191
192fn case_triangle_count_table() -> [u32; 256] {
198 let mut out = [0u32; 256];
199 for (i, row) in TRI_TABLE.iter().enumerate() {
200 let mut count = 0u32;
201 let mut j = 0;
202 while j < 15 && row[j] >= 0 {
203 count += 1;
204 j += 3;
205 }
206 out[i] = count;
207 }
208 out
209}
210
211fn case_table_flat() -> [i32; 256 * 16] {
213 let mut out = [-1i32; 256 * 16];
214 for (i, row) in TRI_TABLE.iter().enumerate() {
215 for (j, &v) in row.iter().enumerate() {
216 out[i * 16 + j] = v as i32;
217 }
218 }
219 out
220}
221
222impl DeviceResources {
227 pub(crate) fn ensure_mc_pipelines(&mut self, device: &wgpu::Device) {
231 if self.mc.classify_pipeline.is_some() {
232 return;
233 }
234
235 let count_table = case_triangle_count_table();
239 let mc_case_count_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
240 label: Some("mc_case_count_buf"),
241 contents: bytemuck::cast_slice(&count_table),
242 usage: wgpu::BufferUsages::STORAGE,
243 });
244
245 let flat_table = case_table_flat();
246 let mc_case_table_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
247 label: Some("mc_case_table_buf"),
248 contents: bytemuck::cast_slice(&flat_table),
249 usage: wgpu::BufferUsages::STORAGE,
250 });
251
252 let classify_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
258 label: Some("mc_classify_bgl"),
259 entries: &[
260 bgl_uniform(0),
261 bgl_storage_ro(1),
262 bgl_storage_ro(2),
263 bgl_storage_rw(3),
264 bgl_storage_rw(4),
265 ],
266 });
267
268 let prefix_sum_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
270 label: Some("mc_prefix_sum_bgl"),
271 entries: &[
272 bgl_uniform(0),
273 bgl_storage_ro(1),
274 bgl_storage_rw(2),
275 bgl_storage_rw(3),
276 bgl_storage_rw(4),
277 bgl_storage_rw(5), ],
279 });
280
281 let generate_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
283 label: Some("mc_generate_bgl"),
284 entries: &[
285 bgl_uniform(0),
286 bgl_storage_ro(1),
287 bgl_storage_ro(2),
288 bgl_storage_ro(3),
289 bgl_storage_ro(4),
290 bgl_storage_rw(5),
291 ],
292 });
293
294 let render_bgl = crate::resources::builders::uniform_bgl(
296 device,
297 "mc_render_bgl",
298 wgpu::ShaderStages::FRAGMENT,
299 );
300
301 let classify_shader = crate::resources::builders::wgsl_module(
305 device,
306 "mc_classify_shader",
307 crate::resources::builders::wgsl_source!("mc_classify"),
308 );
309 let classify_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
310 label: Some("mc_classify_layout"),
311 bind_group_layouts: &[&classify_bgl],
312 push_constant_ranges: &[],
313 });
314 let classify_pipeline = crate::resources::builders::compute_pipeline(
315 device,
316 "mc_classify_pipeline",
317 &classify_layout,
318 &classify_shader,
319 "main",
320 );
321
322 let prefix_sum_shader = crate::resources::builders::wgsl_module(
323 device,
324 "mc_prefix_sum_shader",
325 crate::resources::builders::wgsl_source!("mc_prefix_sum"),
326 );
327 let prefix_sum_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
328 label: Some("mc_prefix_sum_layout"),
329 bind_group_layouts: &[&prefix_sum_bgl],
330 push_constant_ranges: &[],
331 });
332 let prefix_sum_pipeline = crate::resources::builders::compute_pipeline(
333 device,
334 "mc_prefix_sum_pipeline",
335 &prefix_sum_layout,
336 &prefix_sum_shader,
337 "main",
338 );
339
340 let generate_shader = crate::resources::builders::wgsl_module(
341 device,
342 "mc_generate_shader",
343 crate::resources::builders::wgsl_source!("mc_generate"),
344 );
345 let generate_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
346 label: Some("mc_generate_layout"),
347 bind_group_layouts: &[&generate_bgl],
348 push_constant_ranges: &[],
349 });
350 let generate_pipeline = crate::resources::builders::compute_pipeline(
351 device,
352 "mc_generate_pipeline",
353 &generate_layout,
354 &generate_shader,
355 "main",
356 );
357
358 let surface_shader = crate::resources::builders::wgsl_module(
362 device,
363 "mc_surface_shader",
364 crate::resources::builders::wgsl_source!("mc_surface"),
365 );
366 let surface_layout = crate::resources::builders::standard_scene_layout(
367 device,
368 "mc_surface_layout",
369 &self.camera_bind_group_layout,
370 &render_bgl,
371 );
372
373 let vertex_attrs = [
374 wgpu::VertexAttribute {
375 format: wgpu::VertexFormat::Float32x3,
376 offset: 0,
377 shader_location: 0,
378 },
379 wgpu::VertexAttribute {
380 format: wgpu::VertexFormat::Float32x3,
381 offset: 12,
382 shader_location: 1,
383 },
384 ];
385 let vertex_layout = wgpu::VertexBufferLayout {
386 array_stride: 24,
387 step_mode: wgpu::VertexStepMode::Vertex,
388 attributes: &vertex_attrs,
389 };
390
391 let wireframe_render_bgl =
395 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
396 label: Some("mc_wireframe_render_bgl"),
397 entries: &[wgpu::BindGroupLayoutEntry {
398 binding: 0,
399 visibility: wgpu::ShaderStages::VERTEX,
400 ty: wgpu::BindingType::Buffer {
401 ty: wgpu::BufferBindingType::Storage { read_only: true },
402 has_dynamic_offset: false,
403 min_binding_size: None,
404 },
405 count: None,
406 }],
407 });
408
409 let wireframe_shader = crate::resources::builders::wgsl_module(
410 device,
411 "mc_wireframe_shader",
412 crate::resources::builders::wgsl_source!("mc_wireframe"),
413 );
414 let wireframe_layout = crate::resources::builders::standard_scene_layout(
415 device,
416 "mc_wireframe_layout",
417 &self.camera_bind_group_layout,
418 &wireframe_render_bgl,
419 );
420 self.mc.case_count_buf = Some(mc_case_count_buf);
424 self.mc.case_table_buf = Some(mc_case_table_buf);
425 self.mc.classify_bgl = Some(classify_bgl);
426 self.mc.prefix_sum_bgl = Some(prefix_sum_bgl);
427 self.mc.generate_bgl = Some(generate_bgl);
428 self.mc.render_bgl = Some(render_bgl);
429 self.mc.classify_pipeline = Some(classify_pipeline);
430 self.mc.prefix_sum_pipeline = Some(prefix_sum_pipeline);
431 self.mc.generate_pipeline = Some(generate_pipeline);
432 self.mc.surface_pipeline = Some(crate::resources::builders::build_dual_pipeline(
433 device,
434 &crate::resources::builders::DualPipelineDesc {
435 label: "mc_surface_pipeline",
436 layout: &surface_layout,
437 shader: &surface_shader,
438 vertex_entry: "vs_main",
439 fragment_entry: "fs_main",
440 vertex_buffers: &[vertex_layout.clone()],
441 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
442 topology: wgpu::PrimitiveTopology::TriangleList,
443 cull_mode: None,
444 depth_write: true,
445 depth_compare: wgpu::CompareFunction::LessEqual,
446 sample_count: 1,
447 ldr_format: self.target_format,
448 },
449 ));
450 self.mc.wireframe_render_bgl = Some(wireframe_render_bgl);
451 self.mc.wireframe_pipeline = Some(crate::resources::builders::build_dual_pipeline(
452 device,
453 &crate::resources::builders::DualPipelineDesc {
454 label: "mc_wireframe_pipeline",
455 layout: &wireframe_layout,
456 shader: &wireframe_shader,
457 vertex_entry: "vs_main",
458 fragment_entry: "fs_main",
459 vertex_buffers: &[], blend: Some(wgpu::BlendState::ALPHA_BLENDING),
461 topology: wgpu::PrimitiveTopology::LineList,
462 cull_mode: None,
463 depth_write: true,
464 depth_compare: wgpu::CompareFunction::LessEqual,
465 sample_count: 1,
466 ldr_format: self.target_format,
467 },
468 ));
469 }
470
471 pub fn upload_volume_for_mc(
480 &mut self,
481 device: &wgpu::Device,
482 queue: &wgpu::Queue,
483 vol: &VolumeData,
484 ) -> crate::ViewportResult<McVolumeId> {
485 let gpu_data = build_mc_volume_gpu_data(device, queue, vol)?;
486 Ok(self.insert_mc_volume_gpu_data(gpu_data))
487 }
488
489 pub(crate) fn insert_mc_volume_gpu_data(
494 &mut self,
495 mut gpu_data: McVolumeGpuData,
496 ) -> McVolumeId {
497 if let Some(free_idx) = self.mc.volumes.iter().position(|v| !v.alive) {
498 gpu_data.generation = self.mc.volumes[free_idx].generation;
499 self.mc.volumes[free_idx] = gpu_data;
500 McVolumeId::new(free_idx as u32, self.mc.volumes[free_idx].generation)
501 } else {
502 let idx = self.mc.volumes.len();
503 self.mc.volumes.push(gpu_data);
504 McVolumeId::new(idx as u32, 0)
505 }
506 }
507
508 pub(crate) fn mc_volume(&self, id: McVolumeId) -> Option<&McVolumeGpuData> {
511 let vol = self.mc.volumes.get(id.index as usize)?;
512 if vol.generation != id.generation || !vol.alive {
513 return None;
514 }
515 Some(vol)
516 }
517}
518
519pub(crate) fn build_mc_volume_gpu_data(
522 device: &wgpu::Device,
523 queue: &wgpu::Queue,
524 vol: &VolumeData,
525) -> crate::ViewportResult<McVolumeGpuData> {
526 {
527 let [nx, ny, nz] = vol.dims;
528 let max_binding = device.limits().max_storage_buffer_binding_size as u64;
533 let max_buf = device.limits().max_buffer_size;
534 let max_limit = max_binding.min(max_buf);
535
536 let cells_xy = (nx - 1) as u64 * (ny - 1) as u64;
540 let max_cells_per_slab = max_limit / (15 * 24);
541 let z_cells_per_slab = if cells_xy > 0 {
542 (max_cells_per_slab / cells_xy).min((nz - 1) as u64) as u32
543 } else {
544 nz - 1
545 };
546 if z_cells_per_slab == 0 {
547 return Err(crate::ViewportError::McBufferTooLarge {
549 buffer: "vertex_buf",
550 needed: cells_xy * 15 * 24,
551 limit: max_limit,
552 });
553 }
554
555 let nz_cells_total = nz - 1;
556 let slab_count = nz_cells_total.div_ceil(z_cells_per_slab);
557 let nodes_per_z = (nx * ny) as usize;
558
559 let mut slabs = Vec::with_capacity(slab_count as usize);
560
561 for s in 0..slab_count {
562 let z_cell_start = s * z_cells_per_slab;
563 let z_cell_end = (z_cell_start + z_cells_per_slab).min(nz_cells_total);
564 let slab_z_cells = z_cell_end - z_cell_start; let slab_nz = slab_z_cells + 1; let slab_cell_count = (cells_xy * slab_z_cells as u64) as u32;
570 let slab_block_count = slab_cell_count.div_ceil(256);
571 let slab_cell_bytes = (slab_cell_count as u64) * 4;
572 let slab_block_bytes = (slab_block_count as u64) * 4;
573 let slab_vertex_bytes = (slab_cell_count as u64) * 15 * 24;
575
576 let scalar_start = z_cell_start as usize * nodes_per_z;
580 let scalar_end = (z_cell_start + slab_nz) as usize * nodes_per_z;
581 let slab_origin_z = vol.origin[2] + z_cell_start as f32 * vol.spacing[2];
582
583 let scalar_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
584 label: Some("mc_scalar_buf"),
585 contents: bytemuck::cast_slice(&vol.data[scalar_start..scalar_end]),
586 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
587 });
588 let counts_buf = device.create_buffer(&wgpu::BufferDescriptor {
589 label: Some("mc_counts_buf"),
590 size: slab_cell_bytes,
591 usage: wgpu::BufferUsages::STORAGE,
592 mapped_at_creation: false,
593 });
594 let case_idx_buf = device.create_buffer(&wgpu::BufferDescriptor {
595 label: Some("mc_case_idx_buf"),
596 size: slab_cell_bytes,
597 usage: wgpu::BufferUsages::STORAGE,
598 mapped_at_creation: false,
599 });
600 let offsets_buf = device.create_buffer(&wgpu::BufferDescriptor {
601 label: Some("mc_offsets_buf"),
602 size: slab_cell_bytes,
603 usage: wgpu::BufferUsages::STORAGE,
604 mapped_at_creation: false,
605 });
606 let block_sums_buf = device.create_buffer(&wgpu::BufferDescriptor {
607 label: Some("mc_block_sums_buf"),
608 size: slab_block_bytes,
609 usage: wgpu::BufferUsages::STORAGE,
610 mapped_at_creation: false,
611 });
612 let vertex_buf = device.create_buffer(&wgpu::BufferDescriptor {
613 label: Some("mc_vertex_buf"),
614 size: slab_vertex_bytes,
615 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::VERTEX,
616 mapped_at_creation: false,
617 });
618 let initial_indirect = bytemuck::cast_slice(&[0u32, 1u32, 0u32, 0u32]);
619 let indirect_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
620 label: Some("mc_indirect_buf"),
621 contents: initial_indirect,
623 usage: wgpu::BufferUsages::STORAGE
624 | wgpu::BufferUsages::INDIRECT
625 | wgpu::BufferUsages::COPY_DST,
626 });
627 let wire_indirect_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
628 label: Some("mc_wire_indirect_buf"),
629 contents: initial_indirect,
630 usage: wgpu::BufferUsages::STORAGE
631 | wgpu::BufferUsages::INDIRECT
632 | wgpu::BufferUsages::COPY_DST,
633 });
634
635 slabs.push(McSlabGpuData {
636 scalar_buf,
637 counts_buf,
638 case_idx_buf,
639 offsets_buf,
640 block_sums_buf,
641 vertex_buf,
642 indirect_buf,
643 wire_indirect_buf,
644 dims: [nx, ny, slab_nz],
645 origin: [vol.origin[0], vol.origin[1], slab_origin_z],
646 spacing: vol.spacing,
647 cell_count: slab_cell_count,
648 block_count: slab_block_count,
649 });
650 }
651
652 let _ = queue; Ok(McVolumeGpuData {
655 slabs,
656 alive: true,
657 generation: 0,
658 })
659 }
660}
661
662impl DeviceResources {
663 pub fn begin_upload_volume_for_mc(
682 &mut self,
683 device: &wgpu::Device,
684 queue: &wgpu::Queue,
685 vol: VolumeData,
686 ) -> crate::resources::JobId {
687 let slot = crate::resources::ResultSlot::<McVolumeId>::new();
688 let slot_for_apply = slot.clone();
689 let device_for_worker = device.clone();
690 let queue_for_worker = queue.clone();
691
692 let id = {
693 let mut runner = self.jobs.lock().expect("upload job runner poisoned");
694 runner.submit_cpu(move |progress| {
695 progress.set(0.1);
696 let gpu_data =
697 build_mc_volume_gpu_data(&device_for_worker, &queue_for_worker, &vol)?;
698 progress.set(0.95);
699 Ok(crate::resources::upload_jobs::JobProduct::with_apply(
700 Box::new(move |resources: &mut DeviceResources| {
701 let id = resources.insert_mc_volume_gpu_data(gpu_data);
702 slot_for_apply.set(id);
703 }),
704 ))
705 })
706 };
707
708 self.job_results
709 .volume_mc
710 .lock()
711 .expect("volume mc result map poisoned")
712 .insert(id, slot);
713 id
714 }
715
716 pub fn upload_result_volume_mc(
719 &mut self,
720 id: crate::resources::JobId,
721 ) -> crate::error::ViewportResult<McVolumeId> {
722 let mut map = self
723 .job_results
724 .volume_mc
725 .lock()
726 .expect("volume mc result map poisoned");
727 let slot = match map.get(&id) {
728 Some(s) => s.clone(),
729 None => {
730 return Err(crate::error::ViewportError::JobResultMissing {
731 reason: "unknown id or wrong upload type",
732 });
733 }
734 };
735 match slot.take() {
736 Some(vid) => {
737 map.remove(&id);
738 Ok(vid)
739 }
740 None => Err(crate::error::ViewportError::JobNotReady),
741 }
742 }
743
744 pub fn free_mc_volume(&mut self, id: McVolumeId) {
751 if let Some(v) = self.mc.volumes.get_mut(id.index as usize) {
752 if v.generation == id.generation && v.alive {
753 v.slabs.clear();
754 v.alive = false;
755 v.generation = v.generation.wrapping_add(1);
756 }
757 }
758 }
759
760 pub(crate) fn mc_volume_resident_bytes(&self) -> u64 {
762 self.mc
763 .volumes
764 .iter()
765 .filter(|v| v.alive)
766 .map(|v| v.gpu_bytes())
767 .sum()
768 }
769
770 pub(crate) fn ensure_mc_outline_mask_pipeline(&mut self, device: &wgpu::Device) {
777 if self.mc.outline_mask_pipeline.is_some() {
778 return;
779 }
780
781 let shader = crate::resources::builders::wgsl_module(
782 device,
783 "mc_outline_mask_shader",
784 crate::resources::builders::wgsl_source!("outline_mask"),
785 );
786
787 let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
788 label: Some("mc_outline_mask_pipeline_layout"),
789 bind_group_layouts: &[
790 &self.camera_bind_group_layout,
791 &self.outline.bind_group_layout,
792 ],
793 push_constant_ranges: &[],
794 });
795
796 let vert_attrs = [wgpu::VertexAttribute {
797 offset: 0,
798 shader_location: 0,
799 format: wgpu::VertexFormat::Float32x3,
800 }];
801 let vert_layout = wgpu::VertexBufferLayout {
802 array_stride: 24, step_mode: wgpu::VertexStepMode::Vertex,
804 attributes: &vert_attrs,
805 };
806
807 self.mc.outline_mask_pipeline =
810 Some(crate::resources::builders::build_outline_mask_pipeline(
811 device,
812 "mc_outline_mask_pipeline",
813 &layout,
814 &shader,
815 wgpu::TextureFormat::R8Unorm,
816 &[vert_layout],
817 None,
818 true,
819 wgpu::CompareFunction::LessEqual,
820 ));
821 }
822
823 pub(crate) fn run_mc_jobs(
827 &self,
828 device: &wgpu::Device,
829 queue: &wgpu::Queue,
830 jobs: &[GpuMarchingCubesJob],
831 ) -> Vec<McFrameData> {
832 if jobs.is_empty() {
833 return Vec::new();
834 }
835
836 let classify_pipeline = self.mc.classify_pipeline.as_ref().expect("mc pipelines");
837 let prefix_sum_pipeline = self.mc.prefix_sum_pipeline.as_ref().unwrap();
838 let generate_pipeline = self.mc.generate_pipeline.as_ref().unwrap();
839 let classify_bgl = self.mc.classify_bgl.as_ref().unwrap();
840 let prefix_sum_bgl = self.mc.prefix_sum_bgl.as_ref().unwrap();
841 let generate_bgl = self.mc.generate_bgl.as_ref().unwrap();
842 let render_bgl = self.mc.render_bgl.as_ref().unwrap();
843 let case_count_buf = self.mc.case_count_buf.as_ref().unwrap();
844 let case_table_buf = self.mc.case_table_buf.as_ref().unwrap();
845
846 let mut frame_data = Vec::with_capacity(jobs.len());
847 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
848 label: Some("mc_compute_encoder"),
849 });
850
851 for job in jobs {
852 let Some(vol) = self.mc_volume(job.volume_id) else {
853 continue;
854 };
855
856 let mat_raw = McSurfaceRaw {
860 base_colour: job.material.base_colour,
861 roughness: job.material.roughness,
862 unlit: job.settings.unlit as u32,
863 opacity: job.settings.opacity,
864 ambient: job.material.ambient,
865 _pad: 0,
866 };
867 let mat_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
868 label: Some("mc_surface_mat"),
869 contents: bytemuck::bytes_of(&mat_raw),
870 usage: wgpu::BufferUsages::UNIFORM,
871 });
872 let render_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
873 label: Some("mc_render_bg"),
874 layout: render_bgl,
875 entries: &[wgpu::BindGroupEntry {
876 binding: 0,
877 resource: mat_buf.as_entire_binding(),
878 }],
879 });
880
881 for slab in &vol.slabs {
883 let cc = slab.cell_count;
884 let bc = slab.block_count;
885
886 let classify_params = ClassifyParams {
890 nx: slab.dims[0],
891 ny: slab.dims[1],
892 nz: slab.dims[2],
893 isovalue: job.isovalue,
894 };
895 let classify_uniform =
896 device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
897 label: Some("mc_classify_uniform"),
898 contents: bytemuck::bytes_of(&classify_params),
899 usage: wgpu::BufferUsages::UNIFORM,
900 });
901
902 let classify_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
903 label: Some("mc_classify_bg"),
904 layout: classify_bgl,
905 entries: &[
906 wgpu::BindGroupEntry {
907 binding: 0,
908 resource: classify_uniform.as_entire_binding(),
909 },
910 wgpu::BindGroupEntry {
911 binding: 1,
912 resource: slab.scalar_buf.as_entire_binding(),
913 },
914 wgpu::BindGroupEntry {
915 binding: 2,
916 resource: case_count_buf.as_entire_binding(),
917 },
918 wgpu::BindGroupEntry {
919 binding: 3,
920 resource: slab.counts_buf.as_entire_binding(),
921 },
922 wgpu::BindGroupEntry {
923 binding: 4,
924 resource: slab.case_idx_buf.as_entire_binding(),
925 },
926 ],
927 });
928
929 let ps_uniforms: [wgpu::Buffer; 3] = std::array::from_fn(|level| {
933 let params = PrefixSumParams {
934 cell_count: cc,
935 block_count: bc,
936 level: level as u32,
937 _pad: 0,
938 };
939 device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
940 label: Some("mc_ps_uniform"),
941 contents: bytemuck::bytes_of(¶ms),
942 usage: wgpu::BufferUsages::UNIFORM,
943 })
944 });
945
946 let ps_bgs: [wgpu::BindGroup; 3] = std::array::from_fn(|level| {
947 device.create_bind_group(&wgpu::BindGroupDescriptor {
948 label: Some("mc_ps_bg"),
949 layout: prefix_sum_bgl,
950 entries: &[
951 wgpu::BindGroupEntry {
952 binding: 0,
953 resource: ps_uniforms[level].as_entire_binding(),
954 },
955 wgpu::BindGroupEntry {
956 binding: 1,
957 resource: slab.counts_buf.as_entire_binding(),
958 },
959 wgpu::BindGroupEntry {
960 binding: 2,
961 resource: slab.offsets_buf.as_entire_binding(),
962 },
963 wgpu::BindGroupEntry {
964 binding: 3,
965 resource: slab.block_sums_buf.as_entire_binding(),
966 },
967 wgpu::BindGroupEntry {
968 binding: 4,
969 resource: slab.indirect_buf.as_entire_binding(),
970 },
971 wgpu::BindGroupEntry {
972 binding: 5,
973 resource: slab.wire_indirect_buf.as_entire_binding(),
974 },
975 ],
976 })
977 });
978
979 let generate_params = GenerateParams {
983 nx: slab.dims[0],
984 ny: slab.dims[1],
985 nz: slab.dims[2],
986 isovalue: job.isovalue,
987 origin_x: slab.origin[0],
988 origin_y: slab.origin[1],
989 origin_z: slab.origin[2],
990 _pad0: 0.0,
991 spacing_x: slab.spacing[0],
992 spacing_y: slab.spacing[1],
993 spacing_z: slab.spacing[2],
994 _pad1: 0.0,
995 };
996 let generate_uniform =
997 device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
998 label: Some("mc_generate_uniform"),
999 contents: bytemuck::bytes_of(&generate_params),
1000 usage: wgpu::BufferUsages::UNIFORM,
1001 });
1002
1003 let generate_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
1004 label: Some("mc_generate_bg"),
1005 layout: generate_bgl,
1006 entries: &[
1007 wgpu::BindGroupEntry {
1008 binding: 0,
1009 resource: generate_uniform.as_entire_binding(),
1010 },
1011 wgpu::BindGroupEntry {
1012 binding: 1,
1013 resource: slab.scalar_buf.as_entire_binding(),
1014 },
1015 wgpu::BindGroupEntry {
1016 binding: 2,
1017 resource: case_table_buf.as_entire_binding(),
1018 },
1019 wgpu::BindGroupEntry {
1020 binding: 3,
1021 resource: slab.offsets_buf.as_entire_binding(),
1022 },
1023 wgpu::BindGroupEntry {
1024 binding: 4,
1025 resource: slab.case_idx_buf.as_entire_binding(),
1026 },
1027 wgpu::BindGroupEntry {
1028 binding: 5,
1029 resource: slab.vertex_buf.as_entire_binding(),
1030 },
1031 ],
1032 });
1033
1034 {
1038 let mut cp = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
1039 label: Some("mc_classify_pass"),
1040 timestamp_writes: None,
1041 });
1042 cp.set_pipeline(classify_pipeline);
1043 cp.set_bind_group(0, &classify_bg, &[]);
1044 cp.dispatch_workgroups(cc.div_ceil(256), 1, 1);
1045 }
1046
1047 {
1051 let mut cp = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
1052 label: Some("mc_ps_level0_pass"),
1053 timestamp_writes: None,
1054 });
1055 cp.set_pipeline(prefix_sum_pipeline);
1056 cp.set_bind_group(0, &ps_bgs[0], &[]);
1057 cp.dispatch_workgroups(bc, 1, 1);
1058 }
1059
1060 {
1064 let mut cp = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
1065 label: Some("mc_ps_level1_pass"),
1066 timestamp_writes: None,
1067 });
1068 cp.set_pipeline(prefix_sum_pipeline);
1069 cp.set_bind_group(0, &ps_bgs[1], &[]);
1070 cp.dispatch_workgroups(1, 1, 1);
1071 }
1072
1073 {
1077 let mut cp = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
1078 label: Some("mc_ps_level2_pass"),
1079 timestamp_writes: None,
1080 });
1081 cp.set_pipeline(prefix_sum_pipeline);
1082 cp.set_bind_group(0, &ps_bgs[2], &[]);
1083 cp.dispatch_workgroups(bc, 1, 1);
1084 }
1085
1086 {
1090 let mut cp = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
1091 label: Some("mc_generate_pass"),
1092 timestamp_writes: None,
1093 });
1094 cp.set_pipeline(generate_pipeline);
1095 cp.set_bind_group(0, &generate_bg, &[]);
1096 cp.dispatch_workgroups(cc.div_ceil(256), 1, 1);
1097 }
1098 }
1099
1100 let wire_slab_bgs: Vec<wgpu::BindGroup> =
1101 if let Some(ref wire_bgl) = self.mc.wireframe_render_bgl {
1102 vol.slabs
1103 .iter()
1104 .map(|slab| {
1105 device.create_bind_group(&wgpu::BindGroupDescriptor {
1106 label: Some("mc_wire_slab_bg"),
1107 layout: wire_bgl,
1108 entries: &[wgpu::BindGroupEntry {
1109 binding: 0,
1110 resource: slab.vertex_buf.as_entire_binding(),
1111 }],
1112 })
1113 })
1114 .collect()
1115 } else {
1116 Vec::new()
1117 };
1118
1119 frame_data.push(McFrameData {
1120 volume_idx: job.volume_id.index(),
1121 render_bg,
1122 wireframe: job.settings.wireframe,
1123 wire_slab_bgs,
1124 });
1125 }
1126
1127 queue.submit(std::iter::once(encoder.finish()));
1128 frame_data
1129 }
1130}
1131
1132fn bgl_uniform(binding: u32) -> wgpu::BindGroupLayoutEntry {
1137 wgpu::BindGroupLayoutEntry {
1138 binding,
1139 visibility: wgpu::ShaderStages::COMPUTE,
1140 ty: wgpu::BindingType::Buffer {
1141 ty: wgpu::BufferBindingType::Uniform,
1142 has_dynamic_offset: false,
1143 min_binding_size: None,
1144 },
1145 count: None,
1146 }
1147}
1148
1149fn bgl_storage_ro(binding: u32) -> wgpu::BindGroupLayoutEntry {
1150 wgpu::BindGroupLayoutEntry {
1151 binding,
1152 visibility: wgpu::ShaderStages::COMPUTE,
1153 ty: wgpu::BindingType::Buffer {
1154 ty: wgpu::BufferBindingType::Storage { read_only: true },
1155 has_dynamic_offset: false,
1156 min_binding_size: None,
1157 },
1158 count: None,
1159 }
1160}
1161
1162fn bgl_storage_rw(binding: u32) -> wgpu::BindGroupLayoutEntry {
1163 wgpu::BindGroupLayoutEntry {
1164 binding,
1165 visibility: wgpu::ShaderStages::COMPUTE,
1166 ty: wgpu::BindingType::Buffer {
1167 ty: wgpu::BufferBindingType::Storage { read_only: false },
1168 has_dynamic_offset: false,
1169 min_binding_size: None,
1170 },
1171 count: None,
1172 }
1173}
1174
1175#[cfg(test)]
1176mod residency_tests {
1177 use crate::DeviceResources;
1178 use crate::geometry::marching_cubes::VolumeData;
1179
1180 fn try_make_device() -> Option<(wgpu::Device, wgpu::Queue)> {
1181 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
1182 let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
1183 power_preference: wgpu::PowerPreference::LowPower,
1184 compatible_surface: None,
1185 force_fallback_adapter: false,
1186 }))
1187 .ok()?;
1188 pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor::default())).ok()
1189 }
1190
1191 fn sample_volume() -> VolumeData {
1192 let dims = [4u32, 4, 4];
1193 let data = (0..(dims[0] * dims[1] * dims[2]))
1194 .map(|i| (i % 2) as f32)
1195 .collect();
1196 VolumeData {
1197 data,
1198 dims,
1199 origin: [0.0, 0.0, 0.0],
1200 spacing: [1.0, 1.0, 1.0],
1201 }
1202 }
1203
1204 #[test]
1205 fn stale_mc_volume_handle_does_not_alias_after_slot_reuse() {
1206 let Some((device, queue)) = try_make_device() else {
1207 eprintln!("skipping: no wgpu adapter available");
1208 return;
1209 };
1210 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
1211
1212 let id1 = resources
1213 .upload_volume_for_mc(&device, &queue, &sample_volume())
1214 .unwrap();
1215 assert!(resources.mc_volume(id1).is_some());
1216 resources.free_mc_volume(id1);
1217 assert!(
1218 resources.mc_volume(id1).is_none(),
1219 "a removed handle must not resolve"
1220 );
1221
1222 let id2 = resources
1224 .upload_volume_for_mc(&device, &queue, &sample_volume())
1225 .unwrap();
1226 assert_eq!(id1.index(), id2.index(), "the freed slot should be reused");
1227 assert_ne!(id1, id2, "the reused slot must carry a new generation");
1228 assert!(resources.mc_volume(id2).is_some());
1229 assert!(
1230 resources.mc_volume(id1).is_none(),
1231 "the stale handle must not alias the volume now occupying its slot"
1232 );
1233 }
1234
1235 #[test]
1236 fn free_mc_volume_reclaims_resident_bytes() {
1237 let Some((device, queue)) = try_make_device() else {
1238 eprintln!("skipping: no wgpu adapter available");
1239 return;
1240 };
1241 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
1242
1243 let start = resources.resident_bytes().mc_volume_bytes;
1244 let id = resources
1245 .upload_volume_for_mc(&device, &queue, &sample_volume())
1246 .unwrap();
1247 let after_upload = resources.resident_bytes().mc_volume_bytes;
1248 assert!(
1249 after_upload > start,
1250 "uploading a volume must increase resident volume bytes"
1251 );
1252
1253 resources.free_mc_volume(id);
1254 assert_eq!(
1255 resources.resident_bytes().mc_volume_bytes,
1256 start,
1257 "freeing a volume must drop its slab buffers out of the resident total"
1258 );
1259 }
1260}