1use crate::{
106 Gpu,
107 buf::{
108 ArrayBuffer, BufferItemCount, BufferSizeError, BufferType, ImageBuffer,
109 buffer_ro, buffer_ro_dyn, buffer_rw,
110 },
111 opcode_constants, tag,
112};
113use fidget_bytecode::{Bytecode, ReservedRegister};
114use fidget_core::{
115 eval::Function,
116 render::{ImageSize, VoxelSize},
117 shape::{MissingVar, ShapeVars},
118 var::Var,
119 vm::VmShape,
120};
121use fidget_raster::voxel::{GeometryPixel, Image};
122use std::{collections::BTreeMap, num::NonZeroU64};
123use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
124
125const COMMON_SHADER: &str = include_str!("shaders/common.wgsl");
126const VOXEL_TILES_SHADER: &str = include_str!("shaders/voxel_tiles.wgsl");
127const STACK_SHADER: &str = include_str!("shaders/stack.wgsl");
128const DUMMY_STACK_SHADER: &str = include_str!("shaders/dummy_stack.wgsl");
129const INTERVAL_TILES_SHADER: &str = include_str!("shaders/interval_tiles.wgsl");
130const REPACK_SHADER: &str = include_str!("shaders/repack.wgsl");
131const SORT_SHADER: &str = include_str!("shaders/sort.wgsl");
132const INTERVAL_ROOT_SHADER: &str = include_str!("shaders/interval_root.wgsl");
133const INTERVAL_OPS_SHADER: &str = include_str!("shaders/interval_ops.wgsl");
134const CLEAR_SHADER: &str = include_str!("shaders/clear.wgsl");
135const MERGE_SHADER: &str = include_str!("shaders/merge.wgsl");
136const NORMALS_SHADER: &str = include_str!("shaders/normals.wgsl");
137const TAPE_INTERPRETER: &str = include_str!("shaders/tape_interpreter.wgsl");
138const TAPE_SIMPLIFY: &str = include_str!("shaders/tape_simplify.wgsl");
139
140#[derive(Debug, thiserror::Error)]
142#[error("failed to resize `{buf}` tile buffer")]
143pub struct TileBuffersError {
144 pub buf: TileBufferName,
146 #[source]
148 pub err: BufferSizeError,
149}
150
151#[derive(Debug)]
155#[expect(missing_docs)]
156pub enum TileBufferName {
157 Tiles,
158 Sorted,
159 Zmin,
160}
161
162impl std::fmt::Display for TileBufferName {
163 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164 let s = match self {
165 TileBufferName::Tiles => "tiles",
166 TileBufferName::Sorted => "sorted",
167 TileBufferName::Zmin => "zmin",
168 };
169 s.fmt(f)
170 }
171}
172
173#[derive(Debug, thiserror::Error)]
175#[error("failed to resize `{buf}` root tile buffer")]
176pub struct RootTileBuffersError {
177 pub buf: RootTileBufferName,
179 #[source]
181 pub err: BufferSizeError,
182}
183
184#[derive(Debug)]
186#[expect(missing_docs)]
187pub enum RootTileBufferName {
188 Tiles,
189 Strata,
190 Zmin,
191 Zmax,
192}
193
194impl std::fmt::Display for RootTileBufferName {
195 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196 let s = match self {
197 RootTileBufferName::Tiles => "tiles",
198 RootTileBufferName::Strata => "strata",
199 RootTileBufferName::Zmin => "zmin",
200 RootTileBufferName::Zmax => "zmax",
201 };
202 s.fmt(f)
203 }
204}
205
206#[derive(Debug)]
208#[expect(missing_docs)]
209pub enum BufferName {
210 Tile64(RootTileBufferName),
212 Tile16(TileBufferName),
214 Tile4(TileBufferName),
216 TileTapes,
217 Voxels,
218 Heightmap,
219 Geom,
220 Image,
221}
222
223impl std::fmt::Display for BufferName {
224 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225 match self {
226 BufferName::Tile64(buf) => write!(f, "`{buf}` tile64"),
227 BufferName::Tile16(buf) => write!(f, "`{buf}` tile16"),
228 BufferName::Tile4(buf) => write!(f, "`{buf}` tile4"),
229 BufferName::TileTapes => write!(f, "`tile tapes`"),
230 BufferName::Voxels => write!(f, "`voxels`"),
231 BufferName::Heightmap => write!(f, "`heightmap`"),
232 BufferName::Geom => write!(f, "`geom`"),
233 BufferName::Image => write!(f, "`image`"),
234 }
235 }
236}
237
238#[derive(Debug, thiserror::Error)]
240#[error("failed to build {buf} buffer when requesting size {requested:?}")]
241pub struct BuffersError {
242 pub requested: VoxelSize,
244 pub buf: BufferName,
246 pub err: BufferSizeError,
248}
249
250#[derive(Copy, Clone)]
258pub struct RenderConfig {
259 pub world_to_model: nalgebra::Matrix4<f32>,
261}
262
263impl Default for RenderConfig {
264 fn default() -> Self {
265 Self {
266 world_to_model: nalgebra::Matrix4::identity(),
267 }
268 }
269}
270
271#[derive(Debug, IntoBytes, Immutable, FromBytes, KnownLayout)]
278#[repr(C)]
279struct Config {
280 mat: [f32; 16],
282
283 axes: [u32; 3],
287
288 tape_data_offset: u32,
290
291 render_size: [u32; 3],
293
294 tape_data_capacity: u32,
296
297 image_size: [u32; 3],
299
300 root_tape_len: u32,
302 }
304
305#[derive(Copy, Clone, Debug)]
310struct TileRenderSize(VoxelSize);
311
312impl From<VoxelSize> for TileRenderSize {
313 fn from(image_size: VoxelSize) -> Self {
314 let nx = image_size.width().div_ceil(64);
315 let ny = image_size.height().div_ceil(64);
316 let nz = image_size.depth().div_ceil(64);
317 Self(VoxelSize::new(nx, ny, nz))
318 }
319}
320
321impl TileRenderSize {
322 fn nx(&self) -> u32 {
324 self.0.width()
325 }
326
327 fn ny(&self) -> u32 {
329 self.0.height()
330 }
331
332 fn nz(&self) -> u32 {
334 self.0.depth()
335 }
336
337 fn width(&self) -> u32 {
339 self.0.width() * 64
340 }
341
342 fn height(&self) -> u32 {
344 self.0.height() * 64
345 }
346
347 fn depth(&self) -> u32 {
349 self.0.depth() * 64
350 }
351
352 fn pixels(&self) -> usize {
354 self.width() as usize * self.height() as usize
355 }
356}
357
358const TAPE_DATA_CAPACITY: usize = 8 * 1024 * 1024; #[repr(C)]
362struct TapeWord {
363 op: u32,
364 imm: u32,
365}
366
367fn interval_root_shader(reg_count: u8) -> String {
369 let mut shader_code = opcode_constants();
370 shader_code += &format!("const REG_COUNT: u32 = {reg_count};");
371 shader_code += INTERVAL_ROOT_SHADER;
372 shader_code += INTERVAL_OPS_SHADER;
373 shader_code += COMMON_SHADER;
374 shader_code += crate::COMMON_SHADER;
375 shader_code += TAPE_INTERPRETER;
376 shader_code += STACK_SHADER;
377 shader_code += TAPE_SIMPLIFY;
378 shader_code
379}
380
381fn repack_shader() -> String {
383 let mut shader_code = String::new();
384 shader_code += REPACK_SHADER;
385 shader_code += COMMON_SHADER;
386 shader_code += crate::COMMON_SHADER;
387 shader_code
388}
389
390fn sort_shader() -> String {
392 let mut shader_code = String::new();
393 shader_code += SORT_SHADER;
394 shader_code += COMMON_SHADER;
395 shader_code += crate::COMMON_SHADER;
396 shader_code
397}
398
399fn interval_tiles_shader(reg_count: u8) -> String {
401 let mut shader_code = opcode_constants();
402 shader_code += &format!("const REG_COUNT: u32 = {reg_count};");
403 shader_code += INTERVAL_TILES_SHADER;
404 shader_code += INTERVAL_OPS_SHADER;
405 shader_code += COMMON_SHADER;
406 shader_code += crate::COMMON_SHADER;
407 shader_code += TAPE_INTERPRETER;
408 shader_code += STACK_SHADER;
409 shader_code += TAPE_SIMPLIFY;
410 shader_code
411}
412
413fn voxel_tiles_shader(reg_count: u8) -> String {
415 let mut shader_code = opcode_constants();
416 shader_code += &format!("const REG_COUNT: u32 = {reg_count};");
417 shader_code += VOXEL_TILES_SHADER;
418 shader_code += COMMON_SHADER;
419 shader_code += crate::COMMON_SHADER;
420 shader_code += TAPE_INTERPRETER;
421 shader_code += DUMMY_STACK_SHADER;
422 shader_code
423}
424
425fn normals_shader(reg_count: u8) -> String {
427 let mut shader_code = opcode_constants();
428 shader_code += &format!("const REG_COUNT: u32 = {reg_count};");
429 shader_code += NORMALS_SHADER;
430 shader_code += COMMON_SHADER;
431 shader_code += crate::COMMON_SHADER;
432 shader_code += TAPE_INTERPRETER;
433 shader_code += DUMMY_STACK_SHADER;
434 shader_code
435}
436
437fn merge_shader() -> String {
439 MERGE_SHADER.to_owned() + COMMON_SHADER + crate::COMMON_SHADER
440}
441
442fn clear_shader() -> String {
444 CLEAR_SHADER.to_owned() + COMMON_SHADER + crate::COMMON_SHADER
445}
446
447struct RegPipeline(BTreeMap<u8, wgpu::ComputePipeline>);
451
452impl RegPipeline {
453 fn build<F: Fn(u8) -> wgpu::ComputePipeline>(builder: F) -> Self {
454 let mut out = BTreeMap::new();
455 for reg_count in [8, 16, 32, 64, 128, 192, 255] {
456 out.insert(reg_count, builder(reg_count));
457 }
458 Self(out)
459 }
460
461 fn get(&self, reg_count: u8) -> &wgpu::ComputePipeline {
466 let (r, v) = self
467 .0
468 .range(reg_count..)
469 .next()
470 .expect("bytecode tape cannot use more than 255 registers");
471 assert!(*r >= reg_count);
472 v
473 }
474}
475
476struct RootContext {
478 root_pipeline: RegPipeline,
480
481 bind_group_layout: wgpu::BindGroupLayout,
483}
484
485fn strata_size_bytes(render_size: TileRenderSize) -> usize {
489 let nx = usize::try_from(render_size.nx()).unwrap();
490 let ny = usize::try_from(render_size.ny()).unwrap();
491 ((nx * ny + 4) * std::mem::size_of::<u32>()).next_multiple_of(256)
493}
494
495impl RootContext {
496 fn new(
497 device: &wgpu::Device,
498 common_bind_group_layout: &wgpu::BindGroupLayout,
499 vars_bind_group_layout: &wgpu::BindGroupLayout,
500 ) -> Self {
501 let bind_group_layout =
503 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
504 label: None,
505 entries: &[
506 buffer_rw(0), buffer_rw(1), ],
509 });
510
511 let root_pipeline = RegPipeline::build(|reg_count| {
512 let shader_code = interval_root_shader(reg_count);
513 let pipeline_layout = device.create_pipeline_layout(
514 &wgpu::PipelineLayoutDescriptor {
515 label: None,
516 bind_group_layouts: &[
517 Some(common_bind_group_layout),
518 Some(vars_bind_group_layout),
519 Some(&bind_group_layout),
520 ],
521 immediate_size: 0u32,
522 },
523 );
524 let shader_module =
525 device.create_shader_module(wgpu::ShaderModuleDescriptor {
526 label: None,
527 source: wgpu::ShaderSource::Wgsl(shader_code.into()),
528 });
529 device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
530 label: Some(&format!("interval root ({reg_count})")),
531 layout: Some(&pipeline_layout),
532 module: &shader_module,
533 entry_point: Some("interval_root_main"),
534 compilation_options: Default::default(),
535 cache: None,
536 })
537 });
538
539 Self {
540 bind_group_layout,
541 root_pipeline,
542 }
543 }
544
545 fn run(
546 &self,
547 ctx: &Context,
548 buffers: &Buffers,
549 reg_count: u8,
550 render_size: TileRenderSize,
551 compute_pass: &mut wgpu::ComputePass,
552 ) {
553 let bind_group = buffers.bind_groups.root(ctx, buffers);
554 compute_pass.set_pipeline(self.root_pipeline.get(reg_count));
555 compute_pass.set_bind_group(2, bind_group, &[]);
556
557 let nx = render_size.nx().div_ceil(4);
559 let ny = render_size.ny().div_ceil(4);
560 let nz = render_size.nz().div_ceil(4);
561 compute_pass.dispatch_workgroups(nx, ny, nz);
562 }
563}
564
565struct RepackContext {
567 repack_pipeline: wgpu::ComputePipeline,
569
570 bind_group_layout: wgpu::BindGroupLayout,
572}
573
574impl RepackContext {
575 fn new(
576 device: &wgpu::Device,
577 common_bind_group_layout: &wgpu::BindGroupLayout,
578 vars_bind_group_layout: &wgpu::BindGroupLayout,
579 ) -> Self {
580 let bind_group_layout =
582 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
583 label: None,
584 entries: &[
585 buffer_ro(0), buffer_ro(1), buffer_rw(2), ],
589 });
590
591 let repack_pipeline = {
592 let shader_code = repack_shader();
593 let pipeline_layout = device.create_pipeline_layout(
594 &wgpu::PipelineLayoutDescriptor {
595 label: None,
596 bind_group_layouts: &[
597 Some(common_bind_group_layout),
598 Some(vars_bind_group_layout),
599 Some(&bind_group_layout),
600 ],
601 immediate_size: 0u32,
602 },
603 );
604 let shader_module =
605 device.create_shader_module(wgpu::ShaderModuleDescriptor {
606 label: None,
607 source: wgpu::ShaderSource::Wgsl(shader_code.into()),
608 });
609 device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
610 label: Some("repack"),
611 layout: Some(&pipeline_layout),
612 module: &shader_module,
613 entry_point: Some("repack_main"),
614 compilation_options: Default::default(),
615 cache: None,
616 })
617 };
618
619 Self {
620 bind_group_layout,
621 repack_pipeline,
622 }
623 }
624
625 fn run(
626 &self,
627 ctx: &Context,
628 buffers: &Buffers,
629 render_size: TileRenderSize,
630 compute_pass: &mut wgpu::ComputePass,
631 ) {
632 let bind_group = buffers.bind_groups.repack(ctx, buffers);
633
634 compute_pass.set_pipeline(&self.repack_pipeline);
635 compute_pass.set_bind_group(2, bind_group, &[]);
636
637 let nx = render_size.nx().div_ceil(64);
642 let ny = render_size.ny();
643 let nz = render_size.nz();
644 compute_pass.dispatch_workgroups(nx, ny, nz);
645 }
646}
647
648struct IntervalContext {
651 interval64_pipeline: RegPipeline,
653
654 sort16_pipeline: wgpu::ComputePipeline,
656
657 interval16_pipeline: RegPipeline,
659
660 sort4_pipeline: wgpu::ComputePipeline,
662
663 interval_bind_group_layout: wgpu::BindGroupLayout,
665
666 sort_bind_group_layout: wgpu::BindGroupLayout,
668}
669
670impl IntervalContext {
671 fn new(
672 device: &wgpu::Device,
673 common_bind_group_layout: &wgpu::BindGroupLayout,
674 vars_bind_group_layout: &wgpu::BindGroupLayout,
675 ) -> Self {
676 let interval_bind_group_layout =
678 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
679 label: None,
680 entries: &[
681 buffer_ro_dyn(0), buffer_ro(1), buffer_rw(2), buffer_rw(3), buffer_rw(4), ],
687 });
688
689 let interval_pipeline_layout =
690 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
691 label: Some("interval pipeline layout"),
692 bind_group_layouts: &[
693 Some(common_bind_group_layout),
694 Some(vars_bind_group_layout),
695 Some(&interval_bind_group_layout),
696 ],
697 immediate_size: 0u32,
698 });
699
700 let interval64_pipeline = RegPipeline::build(|reg_count| {
701 let shader_code = interval_tiles_shader(reg_count);
702 let shader_module = unsafe {
704 device.create_shader_module_trusted(
705 wgpu::ShaderModuleDescriptor {
706 label: Some(&format!(
707 "interval64 tiles shader ({reg_count})"
708 )),
709 source: wgpu::ShaderSource::Wgsl(shader_code.into()),
710 },
711 wgpu::ShaderRuntimeChecks {
712 bounds_checks: false,
713 force_loop_bounding: false,
714 ray_query_initialization_tracking: false,
715 task_shader_dispatch_tracking: false,
716 mesh_shader_primitive_indices_clamp: false,
717 },
718 )
719 };
720 device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
721 label: Some(&format!("interval64 ({reg_count})")),
722 layout: Some(&interval_pipeline_layout),
723 module: &shader_module,
724 entry_point: Some("interval_tile_main"),
725 compilation_options: wgpu::PipelineCompilationOptions {
726 constants: &[("TILE_SIZE", 64.0), ("SUBTILE_SIZE", 16.0)],
727 ..Default::default()
728 },
729 cache: None,
730 })
731 });
732
733 let interval16_pipeline = RegPipeline::build(|reg_count| {
734 let shader_code = interval_tiles_shader(reg_count);
735 let shader_module = unsafe {
737 device.create_shader_module_trusted(
738 wgpu::ShaderModuleDescriptor {
739 label: Some(&format!(
740 "interval16 tiles shader ({reg_count})"
741 )),
742 source: wgpu::ShaderSource::Wgsl(shader_code.into()),
743 },
744 wgpu::ShaderRuntimeChecks {
745 bounds_checks: false,
746 force_loop_bounding: false,
747 ray_query_initialization_tracking: false,
748 task_shader_dispatch_tracking: false,
749 mesh_shader_primitive_indices_clamp: false,
750 },
751 )
752 };
753 device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
754 label: Some(&format!("interval16 ({reg_count})")),
755 layout: Some(&interval_pipeline_layout),
756 module: &shader_module,
757 entry_point: Some("interval_tile_main"),
758 compilation_options: wgpu::PipelineCompilationOptions {
759 constants: &[("TILE_SIZE", 16.0), ("SUBTILE_SIZE", 4.0)],
760 ..Default::default()
761 },
762 cache: None,
763 })
764 });
765
766 let sort_bind_group_layout =
767 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
768 label: Some("sort bind group layout"),
769 entries: &[
770 buffer_ro(0), buffer_rw(1), buffer_rw(2), ],
774 });
775 let sort_pipeline_layout =
776 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
777 label: Some("sort pipeline layout"),
778 bind_group_layouts: &[
779 Some(common_bind_group_layout),
780 Some(vars_bind_group_layout),
781 Some(&sort_bind_group_layout),
782 ],
783 immediate_size: 0u32,
784 });
785
786 let shader_code = sort_shader();
787 let shader_module = unsafe {
789 device.create_shader_module_trusted(
790 wgpu::ShaderModuleDescriptor {
791 label: Some("sort shader module"),
792 source: wgpu::ShaderSource::Wgsl(shader_code.into()),
793 },
794 wgpu::ShaderRuntimeChecks {
795 bounds_checks: false,
796 force_loop_bounding: false,
797 ray_query_initialization_tracking: false,
798 task_shader_dispatch_tracking: false,
799 mesh_shader_primitive_indices_clamp: false,
800 },
801 )
802 };
803 let sort16_pipeline =
804 device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
805 label: Some("sort16"),
806 layout: Some(&sort_pipeline_layout),
807 module: &shader_module,
808 entry_point: Some("sort_main"),
809 compilation_options: wgpu::PipelineCompilationOptions {
810 constants: &[("SUBTILE_SIZE", 16.0)],
811 ..Default::default()
812 },
813 cache: None,
814 });
815 let sort4_pipeline =
816 device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
817 label: Some("sort4"),
818 layout: Some(&sort_pipeline_layout),
819 module: &shader_module,
820 entry_point: Some("sort_main"),
821 compilation_options: wgpu::PipelineCompilationOptions {
822 constants: &[("SUBTILE_SIZE", 4.0)],
823 ..Default::default()
824 },
825 cache: None,
826 });
827
828 Self {
829 interval_bind_group_layout,
830 sort_bind_group_layout,
831 interval64_pipeline,
832 sort16_pipeline,
833 interval16_pipeline,
834 sort4_pipeline,
835 }
836 }
837
838 fn run(
839 &self,
840 ctx: &Context,
841 buffers: &Buffers,
842 strata: u64,
843 reg_count: u8,
844 compute_pass: &mut wgpu::ComputePass,
845 ) {
846 let strata_bytes = u64::try_from(buffers.strata_size_bytes()).unwrap();
847 let offset_bytes = strata * strata_bytes;
848 let bind_group16 = buffers.bind_groups.interval16(ctx, buffers);
849 compute_pass.set_pipeline(self.interval64_pipeline.get(reg_count));
850 compute_pass.set_bind_group(
851 2,
852 bind_group16,
853 &[u32::try_from(offset_bytes).unwrap()],
854 );
855 compute_pass.dispatch_workgroups_indirect(
856 buffers.tile64.strata.data(),
857 offset_bytes,
858 );
859
860 let bind_group_sort16 = buffers.bind_groups.sort16(ctx, buffers);
861 compute_pass.set_pipeline(&self.sort16_pipeline);
862 compute_pass.set_bind_group(2, bind_group_sort16, &[]);
863 compute_pass
864 .dispatch_workgroups_indirect(buffers.tile16.tiles.data(), 0);
865
866 let bind_group4 = buffers.bind_groups.interval4(ctx, buffers);
867 compute_pass.set_pipeline(self.interval16_pipeline.get(reg_count));
868 compute_pass.set_bind_group(2, bind_group4, &[0]);
869 compute_pass
870 .dispatch_workgroups_indirect(buffers.tile16.sorted.data(), 0);
871
872 let bind_group_sort4 = buffers.bind_groups.sort4(ctx, buffers);
873 compute_pass.set_pipeline(&self.sort4_pipeline);
874 compute_pass.set_bind_group(2, bind_group_sort4, &[]);
875 compute_pass
876 .dispatch_workgroups_indirect(buffers.tile4.tiles.data(), 0);
877 }
878}
879
880struct VoxelContext {
883 bind_group_layout: wgpu::BindGroupLayout,
885
886 voxel_pipeline: RegPipeline,
888}
889
890impl VoxelContext {
891 fn new(
892 device: &wgpu::Device,
893 common_bind_group_layout: &wgpu::BindGroupLayout,
894 vars_bind_group_layout: &wgpu::BindGroupLayout,
895 ) -> Self {
896 let bind_group_layout =
898 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
899 label: Some("voxel bind group layout"),
900 entries: &[
901 buffer_ro(0), buffer_ro(1), buffer_rw(2), ],
905 });
906
907 let voxel_pipeline = RegPipeline::build(|reg_count| {
908 let shader_code = voxel_tiles_shader(reg_count);
909 let pipeline_layout = device.create_pipeline_layout(
910 &wgpu::PipelineLayoutDescriptor {
911 label: Some("voxel pipeline layout"),
912 bind_group_layouts: &[
913 Some(common_bind_group_layout),
914 Some(vars_bind_group_layout),
915 Some(&bind_group_layout),
916 ],
917 immediate_size: 0u32,
918 },
919 );
920 let shader_module = unsafe {
922 device.create_shader_module_trusted(
923 wgpu::ShaderModuleDescriptor {
924 label: Some("voxel shader module"),
925 source: wgpu::ShaderSource::Wgsl(shader_code.into()),
926 },
927 wgpu::ShaderRuntimeChecks {
928 bounds_checks: false,
929 force_loop_bounding: false,
930 ray_query_initialization_tracking: false,
931 task_shader_dispatch_tracking: false,
932 mesh_shader_primitive_indices_clamp: false,
933 },
934 )
935 };
936 device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
937 label: Some(&format!("voxels ({reg_count})")),
938 layout: Some(&pipeline_layout),
939 module: &shader_module,
940 entry_point: Some("voxel_ray_main"),
941 compilation_options: Default::default(),
942 cache: None,
943 })
944 });
945
946 Self {
947 bind_group_layout,
948 voxel_pipeline,
949 }
950 }
951
952 fn run(
953 &self,
954 ctx: &Context,
955 buffers: &Buffers,
956 reg_count: u8,
957 compute_pass: &mut wgpu::ComputePass,
958 ) {
959 let bind_group = buffers.bind_groups.voxel(ctx, buffers);
960 compute_pass.set_pipeline(self.voxel_pipeline.get(reg_count));
961 compute_pass.set_bind_group(2, bind_group, &[]);
962
963 compute_pass
966 .dispatch_workgroups_indirect(buffers.tile4.sorted.data(), 0);
967 }
968}
969
970struct NormalsContext {
971 bind_group_layout: wgpu::BindGroupLayout,
973
974 normals_pipeline: RegPipeline,
976}
977
978impl NormalsContext {
979 fn new(
980 device: &wgpu::Device,
981 common_bind_group_layout: &wgpu::BindGroupLayout,
982 vars_bind_group_layout: &wgpu::BindGroupLayout,
983 ) -> Self {
984 let bind_group_layout =
986 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
987 label: Some("normals bind group layout"),
988 entries: &[
989 buffer_ro(0), buffer_rw(1), ],
992 });
993
994 let normals_pipeline = RegPipeline::build(|reg_count| {
995 let shader_code = normals_shader(reg_count);
996 let pipeline_layout = device.create_pipeline_layout(
997 &wgpu::PipelineLayoutDescriptor {
998 label: Some("normals pipeline"),
999 bind_group_layouts: &[
1000 Some(common_bind_group_layout),
1001 Some(vars_bind_group_layout),
1002 Some(&bind_group_layout),
1003 ],
1004 immediate_size: 0u32,
1005 },
1006 );
1007 let shader_module =
1008 device.create_shader_module(wgpu::ShaderModuleDescriptor {
1009 label: Some("normals shader module"),
1010 source: wgpu::ShaderSource::Wgsl(shader_code.into()),
1011 });
1012 device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
1013 label: Some(&format!("normals ({reg_count})")),
1014 layout: Some(&pipeline_layout),
1015 module: &shader_module,
1016 entry_point: Some("normals_main"),
1017 compilation_options: Default::default(),
1018 cache: None,
1019 })
1020 });
1021
1022 Self {
1023 bind_group_layout,
1024 normals_pipeline,
1025 }
1026 }
1027
1028 fn run(
1029 &self,
1030 ctx: &Context,
1031 buffers: &Buffers,
1032 reg_count: u8,
1033 compute_pass: &mut wgpu::ComputePass,
1034 ) {
1035 let bind_group = buffers.bind_groups.normals(ctx, buffers);
1036 compute_pass.set_pipeline(self.normals_pipeline.get(reg_count));
1037 compute_pass.set_bind_group(2, bind_group, &[]);
1038
1039 compute_pass.dispatch_workgroups(
1040 buffers.image_size.width().div_ceil(8),
1041 buffers.image_size.height().div_ceil(8),
1042 1,
1043 );
1044 }
1045}
1046
1047pub struct Context {
1051 gpu: Gpu,
1052 has_timestamps: bool,
1053
1054 common_bind_group_layout: wgpu::BindGroupLayout,
1056
1057 vars_bind_group_layout: wgpu::BindGroupLayout,
1059
1060 root_ctx: RootContext,
1061 repack_ctx: RepackContext,
1062 interval_ctx: IntervalContext,
1063 voxel_ctx: VoxelContext,
1064 normals_ctx: NormalsContext,
1065 merge_ctx: MergeContext,
1066 reset_ctx: ResetContext,
1067 clear_ctx: ClearContext,
1068}
1069
1070tag!(TilesBufferTag, u32, STORAGE | INDIRECT);
1071tag!(SortedBufferTag, u32, STORAGE | INDIRECT);
1072tag!(ZminBufferTag, u32, STORAGE | COPY_DST);
1073
1074struct TileBuffers<const N: u64> {
1075 tiles: ArrayBuffer<TilesBufferTag>,
1077 sorted: ArrayBuffer<SortedBufferTag>,
1079 zmin: ImageBuffer<ZminBufferTag>,
1081}
1082
1083impl<const N: u64> TileBuffers<N> {
1084 fn new(
1086 device: &wgpu::Device,
1087 render_size: TileRenderSize,
1088 ) -> Result<Self, TileBuffersError> {
1089 let tile_buf_size = Self::tile_buf_size(render_size);
1090 let tiles =
1091 ArrayBuffer::new(device, format!("active_tile{N}"), tile_buf_size)
1092 .map_err(|err| TileBuffersError {
1093 buf: TileBufferName::Tiles,
1094 err,
1095 })?;
1096 let sorted =
1097 ArrayBuffer::new(device, format!("sorted_tile{N}"), tile_buf_size)
1098 .map_err(|err| TileBuffersError {
1099 buf: TileBufferName::Sorted,
1100 err,
1101 })?;
1102 let zmin = ImageBuffer::new(
1103 device,
1104 format!("tile{N}_zmin"),
1105 Self::zmin_buf_size(render_size),
1106 )
1107 .map_err(|err| TileBuffersError {
1108 buf: TileBufferName::Zmin,
1109 err,
1110 })?;
1111
1112 Ok(Self {
1113 tiles,
1114 sorted,
1115 zmin,
1116 })
1117 }
1118
1119 fn tile_buf_size(render_size: TileRenderSize) -> usize {
1120 let n = usize::try_from(N).unwrap();
1121 let nx = usize::try_from(render_size.width()).unwrap() / n;
1122 let ny = usize::try_from(render_size.height()).unwrap() / n;
1123 let nz = 64 / n;
1124 4 + nx * ny * nz
1127 }
1128
1129 fn zmin_buf_size(render_size: TileRenderSize) -> ImageSize {
1130 ImageSize::new(
1131 render_size.width() / u32::try_from(N).unwrap(),
1132 render_size.height() / u32::try_from(N).unwrap(),
1133 )
1134 }
1135
1136 fn grow_to_fit(
1137 &mut self,
1138 device: &wgpu::Device,
1139 render_size: TileRenderSize,
1140 ) -> Result<(), TileBuffersError> {
1141 let TileBuffers {
1142 tiles,
1143 sorted,
1144 zmin,
1145 } = self;
1146 let tile_buf_size = Self::tile_buf_size(render_size);
1147 tiles.grow_to_fit(device, tile_buf_size).map_err(|err| {
1148 TileBuffersError {
1149 buf: TileBufferName::Tiles,
1150 err,
1151 }
1152 })?;
1153 sorted.grow_to_fit(device, tile_buf_size).map_err(|err| {
1154 TileBuffersError {
1155 buf: TileBufferName::Sorted,
1156 err,
1157 }
1158 })?;
1159 zmin.grow_to_fit(device, Self::zmin_buf_size(render_size))
1160 .map_err(|err| TileBuffersError {
1161 buf: TileBufferName::Zmin,
1162 err,
1163 })?;
1164
1165 Ok(())
1166 }
1167
1168 pub fn size(&self) -> u64 {
1172 let TileBuffers {
1174 tiles,
1175 sorted,
1176 zmin,
1177 } = self;
1178 tiles.size_bytes() + sorted.size_bytes() + zmin.size_bytes()
1179 }
1180
1181 pub fn capacity(&self) -> u64 {
1183 let TileBuffers {
1185 tiles,
1186 sorted,
1187 zmin,
1188 } = self;
1189 tiles.capacity() + sorted.capacity() + zmin.capacity()
1190 }
1191}
1192
1193tag!(RootTilesBufferTag, u32, STORAGE | COPY_DST);
1194tag!(RootStrataBufferTag, u8, STORAGE | INDIRECT | COPY_DST);
1195tag!(RootZminBufferTag, u32, STORAGE | COPY_DST);
1196tag!(RootZmaxBufferTag, u32, STORAGE | COPY_DST);
1197
1198struct RootTileBuffers {
1200 tiles: ArrayBuffer<RootTilesBufferTag>,
1202 strata: ArrayBuffer<RootStrataBufferTag>,
1204 zmin: ImageBuffer<RootZminBufferTag>,
1205 zmax: ImageBuffer<RootZmaxBufferTag>,
1206}
1207
1208impl RootTileBuffers {
1209 fn new(
1211 device: &wgpu::Device,
1212 render_size: TileRenderSize,
1213 ) -> Result<Self, RootTileBuffersError> {
1214 const N: usize = 64;
1216
1217 let tiles = ArrayBuffer::new(
1219 device,
1220 format!("tiles_out{N}"),
1221 Self::tiles_buf_size(render_size),
1222 )
1223 .map_err(|err| RootTileBuffersError {
1224 buf: RootTileBufferName::Tiles,
1225 err,
1226 })?;
1227
1228 let strata = ArrayBuffer::new(
1229 device,
1230 format!("strata_tile{N}"),
1231 Self::strata_buf_size(render_size),
1232 )
1233 .map_err(|err| RootTileBuffersError {
1234 buf: RootTileBufferName::Strata,
1235 err,
1236 })?;
1237
1238 let z_buf_size = Self::z_buf_size(render_size);
1239 let zmin =
1240 ImageBuffer::new(device, format!("tile{N}_zmin"), z_buf_size)
1241 .map_err(|err| RootTileBuffersError {
1242 buf: RootTileBufferName::Zmin,
1243 err,
1244 })?;
1245 let zmax =
1246 ImageBuffer::new(device, format!("tile{N}_zmax"), z_buf_size)
1247 .map_err(|err| RootTileBuffersError {
1248 buf: RootTileBufferName::Zmax,
1249 err,
1250 })?;
1251 Ok(Self {
1252 tiles,
1253 strata,
1254 zmin,
1255 zmax,
1256 })
1257 }
1258
1259 fn tiles_buf_size(render_size: TileRenderSize) -> usize {
1260 let nx = usize::try_from(render_size.nx()).unwrap();
1261 let ny = usize::try_from(render_size.ny()).unwrap();
1262 let nz = usize::try_from(render_size.nz()).unwrap();
1263 4 + nx * ny * nz
1266 }
1267
1268 fn strata_buf_size(render_size: TileRenderSize) -> usize {
1269 let nz = usize::try_from(render_size.nz()).unwrap();
1270 let strata_size = strata_size_bytes(render_size);
1271 strata_size * nz
1272 }
1273
1274 fn z_buf_size(render_size: TileRenderSize) -> ImageSize {
1275 ImageSize::new(render_size.nx(), render_size.ny())
1276 }
1277
1278 fn grow_to_fit(
1280 &mut self,
1281 device: &wgpu::Device,
1282 render_size: TileRenderSize,
1283 ) -> Result<(), RootTileBuffersError> {
1284 let RootTileBuffers {
1286 tiles,
1287 strata,
1288 zmin,
1289 zmax,
1290 } = self;
1291 tiles
1292 .grow_to_fit(device, Self::tiles_buf_size(render_size))
1293 .map_err(|err| RootTileBuffersError {
1294 buf: RootTileBufferName::Tiles,
1295 err,
1296 })?;
1297 strata
1298 .grow_to_fit(device, Self::strata_buf_size(render_size))
1299 .map_err(|err| RootTileBuffersError {
1300 buf: RootTileBufferName::Strata,
1301 err,
1302 })?;
1303
1304 let z_buf_size = Self::z_buf_size(render_size);
1305 zmin.grow_to_fit(device, z_buf_size).map_err(|err| {
1306 RootTileBuffersError {
1307 buf: RootTileBufferName::Zmin,
1308 err,
1309 }
1310 })?;
1311 zmax.grow_to_fit(device, z_buf_size).map_err(|err| {
1312 RootTileBuffersError {
1313 buf: RootTileBufferName::Zmax,
1314 err,
1315 }
1316 })?;
1317
1318 Ok(())
1319 }
1320
1321 pub fn size(&self) -> u64 {
1323 let RootTileBuffers {
1325 tiles,
1326 strata,
1327 zmin,
1328 zmax,
1329 } = self;
1330 tiles.size_bytes()
1331 + strata.size_bytes()
1332 + zmin.size_bytes()
1333 + zmax.size_bytes()
1334 }
1335
1336 pub fn capacity(&self) -> u64 {
1338 let RootTileBuffers {
1340 tiles,
1341 strata,
1342 zmin,
1343 zmax,
1344 } = self;
1345 tiles.capacity() + strata.capacity() + zmin.capacity() + zmax.capacity()
1346 }
1347}
1348
1349pub struct RenderShape {
1354 shape: VmShape,
1356 axes: [u32; 3],
1358 bytecode: Bytecode,
1360 vars: wgpu::Buffer,
1365 vars_bind_group: std::cell::OnceCell<wgpu::BindGroup>,
1370}
1371
1372#[derive(Debug, thiserror::Error)]
1374pub enum RenderShapeError {
1375 #[error(
1377 "shape bytecode is {0} tape words (8 bytes each), which exceeds \
1378 buffer capacity of {TAPE_DATA_CAPACITY} tape words"
1379 )]
1380 TooLong(usize),
1381 #[error(transparent)]
1383 RegisterError(#[from] ReservedRegister),
1384}
1385
1386impl RenderShape {
1387 fn new(
1388 shape: &VmShape,
1389 device: &wgpu::Device,
1390 ) -> Result<Self, RenderShapeError> {
1391 let bytecode = Bytecode::new(shape.inner().data())?;
1393 if bytecode.len() / 2 > TAPE_DATA_CAPACITY {
1394 return Err(RenderShapeError::TooLong(bytecode.len() / 2));
1395 }
1396
1397 let vars = shape.inner().vars();
1399 let axes = [Var::X, Var::Y, Var::Z]
1400 .map(|a| vars.get(&a).map(|v| v as u32).unwrap_or(u32::MAX));
1401
1402 let vars = device.create_buffer(&wgpu::BufferDescriptor {
1405 label: Some("vars"),
1406 size: u64::try_from(std::mem::size_of::<f32>() * vars.len())
1407 .unwrap(),
1408 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
1409 mapped_at_creation: false,
1410 });
1411
1412 Ok(Self {
1413 shape: shape.clone(),
1414 axes,
1415 bytecode,
1416 vars,
1417 vars_bind_group: Default::default(),
1418 })
1419 }
1420
1421 fn vars_bind_group(&self, ctx: &Context) -> &wgpu::BindGroup {
1422 self.vars_bind_group.get_or_init(|| {
1423 ctx.gpu
1424 .device
1425 .create_bind_group(&wgpu::BindGroupDescriptor {
1426 label: Some("vars bind group"),
1427 layout: &ctx.vars_bind_group_layout,
1428 entries: &[wgpu::BindGroupEntry {
1429 binding: 0,
1430 resource: self.vars.as_entire_binding(),
1431 }],
1432 })
1433 })
1434 }
1435}
1436
1437tag!(TileTapesBufferTag, u32, STORAGE | COPY_DST);
1438tag!(VoxelsBufferTag, u32, STORAGE | COPY_DST);
1439tag!(pub GeomBufferTag, GeometryPixel, STORAGE | COPY_SRC | COPY_DST,
1440 "Tag for a on-GPU buffer storing [`GeometryPixel`] values");
1441
1442pub struct Buffers {
1450 image_size: VoxelSize,
1455
1456 config_buf: wgpu::Buffer,
1458
1459 z_hist_buf: wgpu::Buffer,
1467
1468 tile_tapes: ArrayBuffer<TileTapesBufferTag>,
1470
1471 tile64: RootTileBuffers,
1473
1474 tile16: TileBuffers<16>,
1476
1477 tile4: TileBuffers<4>,
1479
1480 voxels: ArrayBuffer<VoxelsBufferTag>,
1482
1483 geom: ImageBuffer<GeomBufferTag>,
1485
1486 timestamps: Option<wgpu::QuerySet>,
1491
1492 ts_buf: wgpu::Buffer,
1494
1495 bind_groups: BindGroups,
1497}
1498
1499pub struct ImageReadBuffer {
1506 image_size: VoxelSize,
1508
1509 buffer: ImageReadArrayBuffer,
1514}
1515
1516impl ImageReadBuffer {
1517 fn new(
1518 device: &wgpu::Device,
1519 name: String,
1520 image_size: VoxelSize,
1521 ) -> Result<Self, BufferSizeError> {
1522 Ok(Self {
1523 image_size,
1524 buffer: ImageReadArrayBuffer::new(
1525 device,
1526 name,
1527 Buffers::image_buf_size(image_size),
1528 )?,
1529 })
1530 }
1531
1532 fn grow_to_fit(
1533 &mut self,
1534 device: &wgpu::Device,
1535 image_size: VoxelSize,
1536 ) -> Result<(), BufferSizeError> {
1537 self.image_size = image_size;
1538 self.buffer
1539 .grow_to_fit(device, Buffers::image_buf_size(image_size))
1540 }
1541}
1542
1543tag!(ImageReadTag, u8, COPY_DST | MAP_READ);
1544type ImageReadArrayBuffer = ArrayBuffer<ImageReadTag>;
1545
1546#[derive(Default)]
1548struct BindGroups {
1549 common: std::cell::OnceCell<wgpu::BindGroup>,
1550 merge: std::cell::OnceCell<wgpu::BindGroup>,
1551 root: std::cell::OnceCell<wgpu::BindGroup>,
1552 repack: std::cell::OnceCell<wgpu::BindGroup>,
1553 interval16: std::cell::OnceCell<wgpu::BindGroup>,
1554 sort16: std::cell::OnceCell<wgpu::BindGroup>,
1555 interval4: std::cell::OnceCell<wgpu::BindGroup>,
1556 sort4: std::cell::OnceCell<wgpu::BindGroup>,
1557 voxel: std::cell::OnceCell<wgpu::BindGroup>,
1558 normals: std::cell::OnceCell<wgpu::BindGroup>,
1559 clear: std::cell::OnceCell<wgpu::BindGroup>,
1560}
1561
1562impl BindGroups {
1563 fn common(&self, ctx: &Context, buffers: &Buffers) -> &wgpu::BindGroup {
1564 self.common.get_or_init(|| {
1565 ctx.gpu
1566 .device
1567 .create_bind_group(&wgpu::BindGroupDescriptor {
1568 label: Some("common bind group"),
1569 layout: &ctx.common_bind_group_layout,
1570 entries: &[
1571 wgpu::BindGroupEntry {
1572 binding: 0,
1573 resource: buffers.config_buf.as_entire_binding(),
1574 },
1575 wgpu::BindGroupEntry {
1576 binding: 1,
1577 resource: buffers.tile_tapes.bind_active(),
1578 },
1579 ],
1580 })
1581 })
1582 }
1583
1584 fn clear(&self, ctx: &Context, buffers: &Buffers) -> &wgpu::BindGroup {
1585 self.clear.get_or_init(|| {
1586 ctx.gpu
1587 .device
1588 .create_bind_group(&wgpu::BindGroupDescriptor {
1589 label: Some("clear bind group"),
1590 layout: &ctx.clear_ctx.bind_group_layout,
1591 entries: &[
1592 wgpu::BindGroupEntry {
1593 binding: 0,
1594 resource: buffers
1595 .tile16
1596 .tiles
1597 .data()
1598 .slice(0..16)
1599 .into(),
1600 },
1601 wgpu::BindGroupEntry {
1602 binding: 1,
1603 resource: buffers
1604 .tile16
1605 .sorted
1606 .data()
1607 .slice(0..16)
1608 .into(),
1609 },
1610 wgpu::BindGroupEntry {
1611 binding: 2,
1612 resource: buffers
1613 .tile4
1614 .tiles
1615 .data()
1616 .slice(0..16)
1617 .into(),
1618 },
1619 wgpu::BindGroupEntry {
1620 binding: 3,
1621 resource: buffers
1622 .tile4
1623 .sorted
1624 .data()
1625 .slice(0..16)
1626 .into(),
1627 },
1628 wgpu::BindGroupEntry {
1629 binding: 4,
1630 resource: buffers.z_hist_buf.as_entire_binding(),
1631 },
1632 ],
1633 })
1634 })
1635 }
1636
1637 fn merge(&self, ctx: &Context, buffers: &Buffers) -> &wgpu::BindGroup {
1638 self.merge.get_or_init(|| {
1639 ctx.gpu
1640 .device
1641 .create_bind_group(&wgpu::BindGroupDescriptor {
1642 label: Some("merge bind group"),
1643 layout: &ctx.merge_ctx.bind_group_layout,
1644 entries: &[
1645 wgpu::BindGroupEntry {
1646 binding: 0,
1647 resource: buffers.tile64.zmin.bind_active(),
1648 },
1649 wgpu::BindGroupEntry {
1650 binding: 1,
1651 resource: buffers.tile16.zmin.bind_active(),
1652 },
1653 wgpu::BindGroupEntry {
1654 binding: 2,
1655 resource: buffers.tile4.zmin.bind_active(),
1656 },
1657 wgpu::BindGroupEntry {
1658 binding: 3,
1659 resource: buffers.voxels.bind_active(),
1660 },
1661 ],
1662 })
1663 })
1664 }
1665
1666 fn root(&self, ctx: &Context, buffers: &Buffers) -> &wgpu::BindGroup {
1667 self.root.get_or_init(|| {
1668 ctx.gpu
1669 .device
1670 .create_bind_group(&wgpu::BindGroupDescriptor {
1671 label: Some("interval root bind group"),
1672 layout: &ctx.root_ctx.bind_group_layout,
1673 entries: &[
1674 wgpu::BindGroupEntry {
1675 binding: 0,
1676 resource: buffers.tile64.tiles.bind_active(),
1677 },
1678 wgpu::BindGroupEntry {
1679 binding: 1,
1680 resource: buffers.tile64.zmax.bind_active(),
1681 },
1682 ],
1683 })
1684 })
1685 }
1686
1687 fn repack(&self, ctx: &Context, buffers: &Buffers) -> &wgpu::BindGroup {
1688 self.repack.get_or_init(|| {
1689 ctx.gpu
1690 .device
1691 .create_bind_group(&wgpu::BindGroupDescriptor {
1692 label: Some("repack bind group"),
1693 layout: &ctx.repack_ctx.bind_group_layout,
1694 entries: &[
1695 wgpu::BindGroupEntry {
1696 binding: 0,
1697 resource: buffers.tile64.tiles.bind_active(),
1698 },
1699 wgpu::BindGroupEntry {
1700 binding: 1,
1701 resource: buffers.tile64.zmax.bind_active(),
1702 },
1703 wgpu::BindGroupEntry {
1704 binding: 2,
1705 resource: buffers.tile64.strata.bind_active(),
1706 },
1707 ],
1708 })
1709 })
1710 }
1711
1712 fn interval16(&self, ctx: &Context, buffers: &Buffers) -> &wgpu::BindGroup {
1713 let strata_bytes = u64::try_from(buffers.strata_size_bytes()).unwrap();
1714 self.interval16.get_or_init(|| {
1715 ctx.gpu
1716 .device
1717 .create_bind_group(&wgpu::BindGroupDescriptor {
1718 label: Some("interval16 bind group"),
1719 layout: &ctx.interval_ctx.interval_bind_group_layout,
1720 entries: &[
1721 wgpu::BindGroupEntry {
1722 binding: 0,
1723 resource: buffers
1724 .tile64
1725 .strata
1726 .data()
1727 .slice(0..strata_bytes) .into(),
1729 },
1730 wgpu::BindGroupEntry {
1731 binding: 1,
1732 resource: buffers.tile64.zmin.bind_active(),
1733 },
1734 wgpu::BindGroupEntry {
1735 binding: 2,
1736 resource: buffers.tile16.tiles.bind_active(),
1737 },
1738 wgpu::BindGroupEntry {
1739 binding: 3,
1740 resource: buffers.tile16.zmin.bind_active(),
1741 },
1742 wgpu::BindGroupEntry {
1743 binding: 4,
1744 resource: buffers.z_hist_buf.slice(0..16).into(),
1745 },
1746 ],
1747 })
1748 })
1749 }
1750
1751 fn sort16(&self, ctx: &Context, buffers: &Buffers) -> &wgpu::BindGroup {
1752 self.sort16.get_or_init(|| {
1753 Self::sort_bind_group(
1754 ctx,
1755 &buffers.tile16,
1756 buffers.z_hist_buf.slice(0..16).into(),
1757 )
1758 })
1759 }
1760
1761 fn sort4(&self, ctx: &Context, buffers: &Buffers) -> &wgpu::BindGroup {
1762 self.sort4.get_or_init(|| {
1763 Self::sort_bind_group(
1764 ctx,
1765 &buffers.tile4,
1766 buffers.z_hist_buf.slice(256..320).into(),
1767 )
1768 })
1769 }
1770
1771 fn sort_bind_group<const N: u64>(
1772 ctx: &Context,
1773 tile_bufs: &TileBuffers<N>,
1774 z_hist: wgpu::BindingResource,
1775 ) -> wgpu::BindGroup {
1776 ctx.gpu
1777 .device
1778 .create_bind_group(&wgpu::BindGroupDescriptor {
1779 label: Some(&format!("sort{N} bind group")),
1780 layout: &ctx.interval_ctx.sort_bind_group_layout,
1781 entries: &[
1782 wgpu::BindGroupEntry {
1783 binding: 0,
1784 resource: tile_bufs.tiles.bind_active(),
1785 },
1786 wgpu::BindGroupEntry {
1787 binding: 1,
1788 resource: z_hist,
1789 },
1790 wgpu::BindGroupEntry {
1791 binding: 2,
1792 resource: tile_bufs.sorted.bind_active(),
1793 },
1794 ],
1795 })
1796 }
1797
1798 fn interval4(&self, ctx: &Context, buffers: &Buffers) -> &wgpu::BindGroup {
1799 self.interval4.get_or_init(|| {
1800 ctx.gpu
1801 .device
1802 .create_bind_group(&wgpu::BindGroupDescriptor {
1803 label: Some("interval4 bind group"),
1804 layout: &ctx.interval_ctx.interval_bind_group_layout,
1805 entries: &[
1806 wgpu::BindGroupEntry {
1807 binding: 0,
1808 resource: buffers.tile16.sorted.bind_active(),
1809 },
1810 wgpu::BindGroupEntry {
1811 binding: 1,
1812 resource: buffers.tile16.zmin.bind_active(),
1813 },
1814 wgpu::BindGroupEntry {
1815 binding: 2,
1816 resource: buffers.tile4.tiles.bind_active(),
1817 },
1818 wgpu::BindGroupEntry {
1819 binding: 3,
1820 resource: buffers.tile4.zmin.bind_active(),
1821 },
1822 wgpu::BindGroupEntry {
1823 binding: 4,
1824 resource: buffers.z_hist_buf.slice(256..320).into(),
1825 },
1826 ],
1827 })
1828 })
1829 }
1830
1831 fn voxel(&self, ctx: &Context, buffers: &Buffers) -> &wgpu::BindGroup {
1832 self.voxel.get_or_init(|| {
1833 ctx.gpu
1834 .device
1835 .create_bind_group(&wgpu::BindGroupDescriptor {
1836 label: Some("voxel bind group"),
1837 layout: &ctx.voxel_ctx.bind_group_layout,
1838 entries: &[
1839 wgpu::BindGroupEntry {
1840 binding: 0,
1841 resource: buffers.tile4.sorted.bind_active(),
1842 },
1843 wgpu::BindGroupEntry {
1844 binding: 1,
1845 resource: buffers.tile4.zmin.bind_active(),
1846 },
1847 wgpu::BindGroupEntry {
1848 binding: 2,
1849 resource: buffers.voxels.bind_active(),
1850 },
1851 ],
1852 })
1853 })
1854 }
1855
1856 fn normals(&self, ctx: &Context, buffers: &Buffers) -> &wgpu::BindGroup {
1857 self.normals.get_or_init(|| {
1858 ctx.gpu
1859 .device
1860 .create_bind_group(&wgpu::BindGroupDescriptor {
1861 label: Some("normals bind group"),
1862 layout: &ctx.normals_ctx.bind_group_layout,
1863 entries: &[
1864 wgpu::BindGroupEntry {
1865 binding: 0,
1866 resource: buffers.voxels.bind_active(),
1867 },
1868 wgpu::BindGroupEntry {
1869 binding: 1,
1870 resource: buffers.geom.bind_active(),
1871 },
1872 ],
1873 })
1874 })
1875 }
1876}
1877
1878impl Buffers {
1879 pub fn image_size(&self) -> VoxelSize {
1881 self.image_size
1882 }
1883
1884 pub fn image_storage_buffer(&mut self) -> &ImageBuffer<GeomBufferTag> {
1891 &self.geom
1892 }
1893
1894 fn new(
1895 device: &wgpu::Device,
1896 image_size: VoxelSize,
1897 has_timestamps: bool,
1898 ) -> Result<Self, BuffersError> {
1899 static_assertions::const_assert!(
1901 (std::mem::size_of::<Config>()
1902 + TAPE_DATA_CAPACITY * std::mem::size_of::<TapeWord>())
1903 as u64
1904 <= BufferType::Storage.max_size()
1905 );
1906
1907 ImageReadArrayBuffer::check_size(Self::image_buf_size(image_size))
1910 .map_err(|err| BuffersError {
1911 requested: image_size,
1912 buf: BufferName::Image,
1913 err,
1914 })?;
1915
1916 let config_buf = device.create_buffer(&wgpu::BufferDescriptor {
1917 label: Some("config"),
1918 size: (std::mem::size_of::<Config>()
1919 + TAPE_DATA_CAPACITY * std::mem::size_of::<TapeWord>())
1920 as u64,
1921 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
1922 mapped_at_creation: false,
1923 });
1924
1925 let render_size = TileRenderSize::from(image_size);
1926 let voxels = ArrayBuffer::new(
1927 device,
1928 "voxels".to_string(),
1929 Self::voxels_buf_size(render_size),
1930 )
1931 .map_err(|err| BuffersError {
1932 requested: image_size,
1933 buf: BufferName::Voxels,
1934 err,
1935 })?;
1936 let tile_tapes = ArrayBuffer::new(
1937 device,
1938 "tile tape".to_string(),
1939 Self::tile_tapes_buf_size(render_size),
1940 )
1941 .map_err(|err| BuffersError {
1942 requested: image_size,
1943 buf: BufferName::TileTapes,
1944 err,
1945 })?;
1946
1947 let geom = ImageBuffer::new(
1948 device,
1949 "geom".to_string(),
1950 Self::geom_buf_size(image_size),
1951 )
1952 .map_err(|err| BuffersError {
1953 requested: image_size,
1954 buf: BufferName::Geom,
1955 err,
1956 })?;
1957
1958 let ts_buf = device.create_buffer(&wgpu::BufferDescriptor {
1959 label: Some("ts"),
1960 size: 2 * std::mem::size_of::<u64>() as u64,
1961 usage: wgpu::BufferUsages::QUERY_RESOLVE
1962 | wgpu::BufferUsages::COPY_SRC,
1963 mapped_at_creation: false,
1964 });
1965
1966 let tile64 =
1967 RootTileBuffers::new(device, render_size).map_err(|e| {
1968 BuffersError {
1969 requested: image_size,
1970 buf: BufferName::Tile64(e.buf),
1971 err: e.err,
1972 }
1973 })?;
1974 let tile16 = TileBuffers::new(device, render_size).map_err(|e| {
1975 BuffersError {
1976 requested: image_size,
1977 buf: BufferName::Tile16(e.buf),
1978 err: e.err,
1979 }
1980 })?;
1981 let tile4 = TileBuffers::new(device, render_size).map_err(|e| {
1982 BuffersError {
1983 requested: image_size,
1984 buf: BufferName::Tile4(e.buf),
1985 err: e.err,
1986 }
1987 })?;
1988
1989 let timestamps = if has_timestamps {
1990 Some(device.create_query_set(&wgpu::QuerySetDescriptor {
1991 label: Some("timestamp query set"),
1992 ty: wgpu::QueryType::Timestamp,
1993 count: 2,
1994 }))
1995 } else {
1996 None
1997 };
1998
1999 let z_hist_buf = device.create_buffer(&wgpu::BufferDescriptor {
2001 label: Some("tiles_zhist"),
2002 size: u64::try_from(
2003 (4 * std::mem::size_of::<u32>()).next_multiple_of(256)
2004 + (16 * std::mem::size_of::<u32>()),
2005 )
2006 .unwrap(),
2007 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
2008 mapped_at_creation: false,
2009 });
2010
2011 Ok(Self {
2012 config_buf,
2013 image_size,
2014 tile_tapes,
2015 tile64,
2016 tile16,
2017 tile4,
2018 voxels,
2019 geom,
2020 timestamps,
2021 z_hist_buf,
2022 ts_buf,
2023 bind_groups: Default::default(),
2024 })
2025 }
2026
2027 fn render_size(&self) -> TileRenderSize {
2028 self.image_size.into()
2029 }
2030
2031 fn strata_size_bytes(&self) -> usize {
2033 strata_size_bytes(self.render_size())
2034 }
2035
2036 fn tile_tapes_buf_size(render_size: TileRenderSize) -> usize {
2058 let nx = usize::try_from(render_size.nx()).unwrap();
2059 let ny = usize::try_from(render_size.ny()).unwrap();
2060 let nz = usize::try_from(render_size.nz()).unwrap();
2061
2062 let xy_size = (64usize / 4).pow(3) + (64usize / 16).pow(3);
2064
2065 nx.checked_mul(ny)
2069 .unwrap()
2070 .checked_mul(nz.checked_add(xy_size).unwrap())
2071 .unwrap()
2072 }
2073
2074 fn voxels_buf_size(render_size: TileRenderSize) -> usize {
2075 render_size.pixels()
2076 }
2077
2078 fn geom_buf_size(image_size: VoxelSize) -> ImageSize {
2080 ImageSize::new(image_size.width(), image_size.height())
2081 }
2082
2083 fn image_buf_size(image_size: VoxelSize) -> usize {
2085 Self::geom_buf_size(image_size)
2086 .item_count()
2087 .checked_mul(std::mem::size_of::<GeometryPixel>())
2089 .unwrap()
2090 .checked_add(16)
2092 .unwrap()
2093 }
2094
2095 fn set_image_size(
2103 &mut self,
2104 device: &wgpu::Device,
2105 image_size: VoxelSize,
2106 ) -> Result<(), BuffersError> {
2107 let render_size = TileRenderSize::from(image_size);
2108 let Buffers {
2109 image_size: image_size_ref,
2110 config_buf: _,
2111 z_hist_buf: _,
2112 tile_tapes,
2113 tile64,
2114 tile16,
2115 tile4,
2116 voxels,
2117 geom,
2118 timestamps: _,
2119 ts_buf: _,
2120 bind_groups,
2121 } = self;
2122 if *image_size_ref != image_size {
2124 *bind_groups = Default::default();
2125 }
2126 *image_size_ref = image_size;
2127 tile_tapes
2128 .grow_to_fit(device, Self::tile_tapes_buf_size(render_size))
2129 .map_err(|err| BuffersError {
2130 requested: image_size,
2131 buf: BufferName::TileTapes,
2132 err,
2133 })?;
2134 tile64
2135 .grow_to_fit(device, render_size)
2136 .map_err(|e| BuffersError {
2137 requested: image_size,
2138 buf: BufferName::Tile64(e.buf),
2139 err: e.err,
2140 })?;
2141 tile16
2142 .grow_to_fit(device, render_size)
2143 .map_err(|e| BuffersError {
2144 requested: image_size,
2145 buf: BufferName::Tile16(e.buf),
2146 err: e.err,
2147 })?;
2148 tile4
2149 .grow_to_fit(device, render_size)
2150 .map_err(|e| BuffersError {
2151 requested: image_size,
2152 buf: BufferName::Tile4(e.buf),
2153 err: e.err,
2154 })?;
2155
2156 voxels
2157 .grow_to_fit(device, Self::voxels_buf_size(render_size))
2158 .map_err(|err| BuffersError {
2159 requested: image_size,
2160 buf: BufferName::Voxels,
2161 err,
2162 })?;
2163 geom.grow_to_fit(device, Self::geom_buf_size(image_size))
2164 .map_err(|err| BuffersError {
2165 requested: image_size,
2166 buf: BufferName::Geom,
2167 err,
2168 })?;
2169
2170 ImageReadArrayBuffer::check_size(Self::image_buf_size(image_size))
2173 .map_err(|err| BuffersError {
2174 requested: image_size,
2175 buf: BufferName::Image,
2176 err,
2177 })?;
2178
2179 Ok(())
2180 }
2181
2182 pub fn capacity(&self) -> u64 {
2184 let Buffers {
2186 image_size: _,
2187 config_buf,
2188 z_hist_buf,
2189 tile_tapes,
2190 tile64,
2191 tile16,
2192 tile4,
2193 voxels,
2194 geom,
2195 timestamps: _,
2196 ts_buf,
2197 bind_groups: _,
2198 } = self;
2199 config_buf.size()
2200 + z_hist_buf.size()
2201 + tile_tapes.capacity()
2202 + tile64.capacity()
2203 + tile16.capacity()
2204 + tile4.capacity()
2205 + voxels.capacity()
2206 + geom.capacity()
2207 + ts_buf.size()
2208 }
2209
2210 pub fn size(&self) -> u64 {
2212 let Buffers {
2214 image_size: _,
2215 config_buf,
2216 z_hist_buf,
2217 tile_tapes,
2218 tile64,
2219 tile16,
2220 tile4,
2221 voxels,
2222 geom,
2223 timestamps: _,
2224 ts_buf,
2225 bind_groups: _,
2226 } = self;
2227 config_buf.size()
2228 + z_hist_buf.size()
2229 + tile_tapes.size_bytes()
2230 + tile64.size()
2231 + tile16.size()
2232 + tile4.size()
2233 + voxels.size_bytes()
2234 + geom.size_bytes()
2235 + ts_buf.size()
2236 }
2237}
2238
2239impl Context {
2240 pub fn new(gpu: &Gpu) -> Self {
2245 let has_timestamps = gpu
2246 .device
2247 .features()
2248 .contains(wgpu::Features::TIMESTAMP_QUERY);
2249 if !has_timestamps {
2250 log::warn!(
2251 "WGPU device is missing `TIMESTAMP_QUERY`; \
2252 timestamps are disabled"
2253 );
2254 }
2255
2256 let common_bind_group_layout = gpu.device.create_bind_group_layout(
2258 &wgpu::BindGroupLayoutDescriptor {
2259 label: Some("common bind group layout"),
2260 entries: &[
2261 buffer_rw(0), buffer_rw(1), ],
2264 },
2265 );
2266 let vars_bind_group_layout = gpu.device.create_bind_group_layout(
2267 &wgpu::BindGroupLayoutDescriptor {
2268 label: Some("vars bind group layout"),
2269 entries: &[
2270 buffer_ro(0), ],
2272 },
2273 );
2274
2275 let root_ctx = RootContext::new(
2276 &gpu.device,
2277 &common_bind_group_layout,
2278 &vars_bind_group_layout,
2279 );
2280 let repack_ctx = RepackContext::new(
2281 &gpu.device,
2282 &common_bind_group_layout,
2283 &vars_bind_group_layout,
2284 );
2285 let interval_ctx = IntervalContext::new(
2286 &gpu.device,
2287 &common_bind_group_layout,
2288 &vars_bind_group_layout,
2289 );
2290 let voxel_ctx = VoxelContext::new(
2291 &gpu.device,
2292 &common_bind_group_layout,
2293 &vars_bind_group_layout,
2294 );
2295 let normals_ctx = NormalsContext::new(
2296 &gpu.device,
2297 &common_bind_group_layout,
2298 &vars_bind_group_layout,
2299 );
2300 let merge_ctx = MergeContext::new(
2301 &gpu.device,
2302 &common_bind_group_layout,
2303 &vars_bind_group_layout,
2304 );
2305 let reset_ctx = ResetContext::new();
2306 let clear_ctx = ClearContext::new(
2307 &gpu.device,
2308 &common_bind_group_layout,
2309 &vars_bind_group_layout,
2310 );
2311
2312 Self {
2313 gpu: gpu.clone(),
2314 has_timestamps,
2315 common_bind_group_layout,
2316 vars_bind_group_layout,
2317 root_ctx,
2318 repack_ctx,
2319 interval_ctx,
2320 voxel_ctx,
2321 normals_ctx,
2322 merge_ctx,
2323 reset_ctx,
2324 clear_ctx,
2325 }
2326 }
2327
2328 pub fn buffers(
2335 &self,
2336 image_size: VoxelSize,
2337 ) -> Result<Buffers, BuffersError> {
2338 Buffers::new(&self.gpu.device, image_size, self.has_timestamps)
2339 }
2340
2341 pub fn image_buffer(&self, buffers: &Buffers) -> ImageReadBuffer {
2347 ImageReadBuffer::new(
2348 &self.gpu.device,
2349 "image".to_owned(),
2350 buffers.image_size,
2351 )
2352 .expect(
2353 "buffers.image_size should always be \
2354 a valid size for ImageReadBuffer::new",
2355 )
2356 }
2357
2358 pub fn shape(
2360 &self,
2361 shape: &VmShape,
2362 ) -> Result<RenderShape, RenderShapeError> {
2363 RenderShape::new(shape, &self.gpu.device)
2364 }
2365
2366 #[cfg(not(target_arch = "wasm32"))]
2370 pub fn run(
2371 &self,
2372 shape: &RenderShape,
2373 buffers: &Buffers,
2374 out: &mut ImageReadBuffer,
2375 settings: RenderConfig,
2376 ) -> Result<Image, MissingVar> {
2377 self.run_with_vars(shape, &Default::default(), buffers, out, settings)
2378 }
2379
2380 #[cfg(not(target_arch = "wasm32"))]
2384 pub fn run_with_vars(
2385 &self,
2386 shape: &RenderShape,
2387 vars: &ShapeVars<f32>,
2388 buffers: &Buffers,
2389 out: &mut ImageReadBuffer,
2390 settings: RenderConfig,
2391 ) -> Result<Image, MissingVar> {
2392 self.submit_with_vars(shape, vars, buffers, Some(out), &settings)?;
2393 let image = self.map_image(out);
2394 Ok(image.image())
2395 }
2396
2397 #[cfg(any(target_arch = "wasm32", doc))]
2401 pub async fn run_async(
2402 &self,
2403 shape: &RenderShape,
2404 buffers: &Buffers,
2405 out: &mut ImageReadBuffer,
2406 settings: RenderConfig,
2407 ) -> Result<Image, MissingVar> {
2408 self.run_with_vars_async(
2409 shape,
2410 &Default::default(),
2411 buffers,
2412 out,
2413 settings,
2414 )
2415 .await
2416 }
2417
2418 #[cfg(any(target_arch = "wasm32", doc))]
2422 pub async fn run_with_vars_async(
2423 &self,
2424 shape: &RenderShape,
2425 vars: &ShapeVars<f32>,
2426 buffers: &Buffers,
2427 out: &mut ImageReadBuffer,
2428 settings: RenderConfig,
2429 ) -> Result<Image, MissingVar> {
2430 self.submit_with_vars(shape, vars, buffers, Some(out), &settings)?;
2431 let image = self.map_image_async(out).await;
2432 Ok(image.image())
2433 }
2434
2435 pub fn submit(
2446 &self,
2447 shape: &RenderShape,
2448 buffers: &mut Buffers,
2449 out: Option<&mut ImageReadBuffer>,
2450 settings: &RenderConfig,
2451 ) -> Result<(), MissingVar> {
2452 self.submit_with_vars(
2453 shape,
2454 &Default::default(),
2455 buffers,
2456 out,
2457 settings,
2458 )
2459 }
2460
2461 pub fn submit_with_vars(
2465 &self,
2466 shape: &RenderShape,
2467 vars: &ShapeVars<f32>,
2468 buffers: &Buffers,
2469 out: Option<&mut ImageReadBuffer>,
2470 settings: &RenderConfig,
2471 ) -> Result<(), MissingVar> {
2472 let render_size = TileRenderSize::from(buffers.image_size);
2473
2474 let mat =
2475 settings.world_to_model * buffers.image_size.screen_to_world();
2476
2477 let start_offset = u32::try_from(shape.bytecode.len()).unwrap() / 2;
2479 let config = Config {
2480 mat: mat.data.as_slice().try_into().unwrap(),
2481 axes: shape.axes,
2482 render_size: [
2483 render_size.width(),
2484 render_size.height(),
2485 render_size.depth(),
2486 ],
2487 tape_data_capacity: TAPE_DATA_CAPACITY.try_into().unwrap(),
2488 image_size: [
2489 buffers.image_size.width(),
2490 buffers.image_size.height(),
2491 buffers.image_size.depth(),
2492 ],
2493 tape_data_offset: start_offset,
2494 root_tape_len: start_offset,
2495 };
2496
2497 {
2498 let config_len = std::mem::size_of_val(&config);
2500 let mut writer = self
2501 .gpu
2502 .queue
2503 .write_buffer_with(
2504 &buffers.config_buf,
2505 0,
2506 ((config_len + shape.bytecode.as_bytes().len()) as u64)
2507 .try_into()
2508 .unwrap(),
2509 )
2510 .unwrap();
2511 writer
2512 .slice(..config_len)
2513 .copy_from_slice(config.as_bytes());
2514 writer
2515 .slice(config_len..)
2516 .copy_from_slice(shape.bytecode.as_bytes());
2517 }
2518
2519 if let Some(var_size) = NonZeroU64::new(shape.vars.size()) {
2521 let mut writer = self
2522 .gpu
2523 .queue
2524 .write_buffer_with(&shape.vars, 0, var_size)
2525 .unwrap();
2526 for (v, i) in shape.shape.inner().vars().iter() {
2527 match v {
2528 Var::X | Var::Y | Var::Z => (),
2529 Var::V(vi) => {
2530 let Some(value) = vars.get(vi) else {
2531 return Err(MissingVar { var: vi });
2532 };
2533 let offset = i * std::mem::size_of::<f32>();
2534 writer
2535 .slice(offset..offset + 4)
2536 .copy_from_slice(value.as_bytes());
2537 }
2538 }
2539 }
2540 }
2541
2542 let mut encoder = self.gpu.device.create_command_encoder(
2544 &wgpu::CommandEncoderDescriptor { label: None },
2545 );
2546
2547 self.reset_ctx.run(&mut encoder, buffers);
2549
2550 let mut compute_pass =
2551 encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
2552 label: None,
2553 timestamp_writes: buffers.timestamps.as_ref().map(
2554 |query_set| wgpu::ComputePassTimestampWrites {
2555 query_set,
2556 beginning_of_pass_write_index: Some(0),
2557 end_of_pass_write_index: Some(1),
2558 },
2559 ),
2560 });
2561
2562 let common_bind_group = buffers.bind_groups.common(self, buffers);
2564 compute_pass.set_bind_group(0, common_bind_group, &[]);
2565 let vars_bind_group = shape.vars_bind_group(self);
2566 compute_pass.set_bind_group(1, vars_bind_group, &[]);
2567
2568 self.root_ctx.run(
2570 self,
2571 buffers,
2572 shape.bytecode.reg_count(),
2573 render_size,
2574 &mut compute_pass,
2575 );
2576 self.repack_ctx
2578 .run(self, buffers, render_size, &mut compute_pass);
2579
2580 let strata_count = u64::from(render_size.depth()).div_ceil(64);
2582 for strata in 0..strata_count {
2583 self.interval_ctx.run(
2584 self,
2585 buffers,
2586 strata,
2587 shape.bytecode.reg_count(),
2588 &mut compute_pass,
2589 );
2590 self.voxel_ctx.run(
2591 self,
2592 buffers,
2593 shape.bytecode.reg_count(),
2594 &mut compute_pass,
2595 );
2596
2597 self.merge_ctx.run(self, buffers, &mut compute_pass);
2599 self.normals_ctx.run(
2600 self,
2601 buffers,
2602 shape.bytecode.reg_count(),
2603 &mut compute_pass,
2604 );
2605
2606 self.clear_ctx.run(self, buffers, &mut compute_pass);
2607 }
2608 drop(compute_pass);
2609
2610 if let Some(image) = out {
2613 image
2614 .grow_to_fit(&self.gpu.device, buffers.image_size)
2615 .expect(
2616 "buffers.image_size should always be \
2617 a valid size for ImageReadBuffer::grow_to_fit",
2618 );
2619 if let Some(timestamps) = &buffers.timestamps {
2620 encoder.resolve_query_set(timestamps, 0..2, &buffers.ts_buf, 0);
2621 encoder.copy_buffer_to_buffer(
2622 &buffers.ts_buf,
2623 0,
2624 image.buffer.data(),
2625 buffers.geom.size_bytes(), buffers.ts_buf.size(),
2627 );
2628 }
2629
2630 encoder.copy_buffer_to_buffer(
2632 buffers.geom.data(),
2633 0,
2634 image.buffer.data(),
2635 0,
2636 buffers.geom.size_bytes(),
2637 );
2638 }
2639
2640 self.gpu.queue.submit(Some(encoder.finish()));
2642 Ok(())
2643 }
2644
2645 #[cfg(not(target_arch = "wasm32"))]
2654 pub fn map_image<'a>(
2655 &self,
2656 image: &'a mut ImageReadBuffer,
2657 ) -> MappedImage<'a> {
2658 let slice = image.buffer.map_async(|_| {});
2659 self.gpu
2660 .device
2661 .poll(wgpu::PollType::wait_indefinitely())
2662 .unwrap();
2663 MappedImage {
2664 image,
2665 slice,
2666 ns_per_tick: if self.has_timestamps {
2667 Some(self.gpu.queue.get_timestamp_period())
2668 } else {
2669 None
2670 },
2671 }
2672 }
2673
2674 #[cfg(any(target_arch = "wasm32", doc))]
2683 pub async fn map_image_async<'a>(
2684 &self,
2685 image: &'a mut ImageReadBuffer,
2686 ) -> MappedImage<'a> {
2687 let (tx, rx) = flume::bounded(0);
2688 let slice = image.buffer.map_async(move |_| tx.send(()).unwrap());
2689 rx.recv_async().await.unwrap();
2690 MappedImage {
2691 image,
2692 slice,
2693 ns_per_tick: if self.has_timestamps {
2694 Some(self.gpu.queue.get_timestamp_period())
2695 } else {
2696 None
2697 },
2698 }
2699 }
2700
2701 pub fn set_buffers_image_size(
2706 &self,
2707 buffers: &mut Buffers,
2708 image_size: VoxelSize,
2709 ) -> Result<(), BuffersError> {
2710 buffers.set_image_size(&self.gpu.device, image_size)
2711 }
2712}
2713
2714pub struct MappedImage<'a> {
2716 image: &'a ImageReadBuffer,
2717 slice: wgpu::BufferSlice<'a>,
2718
2719 ns_per_tick: Option<f32>,
2721}
2722
2723impl Drop for MappedImage<'_> {
2724 fn drop(&mut self) {
2725 self.image.buffer.data().unmap();
2726 }
2727}
2728
2729impl MappedImage<'_> {
2730 pub fn image(&self) -> Image {
2732 let result = <[GeometryPixel]>::ref_from_bytes(
2734 &self.slice.get_mapped_range()[..self.image_bytes()],
2735 )
2736 .unwrap()
2737 .to_owned();
2738 Image::build(result, self.image.image_size).unwrap()
2739 }
2740
2741 pub fn time(&self) -> Option<std::time::Duration> {
2747 self.ns_per_tick.map(|ns_per_tick| {
2748 let slice = self.slice.get_mapped_range();
2749 let ts =
2750 <[u64]>::ref_from_bytes(&slice[self.image_bytes()..]).unwrap();
2751 std::time::Duration::from_nanos(
2752 (ts[1].saturating_sub(ts[0]) as f64 * ns_per_tick as f64)
2753 as u64,
2754 )
2755 })
2756 }
2757
2758 fn image_bytes(&self) -> usize {
2759 (self.image.image_size.width() as usize)
2760 * (self.image.image_size.height() as usize)
2761 * std::mem::size_of::<GeometryPixel>()
2762 }
2763}
2764
2765struct ClearContext {
2766 bind_group_layout: wgpu::BindGroupLayout,
2767 pipeline: wgpu::ComputePipeline,
2768}
2769
2770impl ClearContext {
2771 fn new(
2772 device: &wgpu::Device,
2773 common_bind_group_layout: &wgpu::BindGroupLayout,
2774 vars_bind_group_layout: &wgpu::BindGroupLayout,
2775 ) -> Self {
2776 let bind_group_layout =
2778 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
2779 label: Some("clear bind group layout"),
2780 entries: &[
2781 buffer_rw(0), buffer_rw(1), buffer_rw(2), buffer_rw(3), buffer_rw(4), ],
2787 });
2788
2789 let pipeline_layout =
2791 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
2792 label: Some("clear pipeline layout"),
2793 bind_group_layouts: &[
2794 Some(common_bind_group_layout),
2795 Some(vars_bind_group_layout),
2796 Some(&bind_group_layout),
2797 ],
2798 immediate_size: 0u32,
2799 });
2800
2801 let shader_code = clear_shader();
2803 let shader_module =
2804 device.create_shader_module(wgpu::ShaderModuleDescriptor {
2805 label: Some("clear shader module"),
2806 source: wgpu::ShaderSource::Wgsl(shader_code.into()),
2807 });
2808
2809 let pipeline =
2810 device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
2811 label: Some("clear"),
2812 layout: Some(&pipeline_layout),
2813 module: &shader_module,
2814 entry_point: Some("clear_main"),
2815 compilation_options: Default::default(),
2816 cache: None,
2817 });
2818
2819 Self {
2820 pipeline,
2821 bind_group_layout,
2822 }
2823 }
2824
2825 fn run(
2826 &self,
2827 ctx: &Context,
2828 buffers: &Buffers,
2829 compute_pass: &mut wgpu::ComputePass,
2830 ) {
2831 let bind_group = buffers.bind_groups.clear(ctx, buffers);
2832 compute_pass.set_pipeline(&self.pipeline);
2833 compute_pass.set_bind_group(2, bind_group, &[]);
2834 compute_pass.dispatch_workgroups(1, 1, 1);
2835 }
2836}
2837
2838struct MergeContext {
2839 bind_group_layout: wgpu::BindGroupLayout,
2840 pipeline: wgpu::ComputePipeline,
2841}
2842
2843impl MergeContext {
2844 fn new(
2845 device: &wgpu::Device,
2846 common_bind_group_layout: &wgpu::BindGroupLayout,
2847 vars_bind_group_layout: &wgpu::BindGroupLayout,
2848 ) -> Self {
2849 let shader_code = merge_shader();
2850
2851 let bind_group_layout =
2853 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
2854 label: Some("merge bind group layout"),
2855 entries: &[
2856 buffer_rw(0), buffer_rw(1), buffer_rw(2), buffer_rw(3), ],
2861 });
2862
2863 let pipeline_layout =
2865 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
2866 label: Some("merge pipeline layout"),
2867 bind_group_layouts: &[
2868 Some(common_bind_group_layout),
2869 Some(vars_bind_group_layout),
2870 Some(&bind_group_layout),
2871 ],
2872 immediate_size: 0u32,
2873 });
2874
2875 let shader_module =
2877 device.create_shader_module(wgpu::ShaderModuleDescriptor {
2878 label: Some("merge shader module"),
2879 source: wgpu::ShaderSource::Wgsl(shader_code.into()),
2880 });
2881
2882 let pipeline =
2883 device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
2884 label: Some("merge"),
2885 layout: Some(&pipeline_layout),
2886 module: &shader_module,
2887 entry_point: Some("merge_main"),
2888 compilation_options: Default::default(),
2889 cache: None,
2890 });
2891
2892 Self {
2893 pipeline,
2894 bind_group_layout,
2895 }
2896 }
2897
2898 fn run(
2899 &self,
2900 ctx: &Context,
2901 buffers: &Buffers,
2902 compute_pass: &mut wgpu::ComputePass,
2903 ) {
2904 let render_size = buffers.render_size();
2905 let bind_group = buffers.bind_groups.merge(ctx, buffers);
2906 compute_pass.set_pipeline(&self.pipeline);
2907 compute_pass.set_bind_group(2, bind_group, &[]);
2908 compute_pass.dispatch_workgroups(
2909 render_size.width().div_ceil(8),
2910 render_size.height().div_ceil(8),
2911 1,
2912 );
2913 }
2914}
2915
2916struct ResetContext;
2917
2918impl ResetContext {
2919 fn new() -> Self {
2920 ResetContext
2921 }
2922
2923 fn run(&self, encoder: &mut wgpu::CommandEncoder, buffers: &Buffers) {
2924 encoder.clear_buffer(buffers.tile64.tiles.data(), 12, Some(4));
2926
2927 let strata_size_bytes = buffers.strata_size_bytes();
2931 for s in 0..buffers.render_size().nz() {
2932 encoder.clear_buffer(
2933 buffers.tile64.strata.data(),
2934 u64::from(s) * u64::try_from(strata_size_bytes).unwrap(),
2935 Some(16),
2936 );
2937 }
2938
2939 buffers.tile64.zmin.clear(encoder);
2941 buffers.tile64.zmax.clear(encoder);
2942 buffers.tile16.zmin.clear(encoder);
2943 buffers.tile4.zmin.clear(encoder);
2944 buffers.voxels.clear(encoder);
2945 buffers.geom.clear(encoder);
2946
2947 buffers.tile_tapes.clear(encoder);
2949
2950 }
2952}
2953
2954#[cfg(test)]
2955mod test {
2956 use super::*;
2957 use heck::ToShoutySnakeCase;
2958
2959 #[test]
2960 fn shader_has_all_ops() {
2961 for (op, _) in fidget_bytecode::iter_ops() {
2962 let op = format!("OP_{}", op.to_shouty_snake_case());
2963 assert!(
2964 TAPE_INTERPRETER.contains(&op),
2965 "tape interpreter is missing {op}"
2966 );
2967 assert!(
2968 TAPE_SIMPLIFY.contains(&op),
2969 "tape simplification is missing {op}"
2970 );
2971 }
2972 }
2973
2974 #[test]
2975 fn compile_shaders() {
2976 for (src, desc) in [
2977 (interval_root_shader(16), "interval root"),
2978 (interval_tiles_shader(16), "interval tiles"),
2979 (voxel_tiles_shader(16), "voxel tiles"),
2980 (normals_shader(16), "normals tiles"),
2981 (repack_shader(), "repack"),
2982 (sort_shader(), "sort"),
2983 (merge_shader(), "merge"),
2984 (clear_shader(), "clear"),
2985 ] {
2986 crate::compile_shader(&src, desc);
2987 }
2988 }
2989}