1use std::sync::Arc;
10use tracing::{debug, info};
11use wgpu::{
12 BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
13 BindGroupLayoutEntry, BindingType, Buffer, BufferBindingType, BufferDescriptor, BufferUsages,
14 ComputePipeline, ComputePipelineDescriptor, Device, PipelineLayoutDescriptor, Queue,
15 ShaderModuleDescriptor, ShaderSource, ShaderStages,
16};
17
18use crate::gpu_executor::GpuVolume;
19use crate::mpr_ops::MprError;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum RenderMode {
24 MIP,
26 AIP,
28 MinIP,
30 DVR,
32 DVRNoShading,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum TransferFunctionPreset {
39 Bone,
41 SoftTissue,
43 Vessel,
45 Lung,
47 Skin,
49 Custom,
51}
52
53#[repr(C)]
55#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
56pub struct TransferFunctionEntry {
57 pub r: f32,
58 pub g: f32,
59 pub b: f32,
60 pub a: f32,
61}
62
63impl TransferFunctionEntry {
64 pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
65 Self { r, g, b, a }
66 }
67
68 pub fn transparent() -> Self {
69 Self::new(0.0, 0.0, 0.0, 0.0)
70 }
71}
72
73#[derive(Debug, Clone)]
75pub struct TransferFunction {
76 pub entries: Vec<TransferFunctionEntry>,
78 pub min_hu: f32,
80 pub max_hu: f32,
82}
83
84impl TransferFunction {
85 pub fn new(min_hu: f32, max_hu: f32, size: usize) -> Self {
87 Self {
88 entries: vec![TransferFunctionEntry::transparent(); size],
89 min_hu,
90 max_hu,
91 }
92 }
93
94 pub fn from_preset(preset: TransferFunctionPreset) -> Self {
96 match preset {
97 TransferFunctionPreset::Bone => Self::bone_preset(),
98 TransferFunctionPreset::SoftTissue => Self::soft_tissue_preset(),
99 TransferFunctionPreset::Vessel => Self::vessel_preset(),
100 TransferFunctionPreset::Lung => Self::lung_preset(),
101 TransferFunctionPreset::Skin => Self::skin_preset(),
102 TransferFunctionPreset::Custom => Self::new(-1000.0, 3000.0, 256),
103 }
104 }
105
106 pub fn bone_preset() -> Self {
108 let mut tf = Self::new(-200.0, 2000.0, 256);
109 let range = tf.max_hu - tf.min_hu;
110
111 for (i, entry) in tf.entries.iter_mut().enumerate() {
112 let t = i as f32 / 255.0;
113 let hu = tf.min_hu + t * range;
114
115 if hu < 150.0 {
116 *entry = TransferFunctionEntry::transparent();
118 } else if hu < 300.0 {
119 let alpha = (hu - 150.0) / 150.0;
121 *entry = TransferFunctionEntry::new(
122 0.9, 0.85,
124 0.75,
125 alpha * 0.3, );
127 } else if hu < 700.0 {
128 let t_bone = (hu - 300.0) / 400.0;
130 *entry = TransferFunctionEntry::new(
131 0.95 - t_bone * 0.1, 0.9 - t_bone * 0.15,
133 0.75 - t_bone * 0.2,
134 0.4 + t_bone * 0.3, );
136 } else {
137 let t_dense = ((hu - 700.0) / 1300.0).min(1.0);
139 *entry = TransferFunctionEntry::new(
140 1.0, 1.0 - t_dense * 0.1,
142 0.95 - t_dense * 0.2,
143 0.7 + t_dense * 0.3, );
145 }
146 }
147
148 tf
149 }
150
151 pub fn soft_tissue_preset() -> Self {
153 let mut tf = Self::new(-200.0, 400.0, 256);
154 let range = tf.max_hu - tf.min_hu;
155
156 for (i, entry) in tf.entries.iter_mut().enumerate() {
157 let t = i as f32 / 255.0;
158 let hu = tf.min_hu + t * range;
159
160 if hu < -100.0 {
161 *entry = TransferFunctionEntry::transparent();
163 } else if hu < 0.0 {
164 let alpha = (hu + 100.0) / 100.0;
166 *entry = TransferFunctionEntry::new(
167 1.0,
168 0.85,
169 0.4,
170 alpha * 0.15,
171 );
172 } else if hu < 50.0 {
173 let t_water = hu / 50.0;
175 *entry = TransferFunctionEntry::new(
176 0.5 + t_water * 0.3,
177 0.6 + t_water * 0.2,
178 0.9,
179 0.2,
180 );
181 } else if hu < 100.0 {
182 let t_muscle = (hu - 50.0) / 50.0;
184 *entry = TransferFunctionEntry::new(
185 0.8 + t_muscle * 0.15, 0.45 - t_muscle * 0.1, 0.4 - t_muscle * 0.1, 0.25 + t_muscle * 0.1,
189 );
190 } else {
191 let t_dense = ((hu - 100.0) / 300.0).min(1.0);
193 *entry = TransferFunctionEntry::new(
194 0.9 + t_dense * 0.1,
195 0.7 + t_dense * 0.2,
196 0.65 + t_dense * 0.2,
197 0.35 + t_dense * 0.2,
198 );
199 }
200 }
201
202 tf
203 }
204
205 pub fn vessel_preset() -> Self {
207 let mut tf = Self::new(-100.0, 500.0, 256);
208 let range = tf.max_hu - tf.min_hu;
209
210 for (i, entry) in tf.entries.iter_mut().enumerate() {
211 let t = i as f32 / 255.0;
212 let hu = tf.min_hu + t * range;
213
214 if hu < 80.0 {
215 *entry = TransferFunctionEntry::transparent();
217 } else if hu < 150.0 {
218 let alpha = (hu - 80.0) / 70.0;
220 *entry = TransferFunctionEntry::new(
221 0.9,
222 0.2,
223 0.2,
224 alpha * 0.3,
225 );
226 } else if hu < 300.0 {
227 let t_vessel = (hu - 150.0) / 150.0;
229 *entry = TransferFunctionEntry::new(
230 1.0, 0.15 + t_vessel * 0.2,
232 0.1 + t_vessel * 0.15,
233 0.5 + t_vessel * 0.3,
234 );
235 } else {
236 let t_high = ((hu - 300.0) / 200.0).min(1.0);
238 *entry = TransferFunctionEntry::new(
239 1.0,
240 0.4 + t_high * 0.4, 0.25 + t_high * 0.3,
242 0.8 + t_high * 0.2,
243 );
244 }
245 }
246
247 tf
248 }
249
250 pub fn lung_preset() -> Self {
252 let mut tf = Self::new(-1100.0, 0.0, 256);
253 let range = tf.max_hu - tf.min_hu;
254
255 for (i, entry) in tf.entries.iter_mut().enumerate() {
256 let t = i as f32 / 255.0;
257 let hu = tf.min_hu + t * range;
258
259 if hu < -950.0 {
260 *entry = TransferFunctionEntry::transparent();
262 } else if hu < -700.0 {
263 let t_lung = (hu + 950.0) / 250.0;
265 *entry = TransferFunctionEntry::new(
266 0.3 + t_lung * 0.2, 0.4 + t_lung * 0.2,
268 0.7 + t_lung * 0.1,
269 0.05 + t_lung * 0.1,
270 );
271 } else if hu < -400.0 {
272 let t_struct = (hu + 700.0) / 300.0;
274 *entry = TransferFunctionEntry::new(
275 0.5 + t_struct * 0.3,
276 0.4 + t_struct * 0.2,
277 0.6 + t_struct * 0.1,
278 0.15 + t_struct * 0.15,
279 );
280 } else {
281 let t_wall = ((hu + 400.0) / 400.0).min(1.0);
283 *entry = TransferFunctionEntry::new(
284 0.8 + t_wall * 0.15,
285 0.6 + t_wall * 0.2,
286 0.5 + t_wall * 0.2,
287 0.3 + t_wall * 0.2,
288 );
289 }
290 }
291
292 tf
293 }
294
295 pub fn skin_preset() -> Self {
297 let mut tf = Self::new(-500.0, 500.0, 256);
298 let range = tf.max_hu - tf.min_hu;
299
300 for (i, entry) in tf.entries.iter_mut().enumerate() {
301 let t = i as f32 / 255.0;
302 let hu = tf.min_hu + t * range;
303
304 if hu < -200.0 {
305 *entry = TransferFunctionEntry::transparent();
307 } else if hu < -50.0 {
308 let alpha = (hu + 200.0) / 150.0;
310 *entry = TransferFunctionEntry::new(
311 0.95, 0.75,
313 0.6,
314 alpha * 0.6, );
316 } else if hu < 100.0 {
317 *entry = TransferFunctionEntry::new(
319 0.9,
320 0.7,
321 0.55,
322 0.1, );
324 } else {
325 *entry = TransferFunctionEntry::transparent();
327 }
328 }
329
330 tf
331 }
332}
333
334#[derive(Debug, Clone, Copy)]
336pub struct Camera {
337 pub position: [f32; 3],
339 pub look_dir: [f32; 3],
341 pub up: [f32; 3],
343 pub fov: f32,
345}
346
347impl Default for Camera {
348 fn default() -> Self {
349 Self {
350 position: [0.0, 0.0, -500.0],
351 look_dir: [0.0, 0.0, 1.0],
352 up: [0.0, -1.0, 0.0],
353 fov: std::f32::consts::PI / 4.0, }
355 }
356}
357
358impl Camera {
359 pub fn orbit(center: [f32; 3], distance: f32, azimuth: f32, elevation: f32) -> Self {
361 let cos_elev = elevation.cos();
363 let sin_elev = elevation.sin();
364 let cos_azim = azimuth.cos();
365 let sin_azim = azimuth.sin();
366
367 let position = [
368 center[0] + distance * cos_elev * sin_azim,
369 center[1] + distance * sin_elev,
370 center[2] + distance * cos_elev * cos_azim,
371 ];
372
373 let look_dir = [
375 center[0] - position[0],
376 center[1] - position[1],
377 center[2] - position[2],
378 ];
379 let len = (look_dir[0].powi(2) + look_dir[1].powi(2) + look_dir[2].powi(2)).sqrt();
380 let look_dir = [look_dir[0] / len, look_dir[1] / len, look_dir[2] / len];
381
382 let up = [0.0, -1.0, 0.0];
384
385 Self {
386 position,
387 look_dir,
388 up,
389 fov: std::f32::consts::PI / 4.0,
390 }
391 }
392}
393
394#[repr(C)]
396#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
397struct MipUniforms {
398 volume_width: u32,
400 volume_height: u32,
401 volume_depth: u32,
402
403 output_width: u32,
405 output_height: u32,
406
407 camera_x: f32,
409 camera_y: f32,
410 camera_z: f32,
411
412 look_x: f32,
414 look_y: f32,
415 look_z: f32,
416
417 up_x: f32,
419 up_y: f32,
420 up_z: f32,
421
422 fov: f32,
424
425 step_size: f32,
427 max_steps: u32,
428
429 window_width: f32,
431 window_center: f32,
432
433 spacing_x: f32,
435 spacing_y: f32,
436 spacing_z: f32,
437
438 threshold: f32,
440
441 mask_enabled: u32, visible_labels: u32, sculpt_mask_enabled: u32,
447 sculpt_visible_labels: u32,
448
449 clip_enabled: u32,
451 clip_inverted: u32,
452 clip_normal_x: f32,
453 clip_normal_y: f32,
454 clip_normal_z: f32,
455 clip_distance: f32,
456
457 _pad1: u32,
459 _pad2: u32,
460 _pad3: u32,
461}
462
463#[derive(Debug, Clone, Copy, Default)]
465pub struct ClipPlaneConfig {
466 pub enabled: bool,
467 pub inverted: bool,
468 pub normal: [f32; 3],
469 pub distance: f32,
470}
471
472#[derive(Debug, Clone, Copy)]
477pub struct ClipBoxConfig {
478 pub enabled: bool,
479 pub inverted: bool, pub x_min: f32,
482 pub x_max: f32,
483 pub y_min: f32,
485 pub y_max: f32,
486 pub z_min: f32,
488 pub z_max: f32,
489}
490
491impl Default for ClipBoxConfig {
492 fn default() -> Self {
493 Self {
494 enabled: false,
495 inverted: false, x_min: 0.0,
497 x_max: 1.0,
498 y_min: 0.0,
499 y_max: 1.0,
500 z_min: 0.0,
501 z_max: 1.0,
502 }
503 }
504}
505
506impl ClipBoxConfig {
507 #[allow(clippy::too_many_arguments)]
509 pub fn from_percentages(
510 x_min_pct: f32,
511 x_max_pct: f32,
512 y_min_pct: f32,
513 y_max_pct: f32,
514 z_min_pct: f32,
515 z_max_pct: f32,
516 dimensions: [usize; 3],
517 inverted: bool,
518 ) -> Self {
519 let [depth, rows, cols] = dimensions;
520 Self {
521 enabled: true,
522 inverted,
523 x_min: (x_min_pct / 100.0) * cols as f32,
524 x_max: (x_max_pct / 100.0) * cols as f32,
525 y_min: (y_min_pct / 100.0) * rows as f32,
526 y_max: (y_max_pct / 100.0) * rows as f32,
527 z_min: (z_min_pct / 100.0) * depth as f32,
528 z_max: (z_max_pct / 100.0) * depth as f32,
529 }
530 }
531}
532
533#[derive(Debug, Clone, Copy)]
535pub struct SlicePlaneConfig {
536 pub enabled: bool,
538 pub axial: f32,
540 pub sagittal: f32,
542 pub coronal: f32,
544 pub thickness: f32,
546 pub opacity: f32,
548}
549
550impl Default for SlicePlaneConfig {
551 fn default() -> Self {
552 Self {
553 enabled: false,
554 axial: 0.0,
555 sagittal: 0.0,
556 coronal: 0.0,
557 thickness: 1.5,
558 opacity: 0.6,
559 }
560 }
561}
562
563impl SlicePlaneConfig {
564 pub fn new(axial: f32, sagittal: f32, coronal: f32) -> Self {
566 Self {
567 enabled: true,
568 axial,
569 sagittal,
570 coronal,
571 thickness: 1.5,
572 opacity: 0.6,
573 }
574 }
575
576 pub fn from_normalized(
578 axial_norm: f32,
579 sagittal_norm: f32,
580 coronal_norm: f32,
581 dimensions: [usize; 3],
582 ) -> Self {
583 Self {
584 enabled: true,
585 axial: axial_norm * (dimensions[0] - 1) as f32,
586 sagittal: sagittal_norm * (dimensions[2] - 1) as f32,
587 coronal: coronal_norm * (dimensions[1] - 1) as f32,
588 thickness: 1.5,
589 opacity: 0.6,
590 }
591 }
592}
593
594#[derive(Debug, Clone, Copy)]
596pub struct DvrOptions {
597 pub clip_plane: ClipPlaneConfig,
598 pub clip_box: ClipBoxConfig,
599 pub opacity_multiplier: f32,
600 pub light_direction: [f32; 3],
601 pub slice_planes: SlicePlaneConfig,
602 pub step_size: f32,
606}
607
608impl Default for DvrOptions {
609 fn default() -> Self {
610 Self {
611 clip_plane: ClipPlaneConfig::default(),
612 clip_box: ClipBoxConfig::default(),
613 opacity_multiplier: 1.0,
614 light_direction: [0.5, -0.7, -0.5],
615 slice_planes: SlicePlaneConfig::default(),
616 step_size: 0.5, }
618 }
619}
620
621#[repr(C)]
623#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
624struct DvrUniforms {
625 volume_width: u32,
627 volume_height: u32,
628 volume_depth: u32,
629
630 output_width: u32,
632 output_height: u32,
633
634 camera_x: f32,
636 camera_y: f32,
637 camera_z: f32,
638
639 look_x: f32,
641 look_y: f32,
642 look_z: f32,
643
644 up_x: f32,
646 up_y: f32,
647 up_z: f32,
648
649 fov: f32,
651
652 step_size: f32,
654 max_steps: u32,
655
656 tf_min: f32,
658 tf_max: f32,
659 tf_size: u32,
660
661 spacing_x: f32,
663 spacing_y: f32,
664 spacing_z: f32,
665
666 ambient: f32,
668 diffuse: f32,
669 specular: f32,
670 shininess: f32,
671
672 light_x: f32,
674 light_y: f32,
675 light_z: f32,
676
677 opacity_threshold: f32,
679
680 gradient_threshold: f32,
682
683 enable_shading: u32,
685
686 clip_enabled: u32,
688 clip_inverted: u32,
689 clip_normal_x: f32,
690 clip_normal_y: f32,
691 clip_normal_z: f32,
692 clip_distance: f32,
693
694 opacity_multiplier: f32,
696
697 mask_enabled: u32, visible_labels: u32, sculpt_mask_enabled: u32,
703 sculpt_visible_labels: u32,
704
705 slice_planes_enabled: u32,
707 axial_slice: f32,
708 sagittal_slice: f32,
709 coronal_slice: f32,
710 slice_plane_thickness: f32,
711 slice_plane_opacity: f32,
712 _padding2: u32,
713 _padding3: u32,
714}
715
716#[derive(Debug, Clone, Copy, Default)]
718pub struct MaskConfig {
719 pub enabled: bool,
721 pub visible_labels: u32,
724}
725
726impl MaskConfig {
727 pub fn all_visible() -> Self {
729 Self {
730 enabled: true,
731 visible_labels: 0xFFFFFFFF, }
733 }
734
735 pub fn only_label(label: u8) -> Self {
737 Self {
738 enabled: true,
739 visible_labels: 1u32 << label,
740 }
741 }
742
743 pub fn hide_label(label: u8) -> Self {
745 Self {
746 enabled: true,
747 visible_labels: !(1u32 << label),
748 }
749 }
750}
751
752#[repr(C)]
754#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
755pub struct SculptUniforms {
756 pub volume_width: u32,
758 pub volume_height: u32,
759 pub volume_depth: u32,
760
761 pub cam_pos_x: f32,
763 pub cam_pos_y: f32,
764 pub cam_pos_z: f32,
765
766 pub look_x: f32,
768 pub look_y: f32,
769 pub look_z: f32,
770
771 pub right_x: f32,
773 pub right_y: f32,
774 pub right_z: f32,
775
776 pub up_x: f32,
778 pub up_y: f32,
779 pub up_z: f32,
780
781 pub fov_half_tan: f32,
783
784 pub aspect_ratio: f32,
786
787 pub polygon_point_count: u32,
789
790 pub action: u32,
792
793 pub poly_min_x: f32,
795 pub poly_max_x: f32,
796 pub poly_min_y: f32,
797 pub poly_max_y: f32,
798
799 pub spacing_x: f32,
801 pub spacing_y: f32,
802 pub spacing_z: f32,
803
804 pub _padding: f32,
806}
807
808pub struct VolumeRenderer {
810 device: Arc<Device>,
811 queue: Arc<Queue>,
812
813 mip_pipeline: ComputePipeline,
815 aip_pipeline: ComputePipeline,
816 minip_pipeline: ComputePipeline,
817 mip_bind_group_layout: BindGroupLayout,
818 mip_uniform_buffer: Buffer,
819
820 dvr_pipeline: ComputePipeline,
822 dvr_no_shading_pipeline: ComputePipeline,
823 dvr_bind_group_layout: BindGroupLayout,
824 dvr_uniform_buffer: Buffer,
825 transfer_function_buffer: Buffer,
826
827 sculpt_pipeline: ComputePipeline,
829 sculpt_bind_group_layout: BindGroupLayout,
830 sculpt_uniform_buffer: Buffer,
831 sculpt_polygon_buffer: Buffer,
832
833 current_transfer_function: TransferFunction,
835
836 mask_buffer: Option<Buffer>,
838 mask_config: MaskConfig,
839
840 sculpt_mask_buffer: Option<Buffer>,
842 sculpt_mask_config: MaskConfig,
843
844 dummy_mask_buffer: Buffer,
846
847 output_buffer: Buffer,
849 staging_buffer: Buffer,
850
851 output_width: u32,
853 output_height: u32,
854}
855
856impl VolumeRenderer {
857 const WORKGROUP_SIZE: u32 = 16;
858 const TRANSFER_FUNCTION_SIZE: usize = 256;
859
860 pub fn new(
862 device: Arc<Device>,
863 queue: Arc<Queue>,
864 output_width: u32,
865 output_height: u32,
866 ) -> Result<Self, MprError> {
867 let start = std::time::Instant::now();
868
869 let mip_shader_source = include_str!("shaders/mip_raycaster.wgsl");
871 let mip_shader_module = device.create_shader_module(ShaderModuleDescriptor {
872 label: Some("mip_raycaster"),
873 source: ShaderSource::Wgsl(mip_shader_source.into()),
874 });
875
876 let dvr_shader_source = include_str!("shaders/dvr_raycaster.wgsl");
878 let dvr_shader_module = device.create_shader_module(ShaderModuleDescriptor {
879 label: Some("dvr_raycaster"),
880 source: ShaderSource::Wgsl(dvr_shader_source.into()),
881 });
882
883 let mip_bind_group_layout = Self::create_mip_bind_group_layout(&device);
885
886 let dvr_bind_group_layout = Self::create_dvr_bind_group_layout(&device);
888
889 let mip_pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
891 label: Some("mip_pipeline_layout"),
892 bind_group_layouts: &[&mip_bind_group_layout],
893 push_constant_ranges: &[],
894 });
895
896 let mip_pipeline = device.create_compute_pipeline(&ComputePipelineDescriptor {
897 label: Some("mip_pipeline"),
898 layout: Some(&mip_pipeline_layout),
899 module: &mip_shader_module,
900 entry_point: Some("main"),
901 compilation_options: Default::default(),
902 cache: None,
903 });
904
905 let aip_pipeline = device.create_compute_pipeline(&ComputePipelineDescriptor {
906 label: Some("aip_pipeline"),
907 layout: Some(&mip_pipeline_layout),
908 module: &mip_shader_module,
909 entry_point: Some("main_aip"),
910 compilation_options: Default::default(),
911 cache: None,
912 });
913
914 let minip_pipeline = device.create_compute_pipeline(&ComputePipelineDescriptor {
915 label: Some("minip_pipeline"),
916 layout: Some(&mip_pipeline_layout),
917 module: &mip_shader_module,
918 entry_point: Some("main_minip"),
919 compilation_options: Default::default(),
920 cache: None,
921 });
922
923 let dvr_pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
925 label: Some("dvr_pipeline_layout"),
926 bind_group_layouts: &[&dvr_bind_group_layout],
927 push_constant_ranges: &[],
928 });
929
930 let dvr_pipeline = device.create_compute_pipeline(&ComputePipelineDescriptor {
931 label: Some("dvr_pipeline"),
932 layout: Some(&dvr_pipeline_layout),
933 module: &dvr_shader_module,
934 entry_point: Some("main"),
935 compilation_options: Default::default(),
936 cache: None,
937 });
938
939 let dvr_no_shading_pipeline = device.create_compute_pipeline(&ComputePipelineDescriptor {
940 label: Some("dvr_no_shading_pipeline"),
941 layout: Some(&dvr_pipeline_layout),
942 module: &dvr_shader_module,
943 entry_point: Some("main_no_shading"),
944 compilation_options: Default::default(),
945 cache: None,
946 });
947
948 let sculpt_shader_source = include_str!("shaders/sculpt_mask.wgsl");
950 let sculpt_shader_module = device.create_shader_module(ShaderModuleDescriptor {
951 label: Some("sculpt_mask"),
952 source: ShaderSource::Wgsl(sculpt_shader_source.into()),
953 });
954
955 let sculpt_bind_group_layout = Self::create_sculpt_bind_group_layout(&device);
957
958 let sculpt_pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
960 label: Some("sculpt_pipeline_layout"),
961 bind_group_layouts: &[&sculpt_bind_group_layout],
962 push_constant_ranges: &[],
963 });
964
965 let sculpt_pipeline = device.create_compute_pipeline(&ComputePipelineDescriptor {
966 label: Some("sculpt_pipeline"),
967 layout: Some(&sculpt_pipeline_layout),
968 module: &sculpt_shader_module,
969 entry_point: Some("main"),
970 compilation_options: Default::default(),
971 cache: None,
972 });
973
974 let mip_uniform_buffer = device.create_buffer(&BufferDescriptor {
976 label: Some("mip_uniforms"),
977 size: std::mem::size_of::<MipUniforms>() as u64,
978 usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
979 mapped_at_creation: false,
980 });
981
982 let dvr_uniform_buffer = device.create_buffer(&BufferDescriptor {
984 label: Some("dvr_uniforms"),
985 size: std::mem::size_of::<DvrUniforms>() as u64,
986 usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
987 mapped_at_creation: false,
988 });
989
990 let sculpt_uniform_buffer = device.create_buffer(&BufferDescriptor {
992 label: Some("sculpt_uniforms"),
993 size: std::mem::size_of::<SculptUniforms>() as u64,
994 usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
995 mapped_at_creation: false,
996 });
997
998 let sculpt_polygon_buffer = device.create_buffer(&BufferDescriptor {
1000 label: Some("sculpt_polygon"),
1001 size: (1024 * 2 * std::mem::size_of::<f32>()) as u64,
1002 usage: BufferUsages::STORAGE | BufferUsages::COPY_DST,
1003 mapped_at_creation: false,
1004 });
1005
1006 let default_tf = TransferFunction::from_preset(TransferFunctionPreset::Bone);
1008 let transfer_function_buffer = device.create_buffer(&BufferDescriptor {
1009 label: Some("transfer_function"),
1010 size: (Self::TRANSFER_FUNCTION_SIZE * std::mem::size_of::<TransferFunctionEntry>()) as u64,
1011 usage: BufferUsages::STORAGE | BufferUsages::COPY_DST,
1012 mapped_at_creation: false,
1013 });
1014
1015 queue.write_buffer(
1017 &transfer_function_buffer,
1018 0,
1019 bytemuck::cast_slice(&default_tf.entries),
1020 );
1021
1022 let output_size = (output_width * output_height * 4) as u64; let output_buffer = device.create_buffer(&BufferDescriptor {
1025 label: Some("volume_render_output"),
1026 size: output_size,
1027 usage: BufferUsages::STORAGE | BufferUsages::COPY_SRC,
1028 mapped_at_creation: false,
1029 });
1030
1031 let staging_buffer = device.create_buffer(&BufferDescriptor {
1033 label: Some("volume_render_staging"),
1034 size: output_size,
1035 usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST,
1036 mapped_at_creation: false,
1037 });
1038
1039 let dummy_mask_buffer = device.create_buffer(&BufferDescriptor {
1042 label: Some("dummy_mask"),
1043 size: 4, usage: BufferUsages::STORAGE | BufferUsages::COPY_DST,
1045 mapped_at_creation: false,
1046 });
1047
1048 info!(
1049 "Volume renderer created ({}x{}) with DVR and Sculpt support in {:.1}ms",
1050 output_width,
1051 output_height,
1052 start.elapsed().as_secs_f64() * 1000.0
1053 );
1054
1055 Ok(Self {
1056 device,
1057 queue,
1058 mip_pipeline,
1059 aip_pipeline,
1060 minip_pipeline,
1061 mip_bind_group_layout,
1062 mip_uniform_buffer,
1063 dvr_pipeline,
1064 dvr_no_shading_pipeline,
1065 dvr_bind_group_layout,
1066 dvr_uniform_buffer,
1067 transfer_function_buffer,
1068 sculpt_pipeline,
1069 sculpt_bind_group_layout,
1070 sculpt_uniform_buffer,
1071 sculpt_polygon_buffer,
1072 current_transfer_function: default_tf,
1073 mask_buffer: None,
1074 mask_config: MaskConfig::default(),
1075 sculpt_mask_buffer: None,
1076 sculpt_mask_config: MaskConfig::default(),
1077 dummy_mask_buffer,
1078 output_buffer,
1079 staging_buffer,
1080 output_width,
1081 output_height,
1082 })
1083 }
1084
1085 fn create_mip_bind_group_layout(device: &Device) -> BindGroupLayout {
1086 device.create_bind_group_layout(&BindGroupLayoutDescriptor {
1087 label: Some("mip_bind_group_layout"),
1088 entries: &[
1089 BindGroupLayoutEntry {
1091 binding: 0,
1092 visibility: ShaderStages::COMPUTE,
1093 ty: BindingType::Buffer {
1094 ty: BufferBindingType::Storage { read_only: true },
1095 has_dynamic_offset: false,
1096 min_binding_size: None,
1097 },
1098 count: None,
1099 },
1100 BindGroupLayoutEntry {
1102 binding: 1,
1103 visibility: ShaderStages::COMPUTE,
1104 ty: BindingType::Buffer {
1105 ty: BufferBindingType::Uniform,
1106 has_dynamic_offset: false,
1107 min_binding_size: None,
1108 },
1109 count: None,
1110 },
1111 BindGroupLayoutEntry {
1113 binding: 2,
1114 visibility: ShaderStages::COMPUTE,
1115 ty: BindingType::Buffer {
1116 ty: BufferBindingType::Storage { read_only: false },
1117 has_dynamic_offset: false,
1118 min_binding_size: None,
1119 },
1120 count: None,
1121 },
1122 BindGroupLayoutEntry {
1124 binding: 3,
1125 visibility: ShaderStages::COMPUTE,
1126 ty: BindingType::Buffer {
1127 ty: BufferBindingType::Storage { read_only: true },
1128 has_dynamic_offset: false,
1129 min_binding_size: None,
1130 },
1131 count: None,
1132 },
1133 BindGroupLayoutEntry {
1135 binding: 4,
1136 visibility: ShaderStages::COMPUTE,
1137 ty: BindingType::Buffer {
1138 ty: BufferBindingType::Storage { read_only: true },
1139 has_dynamic_offset: false,
1140 min_binding_size: None,
1141 },
1142 count: None,
1143 },
1144 ],
1145 })
1146 }
1147
1148 fn create_dvr_bind_group_layout(device: &Device) -> BindGroupLayout {
1149 device.create_bind_group_layout(&BindGroupLayoutDescriptor {
1150 label: Some("dvr_bind_group_layout"),
1151 entries: &[
1152 BindGroupLayoutEntry {
1154 binding: 0,
1155 visibility: ShaderStages::COMPUTE,
1156 ty: BindingType::Buffer {
1157 ty: BufferBindingType::Storage { read_only: true },
1158 has_dynamic_offset: false,
1159 min_binding_size: None,
1160 },
1161 count: None,
1162 },
1163 BindGroupLayoutEntry {
1165 binding: 1,
1166 visibility: ShaderStages::COMPUTE,
1167 ty: BindingType::Buffer {
1168 ty: BufferBindingType::Uniform,
1169 has_dynamic_offset: false,
1170 min_binding_size: None,
1171 },
1172 count: None,
1173 },
1174 BindGroupLayoutEntry {
1176 binding: 2,
1177 visibility: ShaderStages::COMPUTE,
1178 ty: BindingType::Buffer {
1179 ty: BufferBindingType::Storage { read_only: false },
1180 has_dynamic_offset: false,
1181 min_binding_size: None,
1182 },
1183 count: None,
1184 },
1185 BindGroupLayoutEntry {
1187 binding: 3,
1188 visibility: ShaderStages::COMPUTE,
1189 ty: BindingType::Buffer {
1190 ty: BufferBindingType::Storage { read_only: true },
1191 has_dynamic_offset: false,
1192 min_binding_size: None,
1193 },
1194 count: None,
1195 },
1196 BindGroupLayoutEntry {
1198 binding: 4,
1199 visibility: ShaderStages::COMPUTE,
1200 ty: BindingType::Buffer {
1201 ty: BufferBindingType::Storage { read_only: true },
1202 has_dynamic_offset: false,
1203 min_binding_size: None,
1204 },
1205 count: None,
1206 },
1207 BindGroupLayoutEntry {
1209 binding: 5,
1210 visibility: ShaderStages::COMPUTE,
1211 ty: BindingType::Buffer {
1212 ty: BufferBindingType::Storage { read_only: true },
1213 has_dynamic_offset: false,
1214 min_binding_size: None,
1215 },
1216 count: None,
1217 },
1218 ],
1219 })
1220 }
1221
1222 fn create_sculpt_bind_group_layout(device: &Device) -> BindGroupLayout {
1223 device.create_bind_group_layout(&BindGroupLayoutDescriptor {
1224 label: Some("sculpt_bind_group_layout"),
1225 entries: &[
1226 BindGroupLayoutEntry {
1228 binding: 0,
1229 visibility: ShaderStages::COMPUTE,
1230 ty: BindingType::Buffer {
1231 ty: BufferBindingType::Uniform,
1232 has_dynamic_offset: false,
1233 min_binding_size: None,
1234 },
1235 count: None,
1236 },
1237 BindGroupLayoutEntry {
1239 binding: 1,
1240 visibility: ShaderStages::COMPUTE,
1241 ty: BindingType::Buffer {
1242 ty: BufferBindingType::Storage { read_only: true },
1243 has_dynamic_offset: false,
1244 min_binding_size: None,
1245 },
1246 count: None,
1247 },
1248 BindGroupLayoutEntry {
1250 binding: 2,
1251 visibility: ShaderStages::COMPUTE,
1252 ty: BindingType::Buffer {
1253 ty: BufferBindingType::Storage { read_only: false },
1254 has_dynamic_offset: false,
1255 min_binding_size: None,
1256 },
1257 count: None,
1258 },
1259 ],
1260 })
1261 }
1262
1263 pub fn set_transfer_function_preset(&mut self, preset: TransferFunctionPreset) {
1265 self.current_transfer_function = TransferFunction::from_preset(preset);
1266 self.upload_transfer_function();
1267 }
1268
1269 pub fn set_transfer_function(&mut self, tf: TransferFunction) {
1271 self.current_transfer_function = tf;
1272 self.upload_transfer_function();
1273 }
1274
1275 pub fn transfer_function(&self) -> &TransferFunction {
1277 &self.current_transfer_function
1278 }
1279
1280 fn upload_transfer_function(&self) {
1281 let mut entries = self.current_transfer_function.entries.clone();
1283 entries.resize(Self::TRANSFER_FUNCTION_SIZE, TransferFunctionEntry::transparent());
1284
1285 self.queue.write_buffer(
1286 &self.transfer_function_buffer,
1287 0,
1288 bytemuck::cast_slice(&entries),
1289 );
1290 }
1291
1292 pub fn upload_mask(&mut self, mask_data: &[u8], dimensions: [usize; 3]) {
1296 let total_voxels = dimensions[0] * dimensions[1] * dimensions[2];
1297
1298 if mask_data.len() != total_voxels {
1300 tracing::warn!(
1301 "Mask size mismatch: expected {} voxels, got {}",
1302 total_voxels,
1303 mask_data.len()
1304 );
1305 return;
1306 }
1307
1308 let packed_size = total_voxels.div_ceil(4);
1310 let mut packed_data: Vec<u32> = vec![0; packed_size];
1311
1312 for (i, &label) in mask_data.iter().enumerate() {
1313 let word_index = i / 4;
1314 let byte_offset = (i % 4) * 8;
1315 packed_data[word_index] |= (label as u32) << byte_offset;
1316 }
1317
1318 let buffer_size = (packed_size * std::mem::size_of::<u32>()) as u64;
1320
1321 let needs_recreate = match &self.mask_buffer {
1322 Some(buf) => buf.size() != buffer_size,
1323 None => true,
1324 };
1325
1326 if needs_recreate {
1327 self.mask_buffer = Some(self.device.create_buffer(&BufferDescriptor {
1328 label: Some("segmentation_mask"),
1329 size: buffer_size,
1330 usage: BufferUsages::STORAGE | BufferUsages::COPY_DST,
1331 mapped_at_creation: false,
1332 }));
1333 }
1334
1335 if let Some(ref buffer) = self.mask_buffer {
1337 self.queue.write_buffer(buffer, 0, bytemuck::cast_slice(&packed_data));
1338 }
1339
1340 debug!(
1341 "Uploaded mask: {}x{}x{} ({} voxels, {} packed u32s)",
1342 dimensions[0], dimensions[1], dimensions[2], total_voxels, packed_size
1343 );
1344 }
1345
1346 pub fn set_mask_config(&mut self, config: MaskConfig) {
1348 self.mask_config = config;
1349 }
1350
1351 pub fn mask_config(&self) -> &MaskConfig {
1353 &self.mask_config
1354 }
1355
1356 #[allow(dead_code)]
1359 fn get_effective_mask(&self) -> (&Buffer, bool, u32) {
1360 if self.sculpt_mask_config.enabled {
1362 if let Some(ref buf) = self.sculpt_mask_buffer {
1363 return (buf, true, self.sculpt_mask_config.visible_labels);
1364 }
1365 }
1366
1367 if self.mask_config.enabled {
1369 if let Some(ref buf) = self.mask_buffer {
1370 return (buf, true, self.mask_config.visible_labels);
1371 }
1372 }
1373
1374 (&self.dummy_mask_buffer, false, 0xFFFFFFFF)
1376 }
1377
1378 pub fn has_mask(&self) -> bool {
1380 self.mask_buffer.is_some()
1381 }
1382
1383 pub fn has_sculpt_mask(&self) -> bool {
1385 self.sculpt_mask_buffer.is_some()
1386 }
1387
1388 pub fn clear_mask(&mut self) {
1390 self.mask_buffer = None;
1391 self.mask_config = MaskConfig::default();
1392 }
1393
1394 pub fn clear_sculpt_mask(&mut self) {
1396 self.sculpt_mask_buffer = None;
1397 self.sculpt_mask_config = MaskConfig::default();
1398 info!("Sculpt mask cleared (segmentation mask preserved)");
1399 }
1400
1401 pub fn generate_sculpt_mask_gpu(
1415 &mut self,
1416 dimensions: [usize; 3],
1417 spacing: [f32; 3],
1418 camera: &Camera,
1419 polygon_points: &[[f32; 2]],
1420 action: u32,
1421 aspect_ratio: f32,
1422 ) -> Result<usize, MprError> {
1423 let start = std::time::Instant::now();
1424
1425 let [depth, rows, cols] = dimensions;
1426 let total_voxels = depth * rows * cols;
1427
1428 if polygon_points.len() < 3 {
1430 return Err(MprError::GpuError("Lasso must have at least 3 points".to_string()));
1431 }
1432
1433 if polygon_points.len() > 1024 {
1434 return Err(MprError::GpuError("Lasso has too many points (max 1024)".to_string()));
1435 }
1436
1437 info!(
1438 "GPU sculpt mask generation: {}x{}x{} volume, {} polygon points, action={}",
1439 depth, rows, cols, polygon_points.len(), action
1440 );
1441
1442 let look = camera.look_dir;
1444 let up = camera.up;
1445
1446 let rx = look[1] * up[2] - look[2] * up[1];
1448 let ry = look[2] * up[0] - look[0] * up[2];
1449 let rz = look[0] * up[1] - look[1] * up[0];
1450 let r_len = (rx * rx + ry * ry + rz * rz).sqrt().max(1e-8);
1451 let right = [rx / r_len, ry / r_len, rz / r_len];
1452
1453 let ux = right[1] * look[2] - right[2] * look[1];
1455 let uy = right[2] * look[0] - right[0] * look[2];
1456 let uz = right[0] * look[1] - right[1] * look[0];
1457 let u_len = (ux * ux + uy * uy + uz * uz).sqrt().max(1e-8);
1458 let up_corrected = [ux / u_len, uy / u_len, uz / u_len];
1459
1460 let mut poly_min_x = f32::MAX;
1462 let mut poly_max_x = f32::MIN;
1463 let mut poly_min_y = f32::MAX;
1464 let mut poly_max_y = f32::MIN;
1465 for p in polygon_points {
1466 poly_min_x = poly_min_x.min(p[0]);
1467 poly_max_x = poly_max_x.max(p[0]);
1468 poly_min_y = poly_min_y.min(p[1]);
1469 poly_max_y = poly_max_y.max(p[1]);
1470 }
1471 poly_min_x -= 0.05;
1473 poly_max_x += 0.05;
1474 poly_min_y -= 0.05;
1475 poly_max_y += 0.05;
1476
1477 let uniforms = SculptUniforms {
1479 volume_width: cols as u32,
1480 volume_height: rows as u32,
1481 volume_depth: depth as u32,
1482 cam_pos_x: camera.position[0],
1483 cam_pos_y: camera.position[1],
1484 cam_pos_z: camera.position[2],
1485 look_x: look[0],
1486 look_y: look[1],
1487 look_z: look[2],
1488 right_x: right[0],
1489 right_y: right[1],
1490 right_z: right[2],
1491 up_x: up_corrected[0],
1492 up_y: up_corrected[1],
1493 up_z: up_corrected[2],
1494 fov_half_tan: (camera.fov / 2.0).tan(),
1495 aspect_ratio,
1496 polygon_point_count: polygon_points.len() as u32,
1497 action,
1498 poly_min_x,
1499 poly_max_x,
1500 poly_min_y,
1501 poly_max_y,
1502 spacing_x: spacing[2], spacing_y: spacing[1], spacing_z: spacing[0], _padding: 0.0,
1506 };
1507
1508 self.queue.write_buffer(
1510 &self.sculpt_uniform_buffer,
1511 0,
1512 bytemuck::bytes_of(&uniforms),
1513 );
1514
1515 let polygon_data: Vec<f32> = polygon_points
1517 .iter()
1518 .flat_map(|p| [p[0], p[1]])
1519 .collect();
1520
1521 self.queue.write_buffer(
1523 &self.sculpt_polygon_buffer,
1524 0,
1525 bytemuck::cast_slice(&polygon_data),
1526 );
1527
1528 let packed_size = total_voxels.div_ceil(4);
1530 let mask_buffer_size = (packed_size * std::mem::size_of::<u32>()) as u64;
1531
1532 let needs_recreate = match &self.sculpt_mask_buffer {
1534 Some(buf) => buf.size() != mask_buffer_size,
1535 None => true,
1536 };
1537
1538 if needs_recreate {
1539 self.sculpt_mask_buffer = Some(self.device.create_buffer(&BufferDescriptor {
1540 label: Some("sculpt_mask_output"),
1541 size: mask_buffer_size,
1542 usage: BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
1543 mapped_at_creation: false,
1544 }));
1545
1546 let zeros = vec![0u32; packed_size];
1548 self.queue.write_buffer(
1549 self.sculpt_mask_buffer.as_ref().unwrap(),
1550 0,
1551 bytemuck::cast_slice(&zeros),
1552 );
1553
1554 info!("Created new sculpt mask buffer (separate from segmentation)");
1555 }
1556 let sculpt_buffer = self.sculpt_mask_buffer.as_ref().unwrap();
1560
1561 let bind_group = self.device.create_bind_group(&BindGroupDescriptor {
1563 label: Some("sculpt_bind_group"),
1564 layout: &self.sculpt_bind_group_layout,
1565 entries: &[
1566 BindGroupEntry {
1567 binding: 0,
1568 resource: self.sculpt_uniform_buffer.as_entire_binding(),
1569 },
1570 BindGroupEntry {
1571 binding: 1,
1572 resource: self.sculpt_polygon_buffer.as_entire_binding(),
1573 },
1574 BindGroupEntry {
1575 binding: 2,
1576 resource: sculpt_buffer.as_entire_binding(),
1577 },
1578 ],
1579 });
1580
1581 let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
1583 label: Some("sculpt_encoder"),
1584 });
1585
1586 {
1587 let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
1588 label: Some("sculpt_pass"),
1589 timestamp_writes: None,
1590 });
1591
1592 compute_pass.set_pipeline(&self.sculpt_pipeline);
1593 compute_pass.set_bind_group(0, &bind_group, &[]);
1594
1595 let workgroups_x = (cols as u32).div_ceil(8);
1597 let workgroups_y = (rows as u32).div_ceil(8);
1598 let workgroups_z = (depth as u32).div_ceil(4);
1599
1600 compute_pass.dispatch_workgroups(workgroups_x, workgroups_y, workgroups_z);
1601 }
1602
1603 self.queue.submit(std::iter::once(encoder.finish()));
1604
1605 self.device.poll(wgpu::Maintain::Wait);
1607
1608 let elapsed_ms = start.elapsed().as_secs_f64() * 1000.0;
1609
1610 info!(
1611 "GPU sculpt mask generated in {:.1}ms ({} voxels, {} workgroups)",
1612 elapsed_ms,
1613 total_voxels,
1614 cols.div_ceil(8) * rows.div_ceil(8) * depth.div_ceil(4)
1615 );
1616
1617 self.sculpt_mask_config = MaskConfig {
1621 enabled: true,
1622 visible_labels: 0b11,
1623 };
1624
1625 let bbox_coverage = (poly_max_x - poly_min_x) * (poly_max_y - poly_min_y);
1628 let estimated_affected = (total_voxels as f32 * bbox_coverage * 0.5) as usize;
1629
1630 Ok(estimated_affected)
1631 }
1632
1633 pub fn render(
1635 &self,
1636 volume: &GpuVolume,
1637 camera: &Camera,
1638 mode: RenderMode,
1639 window_center: f32,
1640 window_width: f32,
1641 spacing: [f32; 3],
1642 ) -> Result<RenderResult, MprError> {
1643 self.render_with_options(
1644 volume,
1645 camera,
1646 mode,
1647 window_center,
1648 window_width,
1649 spacing,
1650 DvrOptions::default(),
1651 )
1652 }
1653
1654 #[allow(clippy::too_many_arguments)]
1656 pub fn render_with_options(
1657 &self,
1658 volume: &GpuVolume,
1659 camera: &Camera,
1660 mode: RenderMode,
1661 window_center: f32,
1662 window_width: f32,
1663 spacing: [f32; 3],
1664 options: DvrOptions,
1665 ) -> Result<RenderResult, MprError> {
1666 match mode {
1667 RenderMode::MIP | RenderMode::AIP | RenderMode::MinIP => {
1668 self.render_mip(volume, camera, mode, window_center, window_width, spacing, &options)
1669 }
1670 RenderMode::DVR | RenderMode::DVRNoShading => {
1671 self.render_dvr(volume, camera, mode, spacing, options)
1672 }
1673 }
1674 }
1675
1676 #[allow(clippy::too_many_arguments)]
1678 fn render_mip(
1679 &self,
1680 volume: &GpuVolume,
1681 camera: &Camera,
1682 mode: RenderMode,
1683 window_center: f32,
1684 window_width: f32,
1685 spacing: [f32; 3],
1686 options: &DvrOptions,
1687 ) -> Result<RenderResult, MprError> {
1688 let start = std::time::Instant::now();
1689
1690 let volume_buffer = volume.buffer().ok_or_else(|| {
1693 MprError::GpuError(
1694 "Volume exceeds 2GB GPU buffer limit. Consider downsampling \
1695 the volume or using fewer slices.".to_string()
1696 )
1697 })?;
1698
1699 let dims = volume.dimensions();
1700 let step_size = options.step_size;
1701 let clip = &options.clip_plane;
1702
1703 let max_diagonal = ((dims[0].pow(2) + dims[1].pow(2) + dims[2].pow(2)) as f32).sqrt();
1706 let max_steps = (max_diagonal / step_size * 2.0) as u32; let uniforms = MipUniforms {
1710 volume_width: dims[2] as u32, volume_height: dims[1] as u32, volume_depth: dims[0] as u32, output_width: self.output_width,
1714 output_height: self.output_height,
1715 camera_x: camera.position[0],
1716 camera_y: camera.position[1],
1717 camera_z: camera.position[2],
1718 look_x: camera.look_dir[0],
1719 look_y: camera.look_dir[1],
1720 look_z: camera.look_dir[2],
1721 up_x: camera.up[0],
1722 up_y: camera.up[1],
1723 up_z: camera.up[2],
1724 fov: camera.fov,
1725 step_size,
1726 max_steps,
1727 window_width,
1728 window_center,
1729 spacing_x: spacing[0],
1730 spacing_y: spacing[1],
1731 spacing_z: spacing[2],
1732 threshold: 0.0, mask_enabled: if self.mask_config.enabled && self.mask_buffer.is_some() { 1 } else { 0 },
1735 visible_labels: self.mask_config.visible_labels,
1736 sculpt_mask_enabled: if self.sculpt_mask_config.enabled && self.sculpt_mask_buffer.is_some() { 1 } else { 0 },
1738 sculpt_visible_labels: self.sculpt_mask_config.visible_labels,
1739 clip_enabled: if clip.enabled { 1 } else { 0 },
1740 clip_inverted: if clip.inverted { 1 } else { 0 },
1741 clip_normal_x: clip.normal[0],
1742 clip_normal_y: clip.normal[1],
1743 clip_normal_z: clip.normal[2],
1744 clip_distance: clip.distance,
1745 _pad1: 0,
1746 _pad2: 0,
1747 _pad3: 0,
1748 };
1749
1750 self.queue
1751 .write_buffer(&self.mip_uniform_buffer, 0, bytemuck::bytes_of(&uniforms));
1752
1753 let seg_mask_buffer = self.mask_buffer.as_ref().unwrap_or(&self.dummy_mask_buffer);
1755 let sculpt_mask_buffer = self.sculpt_mask_buffer.as_ref().unwrap_or(&self.dummy_mask_buffer);
1756
1757 let bind_group = self.device.create_bind_group(&BindGroupDescriptor {
1759 label: Some("mip_bind_group"),
1760 layout: &self.mip_bind_group_layout,
1761 entries: &[
1762 BindGroupEntry {
1763 binding: 0,
1764 resource: volume_buffer.as_entire_binding(),
1765 },
1766 BindGroupEntry {
1767 binding: 1,
1768 resource: self.mip_uniform_buffer.as_entire_binding(),
1769 },
1770 BindGroupEntry {
1771 binding: 2,
1772 resource: self.output_buffer.as_entire_binding(),
1773 },
1774 BindGroupEntry {
1775 binding: 3,
1776 resource: seg_mask_buffer.as_entire_binding(),
1777 },
1778 BindGroupEntry {
1779 binding: 4,
1780 resource: sculpt_mask_buffer.as_entire_binding(),
1781 },
1782 ],
1783 });
1784
1785 let pipeline = match mode {
1787 RenderMode::MIP => &self.mip_pipeline,
1788 RenderMode::AIP => &self.aip_pipeline,
1789 RenderMode::MinIP => &self.minip_pipeline,
1790 _ => unreachable!(),
1791 };
1792
1793 self.execute_and_readback(pipeline, &bind_group, mode, start)
1795 }
1796
1797 fn render_dvr(
1799 &self,
1800 volume: &GpuVolume,
1801 camera: &Camera,
1802 mode: RenderMode,
1803 spacing: [f32; 3],
1804 options: DvrOptions,
1805 ) -> Result<RenderResult, MprError> {
1806 let start = std::time::Instant::now();
1807
1808 let volume_buffer = volume.buffer().ok_or_else(|| {
1810 MprError::GpuError(
1811 "Volume exceeds 2GB GPU buffer limit. Consider downsampling \
1812 the volume or using fewer slices.".to_string()
1813 )
1814 })?;
1815
1816 let dims = volume.dimensions();
1817 let tf = &self.current_transfer_function;
1818
1819 let step_size = options.step_size;
1821 let max_diagonal = ((dims[0].pow(2) + dims[1].pow(2) + dims[2].pow(2)) as f32).sqrt();
1822 let max_steps = (max_diagonal / step_size * 2.0) as u32;
1823
1824 let enable_shading = if mode == RenderMode::DVR { 1u32 } else { 0u32 };
1825
1826 let clip = &options.clip_plane;
1828
1829 let uniforms = DvrUniforms {
1831 volume_width: dims[2] as u32,
1832 volume_height: dims[1] as u32,
1833 volume_depth: dims[0] as u32,
1834 output_width: self.output_width,
1835 output_height: self.output_height,
1836 camera_x: camera.position[0],
1837 camera_y: camera.position[1],
1838 camera_z: camera.position[2],
1839 look_x: camera.look_dir[0],
1840 look_y: camera.look_dir[1],
1841 look_z: camera.look_dir[2],
1842 up_x: camera.up[0],
1843 up_y: camera.up[1],
1844 up_z: camera.up[2],
1845 fov: camera.fov,
1846 step_size,
1847 max_steps,
1848 tf_min: tf.min_hu,
1849 tf_max: tf.max_hu,
1850 tf_size: tf.entries.len() as u32,
1851 spacing_x: spacing[0],
1852 spacing_y: spacing[1],
1853 spacing_z: spacing[2],
1854 ambient: 0.2,
1855 diffuse: 0.7,
1856 specular: 0.3,
1857 shininess: 32.0,
1858 light_x: options.light_direction[0],
1859 light_y: options.light_direction[1],
1860 light_z: options.light_direction[2],
1861 opacity_threshold: 0.95,
1862 gradient_threshold: 10.0,
1863 enable_shading,
1864 clip_enabled: if clip.enabled { 1 } else { 0 },
1865 clip_inverted: if clip.inverted { 1 } else { 0 },
1866 clip_normal_x: clip.normal[0],
1867 clip_normal_y: clip.normal[1],
1868 clip_normal_z: clip.normal[2],
1869 clip_distance: clip.distance,
1870 opacity_multiplier: options.opacity_multiplier,
1871 mask_enabled: if self.mask_config.enabled && self.mask_buffer.is_some() { 1 } else { 0 },
1873 visible_labels: self.mask_config.visible_labels,
1874 sculpt_mask_enabled: if self.sculpt_mask_config.enabled && self.sculpt_mask_buffer.is_some() { 1 } else { 0 },
1876 sculpt_visible_labels: self.sculpt_mask_config.visible_labels,
1877 slice_planes_enabled: if options.slice_planes.enabled { 1 } else { 0 },
1879 axial_slice: options.slice_planes.axial,
1880 sagittal_slice: options.slice_planes.sagittal,
1881 coronal_slice: options.slice_planes.coronal,
1882 slice_plane_thickness: options.slice_planes.thickness,
1883 slice_plane_opacity: options.slice_planes.opacity,
1884 _padding2: 0,
1885 _padding3: 0,
1886 };
1887
1888 self.queue
1889 .write_buffer(&self.dvr_uniform_buffer, 0, bytemuck::bytes_of(&uniforms));
1890
1891 let seg_mask_buffer = self.mask_buffer.as_ref().unwrap_or(&self.dummy_mask_buffer);
1893 let sculpt_mask_buffer = self.sculpt_mask_buffer.as_ref().unwrap_or(&self.dummy_mask_buffer);
1894
1895 let bind_group = self.device.create_bind_group(&BindGroupDescriptor {
1897 label: Some("dvr_bind_group"),
1898 layout: &self.dvr_bind_group_layout,
1899 entries: &[
1900 BindGroupEntry {
1901 binding: 0,
1902 resource: volume_buffer.as_entire_binding(),
1903 },
1904 BindGroupEntry {
1905 binding: 1,
1906 resource: self.dvr_uniform_buffer.as_entire_binding(),
1907 },
1908 BindGroupEntry {
1909 binding: 2,
1910 resource: self.output_buffer.as_entire_binding(),
1911 },
1912 BindGroupEntry {
1913 binding: 3,
1914 resource: self.transfer_function_buffer.as_entire_binding(),
1915 },
1916 BindGroupEntry {
1917 binding: 4,
1918 resource: seg_mask_buffer.as_entire_binding(),
1919 },
1920 BindGroupEntry {
1921 binding: 5,
1922 resource: sculpt_mask_buffer.as_entire_binding(),
1923 },
1924 ],
1925 });
1926
1927 let pipeline = if mode == RenderMode::DVR {
1929 &self.dvr_pipeline
1930 } else {
1931 &self.dvr_no_shading_pipeline
1932 };
1933
1934 self.execute_and_readback(pipeline, &bind_group, mode, start)
1936 }
1937
1938 fn execute_and_readback(
1940 &self,
1941 pipeline: &ComputePipeline,
1942 bind_group: &wgpu::BindGroup,
1943 mode: RenderMode,
1944 start: std::time::Instant,
1945 ) -> Result<RenderResult, MprError> {
1946 let mut encoder = self
1948 .device
1949 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1950 label: Some("volume_render_encoder"),
1951 });
1952
1953 {
1954 let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
1955 label: Some("volume_render_pass"),
1956 timestamp_writes: None,
1957 });
1958
1959 compute_pass.set_pipeline(pipeline);
1960 compute_pass.set_bind_group(0, bind_group, &[]);
1961
1962 let workgroups_x = self.output_width.div_ceil(Self::WORKGROUP_SIZE);
1963 let workgroups_y = self.output_height.div_ceil(Self::WORKGROUP_SIZE);
1964
1965 compute_pass.dispatch_workgroups(workgroups_x, workgroups_y, 1);
1966 }
1967
1968 let output_size = (self.output_width * self.output_height * 4) as u64;
1970 encoder.copy_buffer_to_buffer(&self.output_buffer, 0, &self.staging_buffer, 0, output_size);
1971
1972 self.queue.submit(std::iter::once(encoder.finish()));
1973
1974 let buffer_slice = self.staging_buffer.slice(..);
1976 let (tx, rx) = std::sync::mpsc::channel();
1977 buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
1978 tx.send(result).unwrap();
1979 });
1980
1981 self.device.poll(wgpu::Maintain::Wait);
1982 rx.recv()
1983 .map_err(|_| MprError::GpuError("Channel receive failed".to_string()))?
1984 .map_err(|e| MprError::GpuError(format!("Buffer map failed: {:?}", e)))?;
1985
1986 let data = buffer_slice.get_mapped_range();
1987
1988 let rgba_data: Vec<u8> = data.to_vec();
1990
1991 drop(data);
1992 self.staging_buffer.unmap();
1993
1994 let elapsed_ms = start.elapsed().as_secs_f64() * 1000.0;
1995
1996 debug!(
1997 "{:?} render: {}x{} in {:.1}ms",
1998 mode, self.output_width, self.output_height, elapsed_ms
1999 );
2000
2001 Ok(RenderResult {
2002 width: self.output_width,
2003 height: self.output_height,
2004 rgba_data,
2005 render_time_ms: elapsed_ms,
2006 })
2007 }
2008
2009 pub fn resize(&mut self, width: u32, height: u32) -> Result<(), MprError> {
2011 if width == self.output_width && height == self.output_height {
2012 return Ok(());
2013 }
2014
2015 let output_size = (width * height * 4) as u64;
2016
2017 self.output_buffer = self.device.create_buffer(&BufferDescriptor {
2018 label: Some("volume_render_output"),
2019 size: output_size,
2020 usage: BufferUsages::STORAGE | BufferUsages::COPY_SRC,
2021 mapped_at_creation: false,
2022 });
2023
2024 self.staging_buffer = self.device.create_buffer(&BufferDescriptor {
2025 label: Some("volume_render_staging"),
2026 size: output_size,
2027 usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST,
2028 mapped_at_creation: false,
2029 });
2030
2031 self.output_width = width;
2032 self.output_height = height;
2033
2034 info!("Volume renderer resized to {}x{}", width, height);
2035 Ok(())
2036 }
2037}
2038
2039pub struct RenderResult {
2041 pub width: u32,
2042 pub height: u32,
2043 pub rgba_data: Vec<u8>,
2044 pub render_time_ms: f64,
2045}
2046
2047impl RenderResult {
2048 pub fn to_grayscale(&self) -> Vec<u8> {
2050 self.rgba_data
2051 .chunks_exact(4)
2052 .map(|chunk| chunk[0]) .collect()
2054 }
2055}
2056
2057#[cfg(test)]
2058mod tests {
2059 use super::*;
2060
2061 #[test]
2062 fn test_camera_orbit() {
2063 let center = [100.0, 100.0, 100.0];
2064 let camera = Camera::orbit(center, 200.0, 0.0, 0.0);
2065
2066 assert!(camera.position[2] > center[2]);
2068
2069 assert!(camera.look_dir[2] < 0.0);
2071 }
2072
2073 #[test]
2074 fn test_uniform_size() {
2075 assert_eq!(std::mem::size_of::<MipUniforms>() % 16, 0);
2077 }
2078}