1use super::*;
2use crate::resources::Vertex;
3
4impl DeviceResources {
5 pub(crate) fn ensure_image_slice_pipeline(&mut self, device: &wgpu::Device) {
13 if self.image_slice.pipeline.is_some() {
14 return;
15 }
16
17 let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
18 label: Some("image_slice_bgl"),
19 entries: &[
20 wgpu::BindGroupLayoutEntry {
22 binding: 0,
23 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
24 ty: wgpu::BindingType::Buffer {
25 ty: wgpu::BufferBindingType::Uniform,
26 has_dynamic_offset: false,
27 min_binding_size: None,
28 },
29 count: None,
30 },
31 wgpu::BindGroupLayoutEntry {
34 binding: 1,
35 visibility: wgpu::ShaderStages::FRAGMENT,
36 ty: wgpu::BindingType::Texture {
37 multisampled: false,
38 view_dimension: wgpu::TextureViewDimension::D3,
39 sample_type: wgpu::TextureSampleType::Float { filterable: false },
40 },
41 count: None,
42 },
43 wgpu::BindGroupLayoutEntry {
45 binding: 2,
46 visibility: wgpu::ShaderStages::FRAGMENT,
47 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
48 count: None,
49 },
50 wgpu::BindGroupLayoutEntry {
52 binding: 3,
53 visibility: wgpu::ShaderStages::FRAGMENT,
54 ty: wgpu::BindingType::Texture {
55 multisampled: false,
56 view_dimension: wgpu::TextureViewDimension::D2,
57 sample_type: wgpu::TextureSampleType::Float { filterable: true },
58 },
59 count: None,
60 },
61 wgpu::BindGroupLayoutEntry {
63 binding: 4,
64 visibility: wgpu::ShaderStages::FRAGMENT,
65 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
66 count: None,
67 },
68 ],
69 });
70
71 let shader = crate::resources::builders::wgsl_module(
72 device,
73 "image_slice_shader",
74 crate::resources::builders::wgsl_source!("image_slice"),
75 );
76
77 let layout = crate::resources::builders::standard_scene_layout(
78 device,
79 "image_slice_pipeline_layout",
80 &self.camera_bind_group_layout,
81 &bgl,
82 );
83
84 self.image_slice.bgl = Some(bgl);
85 self.image_slice.pipeline = Some(crate::resources::builders::build_dual_pipeline(
86 device,
87 &crate::resources::builders::DualPipelineDesc {
88 label: "image_slice_pipeline",
89 layout: &layout,
90 shader: &shader,
91 vertex_entry: "vs_main",
92 fragment_entry: "fs_main",
93 vertex_buffers: &[], blend: Some(wgpu::BlendState::ALPHA_BLENDING),
95 topology: wgpu::PrimitiveTopology::TriangleList,
96 cull_mode: None,
97 depth_write: false,
98 depth_compare: wgpu::CompareFunction::LessEqual,
99 sample_count: self.sample_count,
100 ldr_format: self.target_format,
101 },
102 ));
103 }
104
105 pub(crate) fn upload_image_slice(
111 &mut self,
112 device: &wgpu::Device,
113 queue: &wgpu::Queue,
114 item: &crate::renderer::ImageSliceItem,
115 ) -> Option<crate::resources::ImageSliceGpuData> {
116 if item.volume_id.0 >= self.content.volume_textures.len() {
118 return None;
119 }
120
121 #[repr(C)]
122 #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
123 struct ImageSliceUniform {
124 bbox_min: [f32; 3],
125 axis: u32,
126 bbox_max: [f32; 3],
127 offset: f32,
128 scalar_min: f32,
129 scalar_max: f32,
130 opacity: f32,
131 _pad: f32,
132 }
133
134 let axis_u32 = match item.axis {
135 crate::renderer::SliceAxis::X => 0u32,
136 crate::renderer::SliceAxis::Y => 1u32,
137 crate::renderer::SliceAxis::Z => 2u32,
138 };
139
140 let uniform_data = ImageSliceUniform {
141 bbox_min: item.bbox_min,
142 axis: axis_u32,
143 bbox_max: item.bbox_max,
144 offset: item.offset.clamp(0.0, 1.0),
145 scalar_min: item.scalar_range.0,
146 scalar_max: item.scalar_range.1,
147 opacity: item.opacity,
148 _pad: 0.0,
149 };
150
151 let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
152 label: Some("image_slice_uniform_buf"),
153 size: std::mem::size_of::<ImageSliceUniform>() as u64,
154 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
155 mapped_at_creation: false,
156 });
157 queue.write_buffer(&uniform_buf, 0, bytemuck::bytes_of(&uniform_data));
158
159 let vol_sampler =
161 crate::resources::builders::clamp_nearest_sampler(device, "image_slice_vol_sampler");
162
163 let lut_view_idx: Option<usize> = self.content.builtin_colourmap_ids.and_then(|ids| {
165 let preset_id = item
166 .colour_lut
167 .unwrap_or(ids[crate::resources::BuiltinColourmap::Viridis as usize]);
168 if preset_id.0 < self.content.colourmap_views.len() {
169 Some(preset_id.0)
170 } else {
171 None
172 }
173 });
174
175 let bgl = self
176 .image_slice
177 .bgl
178 .as_ref()
179 .expect("ensure_image_slice_pipeline not called");
180
181 let vol_view = &self
183 .content
184 .volume_textures
185 .get(item.volume_id.0)
186 .expect("volume existence checked above")
187 .1;
188 let lut_view = lut_view_idx
189 .map(|i| &self.content.colourmap_views[i])
190 .unwrap_or(&self.content.fallback_lut_view);
191
192 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
193 label: Some("image_slice_bg"),
194 layout: bgl,
195 entries: &[
196 wgpu::BindGroupEntry {
197 binding: 0,
198 resource: uniform_buf.as_entire_binding(),
199 },
200 wgpu::BindGroupEntry {
201 binding: 1,
202 resource: wgpu::BindingResource::TextureView(vol_view),
203 },
204 wgpu::BindGroupEntry {
205 binding: 2,
206 resource: wgpu::BindingResource::Sampler(&vol_sampler),
207 },
208 wgpu::BindGroupEntry {
209 binding: 3,
210 resource: wgpu::BindingResource::TextureView(lut_view),
211 },
212 wgpu::BindGroupEntry {
213 binding: 4,
214 resource: wgpu::BindingResource::Sampler(&self.material_sampler),
215 },
216 ],
217 });
218
219 Some(crate::resources::ImageSliceGpuData {
220 bind_group,
221 _uniform_buf: uniform_buf,
222 })
223 }
224
225 pub(crate) fn ensure_screen_image_pipeline(&mut self, device: &wgpu::Device) {
234 if self.screen_image.pipeline.is_some() {
235 return;
236 }
237
238 let shader = crate::resources::builders::wgsl_module(
239 device,
240 "screen_image_shader",
241 crate::resources::builders::wgsl_source!("screen_image"),
242 );
243
244 let bgl = crate::resources::builders::uniform_texture_sampler_bgl(
246 device,
247 "screen_image_bgl",
248 wgpu::ShaderStages::VERTEX_FRAGMENT,
249 wgpu::ShaderStages::FRAGMENT,
250 );
251
252 let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
253 label: Some("screen_image_layout"),
254 bind_group_layouts: &[&bgl],
255 push_constant_ranges: &[],
256 });
257
258 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
259 label: Some("screen_image_pipeline"),
260 layout: Some(&layout),
261 vertex: wgpu::VertexState {
262 module: &shader,
263 entry_point: Some("vs_main"),
264 buffers: &[],
265 compilation_options: wgpu::PipelineCompilationOptions::default(),
266 },
267 fragment: Some(wgpu::FragmentState {
268 module: &shader,
269 entry_point: Some("fs_main"),
270 targets: &[Some(wgpu::ColorTargetState {
271 format: self.target_format,
272 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
273 write_mask: wgpu::ColorWrites::ALL,
274 })],
275 compilation_options: wgpu::PipelineCompilationOptions::default(),
276 }),
277 primitive: wgpu::PrimitiveState {
278 topology: wgpu::PrimitiveTopology::TriangleList,
279 cull_mode: None,
280 ..Default::default()
281 },
282 depth_stencil: Some(wgpu::DepthStencilState {
285 format: wgpu::TextureFormat::Depth24PlusStencil8,
286 depth_write_enabled: false,
287 depth_compare: wgpu::CompareFunction::Always,
288 stencil: wgpu::StencilState::default(),
289 bias: wgpu::DepthBiasState::default(),
290 }),
291 multisample: wgpu::MultisampleState {
292 count: 1,
293 mask: !0,
294 alpha_to_coverage_enabled: false,
295 },
296 multiview: None,
297 cache: None,
298 });
299
300 self.screen_image.bgl = Some(bgl);
301 self.screen_image.pipeline = Some(pipeline);
302 }
303
304 pub(crate) fn ensure_screen_image_dc_pipeline(&mut self, device: &wgpu::Device) {
309 if self.screen_image.dc_pipeline.is_some() {
310 return;
311 }
312
313 let shader = crate::resources::builders::wgsl_module(
314 device,
315 "screen_image_dc_shader",
316 crate::resources::builders::wgsl_source!("screen_image_dc"),
317 );
318
319 let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
320 label: Some("screen_image_dc_bgl"),
321 entries: &[
322 wgpu::BindGroupLayoutEntry {
324 binding: 0,
325 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
326 ty: wgpu::BindingType::Buffer {
327 ty: wgpu::BufferBindingType::Uniform,
328 has_dynamic_offset: false,
329 min_binding_size: None,
330 },
331 count: None,
332 },
333 wgpu::BindGroupLayoutEntry {
335 binding: 1,
336 visibility: wgpu::ShaderStages::FRAGMENT,
337 ty: wgpu::BindingType::Texture {
338 multisampled: false,
339 view_dimension: wgpu::TextureViewDimension::D2,
340 sample_type: wgpu::TextureSampleType::Float { filterable: true },
341 },
342 count: None,
343 },
344 wgpu::BindGroupLayoutEntry {
346 binding: 2,
347 visibility: wgpu::ShaderStages::FRAGMENT,
348 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
349 count: None,
350 },
351 wgpu::BindGroupLayoutEntry {
353 binding: 3,
354 visibility: wgpu::ShaderStages::FRAGMENT,
355 ty: wgpu::BindingType::Texture {
356 multisampled: false,
357 view_dimension: wgpu::TextureViewDimension::D2,
358 sample_type: wgpu::TextureSampleType::Float { filterable: false },
359 },
360 count: None,
361 },
362 ],
363 });
364
365 let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
366 label: Some("screen_image_dc_layout"),
367 bind_group_layouts: &[&bgl],
368 push_constant_ranges: &[],
369 });
370
371 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
372 label: Some("screen_image_dc_pipeline"),
373 layout: Some(&layout),
374 vertex: wgpu::VertexState {
375 module: &shader,
376 entry_point: Some("vs_main"),
377 buffers: &[],
378 compilation_options: wgpu::PipelineCompilationOptions::default(),
379 },
380 fragment: Some(wgpu::FragmentState {
381 module: &shader,
382 entry_point: Some("fs_main"),
383 targets: &[Some(wgpu::ColorTargetState {
384 format: self.target_format,
385 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
386 write_mask: wgpu::ColorWrites::ALL,
387 })],
388 compilation_options: wgpu::PipelineCompilationOptions::default(),
389 }),
390 primitive: wgpu::PrimitiveState {
391 topology: wgpu::PrimitiveTopology::TriangleList,
392 cull_mode: None,
393 ..Default::default()
394 },
395 depth_stencil: Some(wgpu::DepthStencilState {
398 format: wgpu::TextureFormat::Depth24PlusStencil8,
399 depth_write_enabled: false,
400 depth_compare: wgpu::CompareFunction::LessEqual,
401 stencil: wgpu::StencilState::default(),
402 bias: wgpu::DepthBiasState::default(),
403 }),
404 multisample: wgpu::MultisampleState {
405 count: 1,
406 mask: !0,
407 alpha_to_coverage_enabled: false,
408 },
409 multiview: None,
410 cache: None,
411 });
412
413 self.screen_image.dc_bgl = Some(bgl);
414 self.screen_image.dc_pipeline = Some(pipeline);
415 }
416
417 pub(crate) fn upload_screen_image(
422 &self,
423 device: &wgpu::Device,
424 queue: &wgpu::Queue,
425 item: &crate::ScreenImageItem,
426 viewport_w: f32,
427 viewport_h: f32,
428 ) -> ScreenImageGpuData {
429 use crate::ImageAnchor;
430
431 let logical_area = (item.width * item.height) as usize;
437 let tex_scale = if logical_area > 0 {
438 let ratio = item.pixels.len() / logical_area;
439 (ratio as f32).sqrt().round() as u32
440 } else {
441 1
442 }
443 .max(1);
444 let tex_w = (item.width * tex_scale).max(1);
445 let tex_h = (item.height * tex_scale).max(1);
446
447 let texture = device.create_texture(&wgpu::TextureDescriptor {
449 label: Some("screen_image_tex"),
450 size: wgpu::Extent3d {
451 width: tex_w,
452 height: tex_h,
453 depth_or_array_layers: 1,
454 },
455 mip_level_count: 1,
456 sample_count: 1,
457 dimension: wgpu::TextureDimension::D2,
458 format: wgpu::TextureFormat::Rgba8UnormSrgb,
459 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
460 view_formats: &[],
461 });
462
463 if !item.pixels.is_empty() && item.width > 0 && item.height > 0 {
464 let raw: Vec<u8> = item.pixels.iter().flat_map(|p| p.iter().copied()).collect();
465 let needed = (tex_w as usize) * (tex_h as usize) * 4;
466 if raw.len() < needed {
467 tracing::warn!(
468 target: "viewport_lib::screen_image",
469 width = item.width,
470 height = item.height,
471 pixels_len = item.pixels.len(),
472 inferred_tex_w = tex_w,
473 inferred_tex_h = tex_h,
474 expected_bytes = needed,
475 actual_bytes = raw.len(),
476 "ScreenImageItem pixel buffer is smaller than the inferred texture size \
477 (item.width * item.height does not divide pixels.len() into a square scale \
478 factor). Skipping upload; the image will render blank. Resize the buffer or \
479 set item.width / item.height to match the buffer's actual physical resolution."
480 );
481 } else {
482 queue.write_texture(
483 wgpu::TexelCopyTextureInfo {
484 texture: &texture,
485 mip_level: 0,
486 origin: wgpu::Origin3d::ZERO,
487 aspect: wgpu::TextureAspect::All,
488 },
489 &raw,
490 wgpu::TexelCopyBufferLayout {
491 offset: 0,
492 bytes_per_row: Some(tex_w * 4),
493 rows_per_image: Some(tex_h),
494 },
495 wgpu::Extent3d {
496 width: tex_w,
497 height: tex_h,
498 depth_or_array_layers: 1,
499 },
500 );
501 }
502 }
503
504 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
505 let sampler =
506 crate::resources::builders::clamp_linear_sampler(device, "screen_image_sampler");
507
508 let img_w_ndc = 2.0 * item.width as f32 * item.scale / viewport_w.max(1.0);
510 let img_h_ndc = 2.0 * item.height as f32 * item.scale / viewport_h.max(1.0);
511
512 let (ndc_min_x, ndc_max_x, ndc_min_y, ndc_max_y) = match item.anchor {
513 ImageAnchor::TopLeft => (-1.0, -1.0 + img_w_ndc, 1.0 - img_h_ndc, 1.0),
514 ImageAnchor::TopRight => (1.0 - img_w_ndc, 1.0, 1.0 - img_h_ndc, 1.0),
515 ImageAnchor::BottomLeft => (-1.0, -1.0 + img_w_ndc, -1.0, -1.0 + img_h_ndc),
516 ImageAnchor::BottomRight => (1.0 - img_w_ndc, 1.0, -1.0, -1.0 + img_h_ndc),
517 _ => (
518 -img_w_ndc * 0.5,
519 img_w_ndc * 0.5,
520 -img_h_ndc * 0.5,
521 img_h_ndc * 0.5,
522 ),
523 };
524
525 #[repr(C)]
527 #[derive(bytemuck::Pod, bytemuck::Zeroable, Clone, Copy)]
528 struct ScreenImageUniform {
529 ndc_min: [f32; 2],
530 ndc_max: [f32; 2],
531 alpha: f32,
532 _pad: [f32; 3],
533 }
534
535 let uniform_data = ScreenImageUniform {
536 ndc_min: [ndc_min_x, ndc_min_y],
537 ndc_max: [ndc_max_x, ndc_max_y],
538 alpha: item.alpha,
539 _pad: [0.0; 3],
540 };
541
542 let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
543 label: Some("screen_image_uniform"),
544 size: std::mem::size_of::<ScreenImageUniform>() as u64,
545 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
546 mapped_at_creation: false,
547 });
548 queue.write_buffer(&uniform_buf, 0, bytemuck::bytes_of(&uniform_data));
549
550 let bgl = self
551 .screen_image
552 .bgl
553 .as_ref()
554 .expect("ensure_screen_image_pipeline not called");
555
556 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
557 label: Some("screen_image_bg"),
558 layout: bgl,
559 entries: &[
560 wgpu::BindGroupEntry {
561 binding: 0,
562 resource: uniform_buf.as_entire_binding(),
563 },
564 wgpu::BindGroupEntry {
565 binding: 1,
566 resource: wgpu::BindingResource::TextureView(&view),
567 },
568 wgpu::BindGroupEntry {
569 binding: 2,
570 resource: wgpu::BindingResource::Sampler(&sampler),
571 },
572 ],
573 });
574
575 let (depth_texture_opt, depth_bind_group_opt) = if let Some(depth_values) = &item.depth {
578 let dc_bgl =
579 self.screen_image.dc_bgl.as_ref().expect(
580 "ensure_screen_image_dc_pipeline not called before upload_screen_image",
581 );
582
583 let dtex = device.create_texture(&wgpu::TextureDescriptor {
584 label: Some("screen_image_depth_tex"),
585 size: wgpu::Extent3d {
586 width: item.width.max(1),
587 height: item.height.max(1),
588 depth_or_array_layers: 1,
589 },
590 mip_level_count: 1,
591 sample_count: 1,
592 dimension: wgpu::TextureDimension::D2,
593 format: wgpu::TextureFormat::R32Float,
594 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
595 view_formats: &[],
596 });
597
598 let pixel_count = (item.width * item.height) as usize;
600 let safe_depth: Vec<f32> = if depth_values.len() >= pixel_count {
601 depth_values[..pixel_count].to_vec()
602 } else {
603 let mut v = depth_values.clone();
605 v.resize(pixel_count, 1.0);
606 v
607 };
608
609 if item.width > 0 && item.height > 0 {
610 queue.write_texture(
611 wgpu::TexelCopyTextureInfo {
612 texture: &dtex,
613 mip_level: 0,
614 origin: wgpu::Origin3d::ZERO,
615 aspect: wgpu::TextureAspect::All,
616 },
617 bytemuck::cast_slice(&safe_depth),
618 wgpu::TexelCopyBufferLayout {
619 offset: 0,
620 bytes_per_row: Some(item.width * 4),
621 rows_per_image: Some(item.height),
622 },
623 wgpu::Extent3d {
624 width: item.width,
625 height: item.height,
626 depth_or_array_layers: 1,
627 },
628 );
629 }
630
631 let dview = dtex.create_view(&wgpu::TextureViewDescriptor::default());
632
633 let dc_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
634 label: Some("screen_image_dc_bg"),
635 layout: dc_bgl,
636 entries: &[
637 wgpu::BindGroupEntry {
638 binding: 0,
639 resource: uniform_buf.as_entire_binding(),
640 },
641 wgpu::BindGroupEntry {
642 binding: 1,
643 resource: wgpu::BindingResource::TextureView(&view),
644 },
645 wgpu::BindGroupEntry {
646 binding: 2,
647 resource: wgpu::BindingResource::Sampler(&sampler),
648 },
649 wgpu::BindGroupEntry {
650 binding: 3,
651 resource: wgpu::BindingResource::TextureView(&dview),
652 },
653 ],
654 });
655
656 (Some(dtex), Some(dc_bg))
657 } else {
658 (None, None)
659 };
660
661 ScreenImageGpuData {
662 _uniform_buf: uniform_buf,
663 _texture: texture,
664 bind_group,
665 _depth_texture: depth_texture_opt,
666 depth_bind_group: depth_bind_group_opt,
667 }
668 }
669
670 pub(crate) fn upload_overlay_image(
677 &self,
678 device: &wgpu::Device,
679 queue: &wgpu::Queue,
680 item: &crate::OverlayImageItem,
681 viewport_w: f32,
682 viewport_h: f32,
683 ) -> ScreenImageGpuData {
684 use crate::ImageAnchor;
685
686 let logical_area = (item.width * item.height) as usize;
687 let tex_scale = if logical_area > 0 {
688 let ratio = item.pixels.len() / logical_area;
689 (ratio as f32).sqrt().round() as u32
690 } else {
691 1
692 }
693 .max(1);
694 let tex_w = (item.width * tex_scale).max(1);
695 let tex_h = (item.height * tex_scale).max(1);
696
697 let texture = device.create_texture(&wgpu::TextureDescriptor {
698 label: Some("overlay_image_tex"),
699 size: wgpu::Extent3d {
700 width: tex_w,
701 height: tex_h,
702 depth_or_array_layers: 1,
703 },
704 mip_level_count: 1,
705 sample_count: 1,
706 dimension: wgpu::TextureDimension::D2,
707 format: wgpu::TextureFormat::Rgba8UnormSrgb,
708 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
709 view_formats: &[],
710 });
711
712 if !item.pixels.is_empty() && item.width > 0 && item.height > 0 {
713 let raw: Vec<u8> = item.pixels.iter().flat_map(|p| p.iter().copied()).collect();
714 let needed = (tex_w as usize) * (tex_h as usize) * 4;
715 if raw.len() < needed {
716 tracing::warn!(
717 target: "viewport_lib::overlay_image",
718 width = item.width,
719 height = item.height,
720 pixels_len = item.pixels.len(),
721 inferred_tex_w = tex_w,
722 inferred_tex_h = tex_h,
723 expected_bytes = needed,
724 actual_bytes = raw.len(),
725 "OverlayImageItem pixel buffer is smaller than the inferred texture size \
726 (item.width * item.height does not divide pixels.len() into a square scale \
727 factor). Skipping upload; the image will render blank. Resize the buffer or \
728 set item.width / item.height to match the buffer's actual physical resolution."
729 );
730 } else {
731 queue.write_texture(
732 wgpu::TexelCopyTextureInfo {
733 texture: &texture,
734 mip_level: 0,
735 origin: wgpu::Origin3d::ZERO,
736 aspect: wgpu::TextureAspect::All,
737 },
738 &raw,
739 wgpu::TexelCopyBufferLayout {
740 offset: 0,
741 bytes_per_row: Some(tex_w * 4),
742 rows_per_image: Some(tex_h),
743 },
744 wgpu::Extent3d {
745 width: tex_w,
746 height: tex_h,
747 depth_or_array_layers: 1,
748 },
749 );
750 }
751 }
752
753 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
754 let sampler =
755 crate::resources::builders::clamp_linear_sampler(device, "overlay_image_sampler");
756
757 let img_w_ndc = 2.0 * item.width as f32 * item.scale / viewport_w.max(1.0);
758 let img_h_ndc = 2.0 * item.height as f32 * item.scale / viewport_h.max(1.0);
759
760 let (ndc_min_x, ndc_max_x, ndc_min_y, ndc_max_y) = match item.anchor {
761 ImageAnchor::TopLeft => (-1.0, -1.0 + img_w_ndc, 1.0 - img_h_ndc, 1.0),
762 ImageAnchor::TopRight => (1.0 - img_w_ndc, 1.0, 1.0 - img_h_ndc, 1.0),
763 ImageAnchor::BottomLeft => (-1.0, -1.0 + img_w_ndc, -1.0, -1.0 + img_h_ndc),
764 ImageAnchor::BottomRight => (1.0 - img_w_ndc, 1.0, -1.0, -1.0 + img_h_ndc),
765 _ => (
766 -img_w_ndc * 0.5,
767 img_w_ndc * 0.5,
768 -img_h_ndc * 0.5,
769 img_h_ndc * 0.5,
770 ),
771 };
772
773 #[repr(C)]
774 #[derive(bytemuck::Pod, bytemuck::Zeroable, Clone, Copy)]
775 struct ScreenImageUniform {
776 ndc_min: [f32; 2],
777 ndc_max: [f32; 2],
778 alpha: f32,
779 _pad: [f32; 3],
780 }
781
782 let uniform_data = ScreenImageUniform {
783 ndc_min: [ndc_min_x, ndc_min_y],
784 ndc_max: [ndc_max_x, ndc_max_y],
785 alpha: item.alpha,
786 _pad: [0.0; 3],
787 };
788
789 let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
790 label: Some("overlay_image_uniform"),
791 size: std::mem::size_of::<ScreenImageUniform>() as u64,
792 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
793 mapped_at_creation: false,
794 });
795 queue.write_buffer(&uniform_buf, 0, bytemuck::bytes_of(&uniform_data));
796
797 let bgl = self
798 .screen_image
799 .bgl
800 .as_ref()
801 .expect("ensure_screen_image_pipeline not called before upload_overlay_image");
802
803 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
804 label: Some("overlay_image_bg"),
805 layout: bgl,
806 entries: &[
807 wgpu::BindGroupEntry {
808 binding: 0,
809 resource: uniform_buf.as_entire_binding(),
810 },
811 wgpu::BindGroupEntry {
812 binding: 1,
813 resource: wgpu::BindingResource::TextureView(&view),
814 },
815 wgpu::BindGroupEntry {
816 binding: 2,
817 resource: wgpu::BindingResource::Sampler(&sampler),
818 },
819 ],
820 });
821
822 ScreenImageGpuData {
823 _uniform_buf: uniform_buf,
824 _texture: texture,
825 bind_group,
826 _depth_texture: None,
827 depth_bind_group: None,
828 }
829 }
830
831 pub(crate) fn ensure_volume_surface_slice_pipeline(&mut self, device: &wgpu::Device) {
840 if self.volume.surface_slice_pipeline.is_some() {
841 return;
842 }
843
844 let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
845 label: Some("volume_surface_slice_bgl"),
846 entries: &[
847 wgpu::BindGroupLayoutEntry {
849 binding: 0,
850 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
851 ty: wgpu::BindingType::Buffer {
852 ty: wgpu::BufferBindingType::Uniform,
853 has_dynamic_offset: false,
854 min_binding_size: None,
855 },
856 count: None,
857 },
858 wgpu::BindGroupLayoutEntry {
860 binding: 1,
861 visibility: wgpu::ShaderStages::FRAGMENT,
862 ty: wgpu::BindingType::Texture {
863 multisampled: false,
864 view_dimension: wgpu::TextureViewDimension::D3,
865 sample_type: wgpu::TextureSampleType::Float { filterable: false },
866 },
867 count: None,
868 },
869 wgpu::BindGroupLayoutEntry {
871 binding: 2,
872 visibility: wgpu::ShaderStages::FRAGMENT,
873 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
874 count: None,
875 },
876 wgpu::BindGroupLayoutEntry {
878 binding: 3,
879 visibility: wgpu::ShaderStages::FRAGMENT,
880 ty: wgpu::BindingType::Texture {
881 multisampled: false,
882 view_dimension: wgpu::TextureViewDimension::D2,
883 sample_type: wgpu::TextureSampleType::Float { filterable: true },
884 },
885 count: None,
886 },
887 wgpu::BindGroupLayoutEntry {
889 binding: 4,
890 visibility: wgpu::ShaderStages::FRAGMENT,
891 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
892 count: None,
893 },
894 ],
895 });
896
897 let shader = crate::resources::builders::wgsl_module(
898 device,
899 "volume_surface_slice_shader",
900 crate::resources::builders::wgsl_source!("volume_surface_slice"),
901 );
902
903 let layout = crate::resources::builders::standard_scene_layout(
904 device,
905 "volume_surface_slice_layout",
906 &self.camera_bind_group_layout,
907 &bgl,
908 );
909
910 self.volume.surface_slice_bgl = Some(bgl);
911 self.volume.surface_slice_pipeline = Some(crate::resources::builders::build_dual_pipeline(
912 device,
913 &crate::resources::builders::DualPipelineDesc {
914 label: "volume_surface_slice_pipeline",
915 layout: &layout,
916 shader: &shader,
917 vertex_entry: "vs_main",
918 fragment_entry: "fs_main",
919 vertex_buffers: &[Vertex::buffer_layout()],
920 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
921 topology: wgpu::PrimitiveTopology::TriangleList,
922 cull_mode: None,
923 depth_write: true,
924 depth_compare: wgpu::CompareFunction::LessEqual,
925 sample_count: self.sample_count,
926 ldr_format: self.target_format,
927 },
928 ));
929 }
930
931 pub(crate) fn upload_volume_surface_slice(
937 &mut self,
938 device: &wgpu::Device,
939 queue: &wgpu::Queue,
940 item: &crate::renderer::VolumeSurfaceSliceItem,
941 ) -> Option<crate::resources::VolumeSurfaceSliceGpuData> {
942 if item.volume_id.0 >= self.content.volume_textures.len() {
943 return None;
944 }
945 if self.mesh_store.get(item.mesh_id).is_none() {
947 return None;
948 }
949
950 #[repr(C)]
951 #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
952 struct VolumeSurfaceSliceUniform {
953 model: [[f32; 4]; 4],
954 bbox_min: [f32; 3],
955 scalar_min: f32,
956 bbox_max: [f32; 3],
957 scalar_max: f32,
958 opacity: f32,
959 _pad: [f32; 3],
960 }
961
962 let uniform_data = VolumeSurfaceSliceUniform {
963 model: item.model,
964 bbox_min: item.bbox_min,
965 scalar_min: item.scalar_range.0,
966 bbox_max: item.bbox_max,
967 scalar_max: item.scalar_range.1,
968 opacity: item.opacity * item.settings.opacity,
972 _pad: [0.0; 3],
973 };
974
975 let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
976 label: Some("volume_surface_slice_uniform"),
977 size: std::mem::size_of::<VolumeSurfaceSliceUniform>() as u64,
978 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
979 mapped_at_creation: false,
980 });
981 queue.write_buffer(&uniform_buf, 0, bytemuck::bytes_of(&uniform_data));
982
983 let vol_sampler = crate::resources::builders::clamp_nearest_sampler(
984 device,
985 "volume_surface_slice_vol_sampler",
986 );
987
988 let lut_view_idx: Option<usize> = self.content.builtin_colourmap_ids.and_then(|ids| {
989 let preset_id = item
990 .colour_lut
991 .unwrap_or(ids[crate::resources::BuiltinColourmap::Viridis as usize]);
992 if preset_id.0 < self.content.colourmap_views.len() {
993 Some(preset_id.0)
994 } else {
995 None
996 }
997 });
998
999 let bgl = self
1000 .volume
1001 .surface_slice_bgl
1002 .as_ref()
1003 .expect("ensure_volume_surface_slice_pipeline not called");
1004
1005 let vol_view = &self
1006 .content
1007 .volume_textures
1008 .get(item.volume_id.0)
1009 .expect("volume existence checked above")
1010 .1;
1011 let lut_view = lut_view_idx
1012 .map(|i| &self.content.colourmap_views[i])
1013 .unwrap_or(&self.content.fallback_lut_view);
1014
1015 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1016 label: Some("volume_surface_slice_bg"),
1017 layout: bgl,
1018 entries: &[
1019 wgpu::BindGroupEntry {
1020 binding: 0,
1021 resource: uniform_buf.as_entire_binding(),
1022 },
1023 wgpu::BindGroupEntry {
1024 binding: 1,
1025 resource: wgpu::BindingResource::TextureView(vol_view),
1026 },
1027 wgpu::BindGroupEntry {
1028 binding: 2,
1029 resource: wgpu::BindingResource::Sampler(&vol_sampler),
1030 },
1031 wgpu::BindGroupEntry {
1032 binding: 3,
1033 resource: wgpu::BindingResource::TextureView(lut_view),
1034 },
1035 wgpu::BindGroupEntry {
1036 binding: 4,
1037 resource: wgpu::BindingResource::Sampler(&self.material_sampler),
1038 },
1039 ],
1040 });
1041
1042 Some(crate::resources::VolumeSurfaceSliceGpuData {
1043 bind_group,
1044 _uniform_buf: uniform_buf,
1045 mesh_id: item.mesh_id,
1046 })
1047 }
1048
1049 pub(crate) fn ensure_screen_rect_outline_mask_pipeline(&mut self, device: &wgpu::Device) {
1055 if self.screen_image.rect_outline_mask_pipeline.is_some() {
1056 return;
1057 }
1058
1059 let bgl = crate::resources::builders::uniform_bgl(
1060 device,
1061 "screen_rect_outline_bgl",
1062 wgpu::ShaderStages::VERTEX,
1063 );
1064
1065 let shader = crate::resources::builders::wgsl_module(
1066 device,
1067 "screen_rect_outline_mask_shader",
1068 crate::resources::builders::wgsl_source!("outline_mask_ndc"),
1069 );
1070
1071 let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1072 label: Some("screen_rect_outline_mask_pipeline_layout"),
1073 bind_group_layouts: &[&bgl],
1074 push_constant_ranges: &[],
1075 });
1076
1077 let pipeline = crate::resources::builders::build_outline_mask_pipeline(
1078 device,
1079 "screen_rect_outline_mask_pipeline",
1080 &layout,
1081 &shader,
1082 wgpu::TextureFormat::R8Unorm,
1083 &[],
1084 None,
1085 false,
1086 wgpu::CompareFunction::Always,
1087 );
1088
1089 self.screen_image.rect_outline_bgl = Some(bgl);
1090 self.screen_image.rect_outline_mask_pipeline = Some(pipeline);
1091 }
1092}
1093
1094pub struct ScreenImageGpuData {
1096 pub(crate) _uniform_buf: wgpu::Buffer,
1098 pub(crate) _texture: wgpu::Texture,
1100 pub(crate) bind_group: wgpu::BindGroup,
1103 pub(crate) _depth_texture: Option<wgpu::Texture>,
1105 pub(crate) depth_bind_group: Option<wgpu::BindGroup>,
1108}