1use crate::{
100 CopyVarsChanged, CopyVarsError, Gpu, RegPipeline, RenderShape,
101 TAPE_DATA_CAPACITY, TapeWord,
102 buf::{
103 BufferSizeError, BufferType, FlexBuffer, ReadBuffer, buffer_ro,
104 buffer_ro_dyn, buffer_rw,
105 },
106 shaders, tag,
107};
108use fidget_core::{
109 render::{ImageSize, VoxelSize},
110 shape::{MissingVar, ShapeVars},
111};
112use fidget_raster::voxel::{GeometryPixel, Image};
113use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
114
115pub mod effects;
116
117pub use fidget_raster::voxel::{RenderConfig, RenderSize};
118
119const COMMON_SHADER: &str = include_str!("shaders/common.wgsl");
120const INTERVAL_INPUT: &str = include_str!("shaders/interval_input.wgsl");
121const TRANSFORM_INPUT: &str = include_str!("shaders/transform_input.wgsl");
122const VOXEL_TILES_SHADER: &str = include_str!("shaders/voxel_tiles.wgsl");
123const INTERVAL_TILES_SHADER: &str = include_str!("shaders/interval_tiles.wgsl");
124const REPACK_SHADER: &str = include_str!("shaders/repack.wgsl");
125const SORT_SHADER: &str = include_str!("shaders/sort.wgsl");
126const INTERVAL_ROOT_SHADER: &str = include_str!("shaders/interval_root.wgsl");
127const CLEAR_SHADER: &str = include_str!("shaders/clear.wgsl");
128const MERGE_SHADER: &str = include_str!("shaders/merge.wgsl");
129const NORMALS_SHADER: &str = include_str!("shaders/normals.wgsl");
130
131#[derive(Debug, thiserror::Error)]
133#[error("failed to resize `{buf}` tile buffer")]
134pub struct TileBuffersError {
135 pub buf: TileBufferName,
137 #[source]
139 pub err: BufferSizeError,
140}
141
142#[derive(Debug)]
146#[expect(missing_docs)]
147pub enum TileBufferName {
148 Tiles,
149 Sorted,
150 Zmin,
151}
152
153impl std::fmt::Display for TileBufferName {
154 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 let s = match self {
156 TileBufferName::Tiles => "tiles",
157 TileBufferName::Sorted => "sorted",
158 TileBufferName::Zmin => "zmin",
159 };
160 s.fmt(f)
161 }
162}
163
164#[derive(Debug, thiserror::Error)]
166#[error("failed to resize `{buf}` root tile buffer")]
167pub struct RootTileBuffersError {
168 pub buf: RootTileBufferName,
170 #[source]
172 pub err: BufferSizeError,
173}
174
175#[derive(Debug)]
177#[expect(missing_docs)]
178pub enum RootTileBufferName {
179 Tiles,
180 Strata,
181 Zmin,
182 Zmax,
183}
184
185impl std::fmt::Display for RootTileBufferName {
186 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187 let s = match self {
188 RootTileBufferName::Tiles => "tiles",
189 RootTileBufferName::Strata => "strata",
190 RootTileBufferName::Zmin => "zmin",
191 RootTileBufferName::Zmax => "zmax",
192 };
193 s.fmt(f)
194 }
195}
196
197#[derive(Debug)]
199#[expect(missing_docs)]
200pub enum BufferName {
201 Tile64(RootTileBufferName),
203 Tile16(TileBufferName),
205 Tile4(TileBufferName),
207 TileTapes,
208 Voxels,
209 Heightmap,
210 Geom,
211 Image,
212 Vars,
213}
214
215impl std::fmt::Display for BufferName {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 match self {
218 BufferName::Tile64(buf) => write!(f, "`{buf}` tile64"),
219 BufferName::Tile16(buf) => write!(f, "`{buf}` tile16"),
220 BufferName::Tile4(buf) => write!(f, "`{buf}` tile4"),
221 BufferName::TileTapes => write!(f, "`tile tapes`"),
222 BufferName::Voxels => write!(f, "`voxels`"),
223 BufferName::Heightmap => write!(f, "`heightmap`"),
224 BufferName::Geom => write!(f, "`geom`"),
225 BufferName::Image => write!(f, "`image`"),
226 BufferName::Vars => write!(f, "`vars`"),
227 }
228 }
229}
230
231#[derive(Debug, thiserror::Error)]
233#[error("failed to build {buf} buffer")]
234pub struct BufferError {
235 pub buf: BufferName,
237 #[source]
239 pub err: BufferSizeError,
240}
241
242#[derive(Debug, thiserror::Error)]
244pub enum SubmitError {
245 #[error(transparent)]
247 MissingVar(#[from] MissingVar),
248 #[error(transparent)]
250 Buffer(#[from] BufferError),
251}
252
253impl From<CopyVarsError> for SubmitError {
254 fn from(value: CopyVarsError) -> Self {
255 match value {
256 CopyVarsError::MissingVar(v) => Self::MissingVar(v),
257 CopyVarsError::BufferSize(err) => Self::Buffer(BufferError {
258 buf: BufferName::Vars,
259 err,
260 }),
261 }
262 }
263}
264
265#[derive(Debug, IntoBytes, Immutable, FromBytes, KnownLayout)]
272#[cfg_attr(test, derive(facet::Facet))]
273#[repr(C)]
274struct Config {
275 mat: [f32; 16],
277
278 axes: [u32; 3],
282
283 tape_data_offset: u32,
285
286 render_size: [u32; 3],
288
289 tape_data_capacity: u32,
291
292 image_size: [u32; 3],
294
295 root_tape_len: u32,
297 }
299
300#[derive(Copy, Clone, Debug)]
305struct TileRenderSize(VoxelSize);
306
307impl From<VoxelSize> for TileRenderSize {
308 fn from(image_size: VoxelSize) -> Self {
309 let nx = image_size.width().div_ceil(64);
310 let ny = image_size.height().div_ceil(64);
311 let nz = image_size.depth().div_ceil(64);
312 Self(VoxelSize::new(nx, ny, nz))
313 }
314}
315
316impl TileRenderSize {
317 fn nx(&self) -> u32 {
319 self.0.width()
320 }
321
322 fn ny(&self) -> u32 {
324 self.0.height()
325 }
326
327 fn nz(&self) -> u32 {
329 self.0.depth()
330 }
331
332 fn width(&self) -> u32 {
334 self.0.width() * 64
335 }
336
337 fn height(&self) -> u32 {
339 self.0.height() * 64
340 }
341
342 fn depth(&self) -> u32 {
344 self.0.depth() * 64
345 }
346}
347
348fn interval_root_shader(reg_count: u8) -> String {
350 let mut shader_code = shaders::opcode_constants();
351 shader_code += &format!("const REG_COUNT: u32 = {reg_count};");
352 shader_code += COMMON_SHADER;
353 shader_code += INTERVAL_ROOT_SHADER;
354 shader_code += INTERVAL_INPUT;
355 shader_code += TRANSFORM_INPUT;
356 shader_code += shaders::INTERVAL_OPS;
357 shader_code += shaders::COMMON;
358 shader_code += shaders::TAPE_INTERPRETER;
359 shader_code += shaders::STACK;
360 shader_code += shaders::TAPE_SIMPLIFY;
361 shader_code
362}
363
364fn repack_shader() -> String {
366 let mut shader_code = String::new();
367 shader_code += REPACK_SHADER;
368 shader_code += COMMON_SHADER;
369 shader_code += shaders::COMMON;
370 shader_code
371}
372
373fn sort_shader() -> String {
375 let mut shader_code = String::new();
376 shader_code += SORT_SHADER;
377 shader_code += COMMON_SHADER;
378 shader_code += shaders::COMMON;
379 shader_code
380}
381
382fn interval_tiles_shader(reg_count: u8) -> String {
384 let mut shader_code = shaders::opcode_constants();
385 shader_code += &format!("const REG_COUNT: u32 = {reg_count};");
386 shader_code += INTERVAL_TILES_SHADER;
387 shader_code += INTERVAL_INPUT;
388 shader_code += TRANSFORM_INPUT;
389 shader_code += COMMON_SHADER;
390 shader_code += shaders::INTERVAL_OPS;
391 shader_code += shaders::COMMON;
392 shader_code += shaders::TAPE_INTERPRETER;
393 shader_code += shaders::STACK;
394 shader_code += shaders::TAPE_SIMPLIFY;
395 shader_code
396}
397
398fn voxel_tiles_shader(reg_count: u8) -> String {
400 let mut shader_code = shaders::opcode_constants();
401 shader_code += &format!("const REG_COUNT: u32 = {reg_count};");
402 shader_code += VOXEL_TILES_SHADER;
403 shader_code += TRANSFORM_INPUT;
404 shader_code += COMMON_SHADER;
405 shader_code += shaders::FLOAT_OPS;
406 shader_code += shaders::COMMON;
407 shader_code += shaders::TAPE_INTERPRETER;
408 shader_code += shaders::DUMMY_STACK;
409 shader_code
410}
411
412fn normals_shader(reg_count: u8) -> String {
414 let mut shader_code = shaders::opcode_constants();
415 shader_code += &format!("const REG_COUNT: u32 = {reg_count};");
416 shader_code += NORMALS_SHADER;
417 shader_code += TRANSFORM_INPUT;
418 shader_code += COMMON_SHADER;
419 shader_code += shaders::GRAD_OPS;
420 shader_code += shaders::COMMON;
421 shader_code += shaders::TAPE_INTERPRETER;
422 shader_code += shaders::DUMMY_STACK;
423 shader_code
424}
425
426fn merge_shader() -> String {
428 MERGE_SHADER.to_owned() + COMMON_SHADER + shaders::COMMON
429}
430
431fn clear_shader() -> String {
433 CLEAR_SHADER.to_owned() + COMMON_SHADER + shaders::COMMON
434}
435
436struct RootContext {
440 root_pipeline: RegPipeline,
442
443 bind_group_layout: wgpu::BindGroupLayout,
445}
446
447fn strata_size_bytes(render_size: TileRenderSize) -> usize {
451 let nx = usize::try_from(render_size.nx()).unwrap();
452 let ny = usize::try_from(render_size.ny()).unwrap();
453 ((nx * ny + 4) * std::mem::size_of::<u32>()).next_multiple_of(256)
455}
456
457impl RootContext {
458 fn new(
459 device: &wgpu::Device,
460 common_bind_group_layout: &wgpu::BindGroupLayout,
461 ) -> Self {
462 let bind_group_layout =
464 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
465 label: Some("root"),
466 entries: &[
467 buffer_rw(0), buffer_rw(1), ],
470 });
471
472 let pipeline_layout =
473 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
474 label: Some("root pipeline"),
475 bind_group_layouts: &[
476 Some(common_bind_group_layout),
477 Some(&bind_group_layout),
478 ],
479 immediate_size: 0u32,
480 });
481 let device_ = device.clone();
482 let root_pipeline = RegPipeline::build(Box::new(move |reg_count| {
483 let shader_code = interval_root_shader(reg_count);
484 let shader_module =
485 device_.create_shader_module(wgpu::ShaderModuleDescriptor {
486 label: Some("interval root"),
487 source: wgpu::ShaderSource::Wgsl(shader_code.into()),
488 });
489 device_.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
490 label: Some(&format!("interval root ({reg_count})")),
491 layout: Some(&pipeline_layout),
492 module: &shader_module,
493 entry_point: Some("interval_root_main"),
494 compilation_options: Default::default(),
495 cache: None,
496 })
497 }));
498
499 Self {
500 bind_group_layout,
501 root_pipeline,
502 }
503 }
504
505 fn run(
506 &self,
507 ctx: &Context,
508 workspace: &Workspace,
509 reg_count: u8,
510 render_size: TileRenderSize,
511 compute_pass: &mut wgpu::ComputePass,
512 ) {
513 let bind_group = workspace.bind_groups.root(ctx, workspace);
514 compute_pass.set_pipeline(self.root_pipeline.get(reg_count));
515 compute_pass.set_bind_group(1, bind_group, &[]);
516
517 let nx = render_size.nx().div_ceil(4);
519 let ny = render_size.ny().div_ceil(4);
520 let nz = render_size.nz().div_ceil(4);
521 compute_pass.dispatch_workgroups(nx, ny, nz);
522 }
523}
524
525struct RepackContext {
527 repack_pipeline: wgpu::ComputePipeline,
529
530 bind_group_layout: wgpu::BindGroupLayout,
532}
533
534impl RepackContext {
535 fn new(
536 device: &wgpu::Device,
537 common_bind_group_layout: &wgpu::BindGroupLayout,
538 ) -> Self {
539 let bind_group_layout =
541 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
542 label: Some("repack"),
543 entries: &[
544 buffer_ro(0), buffer_ro(1), buffer_rw(2), ],
548 });
549
550 let repack_pipeline = {
551 let shader_code = repack_shader();
552 let pipeline_layout = device.create_pipeline_layout(
553 &wgpu::PipelineLayoutDescriptor {
554 label: Some("repack"),
555 bind_group_layouts: &[
556 Some(common_bind_group_layout),
557 Some(&bind_group_layout),
558 ],
559 immediate_size: 0u32,
560 },
561 );
562 let shader_module =
563 device.create_shader_module(wgpu::ShaderModuleDescriptor {
564 label: Some("repack"),
565 source: wgpu::ShaderSource::Wgsl(shader_code.into()),
566 });
567 device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
568 label: Some("repack"),
569 layout: Some(&pipeline_layout),
570 module: &shader_module,
571 entry_point: Some("repack_main"),
572 compilation_options: Default::default(),
573 cache: None,
574 })
575 };
576
577 Self {
578 bind_group_layout,
579 repack_pipeline,
580 }
581 }
582
583 fn run(
584 &self,
585 ctx: &Context,
586 workspace: &Workspace,
587 render_size: TileRenderSize,
588 compute_pass: &mut wgpu::ComputePass,
589 ) {
590 let bind_group = workspace.bind_groups.repack(ctx, workspace);
591
592 compute_pass.set_pipeline(&self.repack_pipeline);
593 compute_pass.set_bind_group(1, bind_group, &[]);
594
595 let nx = render_size.nx().div_ceil(64);
600 let ny = render_size.ny();
601 let nz = render_size.nz();
602 compute_pass.dispatch_workgroups(nx, ny, nz);
603 }
604}
605
606struct IntervalContext {
609 interval64_pipeline: RegPipeline,
611
612 sort16_pipeline: wgpu::ComputePipeline,
614
615 interval16_pipeline: RegPipeline,
617
618 sort4_pipeline: wgpu::ComputePipeline,
620
621 interval_bind_group_layout: wgpu::BindGroupLayout,
623
624 sort_bind_group_layout: wgpu::BindGroupLayout,
626}
627
628impl IntervalContext {
629 fn new(
630 device: &wgpu::Device,
631 common_bind_group_layout: &wgpu::BindGroupLayout,
632 ) -> Self {
633 let interval_bind_group_layout =
635 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
636 label: Some("interval"),
637 entries: &[
638 buffer_ro_dyn(0), buffer_ro(1), buffer_rw(2), buffer_rw(3), buffer_rw(4), ],
644 });
645
646 let interval_pipeline_layout =
647 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
648 label: Some("interval pipeline layout"),
649 bind_group_layouts: &[
650 Some(common_bind_group_layout),
651 Some(&interval_bind_group_layout),
652 ],
653 immediate_size: 0u32,
654 });
655
656 let device_ = device.clone();
657 let interval_pipeline_layout_ = interval_pipeline_layout.clone();
658 let interval64_pipeline =
659 RegPipeline::build(Box::new(move |reg_count| {
660 let shader_code = interval_tiles_shader(reg_count);
661 let shader_module = unsafe {
663 device_.create_shader_module_trusted(
664 wgpu::ShaderModuleDescriptor {
665 label: Some(&format!(
666 "interval64 tiles shader ({reg_count})"
667 )),
668 source: wgpu::ShaderSource::Wgsl(
669 shader_code.into(),
670 ),
671 },
672 wgpu::ShaderRuntimeChecks::unchecked(),
673 )
674 };
675 device_.create_compute_pipeline(
676 &wgpu::ComputePipelineDescriptor {
677 label: Some(&format!("interval64 ({reg_count})")),
678 layout: Some(&interval_pipeline_layout_),
679 module: &shader_module,
680 entry_point: Some("interval_tile_main"),
681 compilation_options: wgpu::PipelineCompilationOptions {
682 constants: &[
683 ("TILE_SIZE", 64.0),
684 ("SUBTILE_SIZE", 16.0),
685 ],
686 ..Default::default()
687 },
688 cache: None,
689 },
690 )
691 }));
692
693 let device_ = device.clone();
694 let interval16_pipeline =
695 RegPipeline::build(Box::new(move |reg_count| {
696 let shader_code = interval_tiles_shader(reg_count);
697 let shader_module = unsafe {
699 device_.create_shader_module_trusted(
700 wgpu::ShaderModuleDescriptor {
701 label: Some(&format!(
702 "interval16 tiles shader ({reg_count})"
703 )),
704 source: wgpu::ShaderSource::Wgsl(
705 shader_code.into(),
706 ),
707 },
708 wgpu::ShaderRuntimeChecks::unchecked(),
709 )
710 };
711 device_.create_compute_pipeline(
712 &wgpu::ComputePipelineDescriptor {
713 label: Some(&format!("interval16 ({reg_count})")),
714 layout: Some(&interval_pipeline_layout),
715 module: &shader_module,
716 entry_point: Some("interval_tile_main"),
717 compilation_options: wgpu::PipelineCompilationOptions {
718 constants: &[
719 ("TILE_SIZE", 16.0),
720 ("SUBTILE_SIZE", 4.0),
721 ],
722 ..Default::default()
723 },
724 cache: None,
725 },
726 )
727 }));
728
729 let sort_bind_group_layout =
730 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
731 label: Some("sort bind group layout"),
732 entries: &[
733 buffer_ro(0), buffer_rw(1), buffer_rw(2), ],
737 });
738 let sort_pipeline_layout =
739 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
740 label: Some("sort pipeline layout"),
741 bind_group_layouts: &[
742 Some(common_bind_group_layout),
743 Some(&sort_bind_group_layout),
744 ],
745 immediate_size: 0u32,
746 });
747
748 let shader_code = sort_shader();
749 let shader_module = unsafe {
751 device.create_shader_module_trusted(
752 wgpu::ShaderModuleDescriptor {
753 label: Some("sort shader module"),
754 source: wgpu::ShaderSource::Wgsl(shader_code.into()),
755 },
756 wgpu::ShaderRuntimeChecks::unchecked(),
757 )
758 };
759 let sort16_pipeline =
760 device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
761 label: Some("sort16"),
762 layout: Some(&sort_pipeline_layout),
763 module: &shader_module,
764 entry_point: Some("sort_main"),
765 compilation_options: wgpu::PipelineCompilationOptions {
766 constants: &[("SUBTILE_SIZE", 16.0)],
767 ..Default::default()
768 },
769 cache: None,
770 });
771 let sort4_pipeline =
772 device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
773 label: Some("sort4"),
774 layout: Some(&sort_pipeline_layout),
775 module: &shader_module,
776 entry_point: Some("sort_main"),
777 compilation_options: wgpu::PipelineCompilationOptions {
778 constants: &[("SUBTILE_SIZE", 4.0)],
779 ..Default::default()
780 },
781 cache: None,
782 });
783
784 Self {
785 interval_bind_group_layout,
786 sort_bind_group_layout,
787 interval64_pipeline,
788 sort16_pipeline,
789 interval16_pipeline,
790 sort4_pipeline,
791 }
792 }
793
794 fn run(
795 &self,
796 ctx: &Context,
797 workspace: &Workspace,
798 strata: u64,
799 reg_count: u8,
800 compute_pass: &mut wgpu::ComputePass,
801 ) {
802 let strata_bytes =
803 u64::try_from(workspace.strata_size_bytes()).unwrap();
804 let offset_bytes = strata * strata_bytes;
805 let bind_group16 = workspace.bind_groups.interval16(ctx, workspace);
806 compute_pass.set_pipeline(self.interval64_pipeline.get(reg_count));
807 compute_pass.set_bind_group(
808 1,
809 bind_group16,
810 &[u32::try_from(offset_bytes).unwrap()],
811 );
812 compute_pass.dispatch_workgroups_indirect(
813 workspace.tile64.strata.data(),
814 offset_bytes,
815 );
816
817 let bind_group_sort16 = workspace.bind_groups.sort16(ctx, workspace);
818 compute_pass.set_pipeline(&self.sort16_pipeline);
819 compute_pass.set_bind_group(1, bind_group_sort16, &[]);
820 compute_pass
821 .dispatch_workgroups_indirect(workspace.tile16.tiles.data(), 0);
822
823 let bind_group4 = workspace.bind_groups.interval4(ctx, workspace);
824 compute_pass.set_pipeline(self.interval16_pipeline.get(reg_count));
825 compute_pass.set_bind_group(1, bind_group4, &[0]);
826 compute_pass
827 .dispatch_workgroups_indirect(workspace.tile16.sorted.data(), 0);
828
829 let bind_group_sort4 = workspace.bind_groups.sort4(ctx, workspace);
830 compute_pass.set_pipeline(&self.sort4_pipeline);
831 compute_pass.set_bind_group(1, bind_group_sort4, &[]);
832 compute_pass
833 .dispatch_workgroups_indirect(workspace.tile4.tiles.data(), 0);
834 }
835}
836
837struct VoxelContext {
840 bind_group_layout: wgpu::BindGroupLayout,
842
843 voxel_pipeline: RegPipeline,
845}
846
847impl VoxelContext {
848 fn new(
849 device: &wgpu::Device,
850 common_bind_group_layout: &wgpu::BindGroupLayout,
851 ) -> Self {
852 let bind_group_layout =
854 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
855 label: Some("voxel bind group layout"),
856 entries: &[
857 buffer_ro(0), buffer_ro(1), buffer_rw(2), ],
861 });
862
863 let pipeline_layout =
864 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
865 label: Some("voxel pipeline layout"),
866 bind_group_layouts: &[
867 Some(common_bind_group_layout),
868 Some(&bind_group_layout),
869 ],
870 immediate_size: 0u32,
871 });
872 let device_ = device.clone();
873 let voxel_pipeline = RegPipeline::build(Box::new(move |reg_count| {
874 let shader_code = voxel_tiles_shader(reg_count);
875 let shader_module = unsafe {
877 device_.create_shader_module_trusted(
878 wgpu::ShaderModuleDescriptor {
879 label: Some("voxel shader module"),
880 source: wgpu::ShaderSource::Wgsl(shader_code.into()),
881 },
882 wgpu::ShaderRuntimeChecks::unchecked(),
883 )
884 };
885 device_.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
886 label: Some(&format!("voxels ({reg_count})")),
887 layout: Some(&pipeline_layout),
888 module: &shader_module,
889 entry_point: Some("voxel_ray_main"),
890 compilation_options: Default::default(),
891 cache: None,
892 })
893 }));
894
895 Self {
896 bind_group_layout,
897 voxel_pipeline,
898 }
899 }
900
901 fn run(
902 &self,
903 ctx: &Context,
904 workspace: &Workspace,
905 reg_count: u8,
906 compute_pass: &mut wgpu::ComputePass,
907 ) {
908 let bind_group = workspace.bind_groups.voxel(ctx, workspace);
909 compute_pass.set_pipeline(self.voxel_pipeline.get(reg_count));
910 compute_pass.set_bind_group(1, bind_group, &[]);
911
912 compute_pass
915 .dispatch_workgroups_indirect(workspace.tile4.sorted.data(), 0);
916 }
917}
918
919struct NormalsContext {
920 bind_group_layout: wgpu::BindGroupLayout,
922
923 normals_pipeline: RegPipeline,
925}
926
927impl NormalsContext {
928 fn new(
929 device: &wgpu::Device,
930 common_bind_group_layout: &wgpu::BindGroupLayout,
931 ) -> Self {
932 let bind_group_layout =
934 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
935 label: Some("normals bind group layout"),
936 entries: &[
937 buffer_ro(0), buffer_rw(1), ],
940 });
941
942 let pipeline_layout =
943 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
944 label: Some("normals pipeline"),
945 bind_group_layouts: &[
946 Some(common_bind_group_layout),
947 Some(&bind_group_layout),
948 ],
949 immediate_size: 0u32,
950 });
951 let device_ = device.clone();
952 let normals_pipeline = RegPipeline::build(Box::new(move |reg_count| {
953 let shader_code = normals_shader(reg_count);
954 let shader_module =
955 device_.create_shader_module(wgpu::ShaderModuleDescriptor {
956 label: Some("normals shader module"),
957 source: wgpu::ShaderSource::Wgsl(shader_code.into()),
958 });
959 device_.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
960 label: Some(&format!("normals ({reg_count})")),
961 layout: Some(&pipeline_layout),
962 module: &shader_module,
963 entry_point: Some("normals_main"),
964 compilation_options: Default::default(),
965 cache: None,
966 })
967 }));
968
969 Self {
970 bind_group_layout,
971 normals_pipeline,
972 }
973 }
974
975 fn run(
976 &self,
977 ctx: &Context,
978 workspace: &Workspace,
979 reg_count: u8,
980 compute_pass: &mut wgpu::ComputePass,
981 ) {
982 let bind_group = workspace.bind_groups.normals(ctx, workspace);
983 compute_pass.set_pipeline(self.normals_pipeline.get(reg_count));
984 compute_pass.set_bind_group(1, bind_group, &[]);
985
986 compute_pass.dispatch_workgroups(
987 workspace.image_size.width().div_ceil(8),
988 workspace.image_size.height().div_ceil(8),
989 1,
990 );
991 }
992}
993
994pub struct Context {
998 gpu: Gpu,
999
1000 common_bind_group_layout: wgpu::BindGroupLayout,
1002
1003 root_ctx: RootContext,
1004 repack_ctx: RepackContext,
1005 interval_ctx: IntervalContext,
1006 voxel_ctx: VoxelContext,
1007 normals_ctx: NormalsContext,
1008 merge_ctx: MergeContext,
1009 reset_ctx: ResetContext,
1010 clear_ctx: ClearContext,
1011}
1012
1013tag!(TilesBufferTag, u32, usize, STORAGE | INDIRECT);
1014tag!(SortedBufferTag, u32, usize, STORAGE | INDIRECT);
1015tag!(ZminBufferTag, u32, ImageSize, STORAGE | COPY_DST);
1016
1017struct TileBuffers<const N: u64> {
1018 tiles: FlexBuffer<TilesBufferTag>,
1020 sorted: FlexBuffer<SortedBufferTag>,
1022 zmin: FlexBuffer<ZminBufferTag>,
1024}
1025
1026impl<const N: u64> TileBuffers<N> {
1027 fn new(
1029 device: &wgpu::Device,
1030 render_size: TileRenderSize,
1031 ) -> Result<Self, TileBuffersError> {
1032 let tile_buf_size = Self::tile_buf_size(render_size);
1033 let tiles =
1034 FlexBuffer::new(device, format!("active_tile{N}"), tile_buf_size)
1035 .map_err(|err| TileBuffersError {
1036 buf: TileBufferName::Tiles,
1037 err,
1038 })?;
1039 let sorted =
1040 FlexBuffer::new(device, format!("sorted_tile{N}"), tile_buf_size)
1041 .map_err(|err| TileBuffersError {
1042 buf: TileBufferName::Sorted,
1043 err,
1044 })?;
1045 let zmin = FlexBuffer::new(
1046 device,
1047 format!("tile{N}_zmin"),
1048 Self::zmin_buf_size(render_size),
1049 )
1050 .map_err(|err| TileBuffersError {
1051 buf: TileBufferName::Zmin,
1052 err,
1053 })?;
1054
1055 Ok(Self {
1056 tiles,
1057 sorted,
1058 zmin,
1059 })
1060 }
1061
1062 fn tile_buf_size(render_size: TileRenderSize) -> usize {
1063 let n = usize::try_from(N).unwrap();
1064 let nx = usize::try_from(render_size.width()).unwrap() / n;
1065 let ny = usize::try_from(render_size.height()).unwrap() / n;
1066 let nz = 64 / n;
1067 4 + nx * ny * nz
1070 }
1071
1072 fn zmin_buf_size(render_size: TileRenderSize) -> ImageSize {
1073 ImageSize::new(
1074 render_size.width() / u32::try_from(N).unwrap(),
1075 render_size.height() / u32::try_from(N).unwrap(),
1076 )
1077 }
1078
1079 fn grow_to_fit(
1080 &mut self,
1081 device: &wgpu::Device,
1082 render_size: TileRenderSize,
1083 ) -> Result<(), TileBuffersError> {
1084 let TileBuffers {
1085 tiles,
1086 sorted,
1087 zmin,
1088 } = self;
1089 let tile_buf_size = Self::tile_buf_size(render_size);
1090 tiles.grow_to_fit(device, tile_buf_size).map_err(|err| {
1091 TileBuffersError {
1092 buf: TileBufferName::Tiles,
1093 err,
1094 }
1095 })?;
1096 sorted.grow_to_fit(device, tile_buf_size).map_err(|err| {
1097 TileBuffersError {
1098 buf: TileBufferName::Sorted,
1099 err,
1100 }
1101 })?;
1102 zmin.grow_to_fit(device, Self::zmin_buf_size(render_size))
1103 .map_err(|err| TileBuffersError {
1104 buf: TileBufferName::Zmin,
1105 err,
1106 })?;
1107
1108 Ok(())
1109 }
1110
1111 pub fn size(&self) -> u64 {
1115 let TileBuffers {
1117 tiles,
1118 sorted,
1119 zmin,
1120 } = self;
1121 tiles.size_bytes() + sorted.size_bytes() + zmin.size_bytes()
1122 }
1123
1124 pub fn capacity(&self) -> u64 {
1126 let TileBuffers {
1128 tiles,
1129 sorted,
1130 zmin,
1131 } = self;
1132 tiles.capacity() + sorted.capacity() + zmin.capacity()
1133 }
1134}
1135
1136tag!(RootTilesBufferTag, u32, usize, STORAGE | COPY_DST);
1137tag!(
1138 RootStrataBufferTag,
1139 u8,
1140 usize,
1141 STORAGE | INDIRECT | COPY_DST
1142);
1143tag!(RootZminBufferTag, u32, ImageSize, STORAGE | COPY_DST);
1144tag!(RootZmaxBufferTag, u32, ImageSize, STORAGE | COPY_DST);
1145
1146struct RootTileBuffers {
1148 tiles: FlexBuffer<RootTilesBufferTag>,
1150 strata: FlexBuffer<RootStrataBufferTag>,
1152 zmin: FlexBuffer<RootZminBufferTag>,
1153 zmax: FlexBuffer<RootZmaxBufferTag>,
1154}
1155
1156impl RootTileBuffers {
1157 fn new(
1159 device: &wgpu::Device,
1160 render_size: TileRenderSize,
1161 ) -> Result<Self, RootTileBuffersError> {
1162 const N: usize = 64;
1164
1165 let tiles = FlexBuffer::new(
1167 device,
1168 format!("tiles_out{N}"),
1169 Self::tiles_buf_size(render_size),
1170 )
1171 .map_err(|err| RootTileBuffersError {
1172 buf: RootTileBufferName::Tiles,
1173 err,
1174 })?;
1175
1176 let strata = FlexBuffer::new(
1177 device,
1178 format!("strata_tile{N}"),
1179 Self::strata_buf_size(render_size),
1180 )
1181 .map_err(|err| RootTileBuffersError {
1182 buf: RootTileBufferName::Strata,
1183 err,
1184 })?;
1185
1186 let z_buf_size = Self::z_buf_size(render_size);
1187 let zmin = FlexBuffer::new(device, format!("tile{N}_zmin"), z_buf_size)
1188 .map_err(|err| RootTileBuffersError {
1189 buf: RootTileBufferName::Zmin,
1190 err,
1191 })?;
1192 let zmax = FlexBuffer::new(device, format!("tile{N}_zmax"), z_buf_size)
1193 .map_err(|err| RootTileBuffersError {
1194 buf: RootTileBufferName::Zmax,
1195 err,
1196 })?;
1197 Ok(Self {
1198 tiles,
1199 strata,
1200 zmin,
1201 zmax,
1202 })
1203 }
1204
1205 fn tiles_buf_size(render_size: TileRenderSize) -> usize {
1206 let nx = usize::try_from(render_size.nx()).unwrap();
1207 let ny = usize::try_from(render_size.ny()).unwrap();
1208 let nz = usize::try_from(render_size.nz()).unwrap();
1209 4 + nx * ny * nz
1212 }
1213
1214 fn strata_buf_size(render_size: TileRenderSize) -> usize {
1215 let nz = usize::try_from(render_size.nz()).unwrap();
1216 let strata_size = strata_size_bytes(render_size);
1217 strata_size * nz
1218 }
1219
1220 fn z_buf_size(render_size: TileRenderSize) -> ImageSize {
1221 ImageSize::new(render_size.nx(), render_size.ny())
1222 }
1223
1224 fn grow_to_fit(
1226 &mut self,
1227 device: &wgpu::Device,
1228 render_size: TileRenderSize,
1229 ) -> Result<(), RootTileBuffersError> {
1230 let RootTileBuffers {
1232 tiles,
1233 strata,
1234 zmin,
1235 zmax,
1236 } = self;
1237 tiles
1238 .grow_to_fit(device, Self::tiles_buf_size(render_size))
1239 .map_err(|err| RootTileBuffersError {
1240 buf: RootTileBufferName::Tiles,
1241 err,
1242 })?;
1243 strata
1244 .grow_to_fit(device, Self::strata_buf_size(render_size))
1245 .map_err(|err| RootTileBuffersError {
1246 buf: RootTileBufferName::Strata,
1247 err,
1248 })?;
1249
1250 let z_buf_size = Self::z_buf_size(render_size);
1251 zmin.grow_to_fit(device, z_buf_size).map_err(|err| {
1252 RootTileBuffersError {
1253 buf: RootTileBufferName::Zmin,
1254 err,
1255 }
1256 })?;
1257 zmax.grow_to_fit(device, z_buf_size).map_err(|err| {
1258 RootTileBuffersError {
1259 buf: RootTileBufferName::Zmax,
1260 err,
1261 }
1262 })?;
1263
1264 Ok(())
1265 }
1266
1267 pub fn size(&self) -> u64 {
1269 let RootTileBuffers {
1271 tiles,
1272 strata,
1273 zmin,
1274 zmax,
1275 } = self;
1276 tiles.size_bytes()
1277 + strata.size_bytes()
1278 + zmin.size_bytes()
1279 + zmax.size_bytes()
1280 }
1281
1282 pub fn capacity(&self) -> u64 {
1284 let RootTileBuffers {
1286 tiles,
1287 strata,
1288 zmin,
1289 zmax,
1290 } = self;
1291 tiles.capacity() + strata.capacity() + zmin.capacity() + zmax.capacity()
1292 }
1293}
1294
1295tag!(TileTapesBufferTag, u32, usize, STORAGE | COPY_DST);
1296tag!(VoxelsBufferTag, u32, VoxelSize, STORAGE | COPY_DST);
1297tag!(pub GeomBufferTag, GeometryPixel, VoxelSize, STORAGE | COPY_SRC | COPY_DST,
1298 "Tag for a on-GPU buffer storing [`GeometryPixel`] values");
1299tag!(pub(crate) VarsBufferTag, f32, usize, STORAGE | COPY_DST);
1300
1301pub struct Workspace {
1306 image_size: VoxelSize,
1311
1312 config_buf: wgpu::Buffer,
1314
1315 vars_buf: FlexBuffer<VarsBufferTag>,
1317
1318 z_hist_buf: wgpu::Buffer,
1326
1327 tile_tapes: FlexBuffer<TileTapesBufferTag>,
1329
1330 tile64: RootTileBuffers,
1332
1333 tile16: TileBuffers<16>,
1335
1336 tile4: TileBuffers<4>,
1338
1339 voxels: FlexBuffer<VoxelsBufferTag>,
1341
1342 geom: FlexBuffer<GeomBufferTag>,
1346
1347 bind_groups: BindGroups,
1349}
1350
1351#[derive(Default)]
1353struct BindGroups {
1354 common: std::cell::OnceCell<wgpu::BindGroup>,
1355 merge: std::cell::OnceCell<wgpu::BindGroup>,
1356 root: std::cell::OnceCell<wgpu::BindGroup>,
1357 repack: std::cell::OnceCell<wgpu::BindGroup>,
1358 interval16: std::cell::OnceCell<wgpu::BindGroup>,
1359 sort16: std::cell::OnceCell<wgpu::BindGroup>,
1360 interval4: std::cell::OnceCell<wgpu::BindGroup>,
1361 sort4: std::cell::OnceCell<wgpu::BindGroup>,
1362 voxel: std::cell::OnceCell<wgpu::BindGroup>,
1363 normals: std::cell::OnceCell<wgpu::BindGroup>,
1364 clear: std::cell::OnceCell<wgpu::BindGroup>,
1365}
1366
1367impl BindGroups {
1368 fn common(&self, ctx: &Context, workspace: &Workspace) -> &wgpu::BindGroup {
1369 self.common.get_or_init(|| {
1370 ctx.gpu
1371 .device
1372 .create_bind_group(&wgpu::BindGroupDescriptor {
1373 label: Some("common bind group"),
1374 layout: &ctx.common_bind_group_layout,
1375 entries: &[
1376 wgpu::BindGroupEntry {
1377 binding: 0,
1378 resource: workspace.config_buf.as_entire_binding(),
1379 },
1380 wgpu::BindGroupEntry {
1381 binding: 1,
1382 resource: workspace.tile_tapes.bind_active(),
1383 },
1384 wgpu::BindGroupEntry {
1385 binding: 2,
1386 resource: workspace.vars_buf.bind_active(),
1387 },
1388 ],
1389 })
1390 })
1391 }
1392
1393 fn clear(&self, ctx: &Context, workspace: &Workspace) -> &wgpu::BindGroup {
1394 self.clear.get_or_init(|| {
1395 ctx.gpu
1396 .device
1397 .create_bind_group(&wgpu::BindGroupDescriptor {
1398 label: Some("clear bind group"),
1399 layout: &ctx.clear_ctx.bind_group_layout,
1400 entries: &[
1401 wgpu::BindGroupEntry {
1402 binding: 0,
1403 resource: workspace
1404 .tile16
1405 .tiles
1406 .data()
1407 .slice(0..16)
1408 .try_into()
1409 .unwrap(),
1410 },
1411 wgpu::BindGroupEntry {
1412 binding: 1,
1413 resource: workspace
1414 .tile16
1415 .sorted
1416 .data()
1417 .slice(0..16)
1418 .try_into()
1419 .unwrap(),
1420 },
1421 wgpu::BindGroupEntry {
1422 binding: 2,
1423 resource: workspace
1424 .tile4
1425 .tiles
1426 .data()
1427 .slice(0..16)
1428 .try_into()
1429 .unwrap(),
1430 },
1431 wgpu::BindGroupEntry {
1432 binding: 3,
1433 resource: workspace
1434 .tile4
1435 .sorted
1436 .data()
1437 .slice(0..16)
1438 .try_into()
1439 .unwrap(),
1440 },
1441 wgpu::BindGroupEntry {
1442 binding: 4,
1443 resource: workspace.z_hist_buf.as_entire_binding(),
1444 },
1445 ],
1446 })
1447 })
1448 }
1449
1450 fn merge(&self, ctx: &Context, workspace: &Workspace) -> &wgpu::BindGroup {
1451 self.merge.get_or_init(|| {
1452 ctx.gpu
1453 .device
1454 .create_bind_group(&wgpu::BindGroupDescriptor {
1455 label: Some("merge bind group"),
1456 layout: &ctx.merge_ctx.bind_group_layout,
1457 entries: &[
1458 wgpu::BindGroupEntry {
1459 binding: 0,
1460 resource: workspace.tile64.zmin.bind_active(),
1461 },
1462 wgpu::BindGroupEntry {
1463 binding: 1,
1464 resource: workspace.tile16.zmin.bind_active(),
1465 },
1466 wgpu::BindGroupEntry {
1467 binding: 2,
1468 resource: workspace.tile4.zmin.bind_active(),
1469 },
1470 wgpu::BindGroupEntry {
1471 binding: 3,
1472 resource: workspace.voxels.bind_active(),
1473 },
1474 ],
1475 })
1476 })
1477 }
1478
1479 fn root(&self, ctx: &Context, workspace: &Workspace) -> &wgpu::BindGroup {
1480 self.root.get_or_init(|| {
1481 ctx.gpu
1482 .device
1483 .create_bind_group(&wgpu::BindGroupDescriptor {
1484 label: Some("interval root bind group"),
1485 layout: &ctx.root_ctx.bind_group_layout,
1486 entries: &[
1487 wgpu::BindGroupEntry {
1488 binding: 0,
1489 resource: workspace.tile64.tiles.bind_active(),
1490 },
1491 wgpu::BindGroupEntry {
1492 binding: 1,
1493 resource: workspace.tile64.zmax.bind_active(),
1494 },
1495 ],
1496 })
1497 })
1498 }
1499
1500 fn repack(&self, ctx: &Context, workspace: &Workspace) -> &wgpu::BindGroup {
1501 self.repack.get_or_init(|| {
1502 ctx.gpu
1503 .device
1504 .create_bind_group(&wgpu::BindGroupDescriptor {
1505 label: Some("repack bind group"),
1506 layout: &ctx.repack_ctx.bind_group_layout,
1507 entries: &[
1508 wgpu::BindGroupEntry {
1509 binding: 0,
1510 resource: workspace.tile64.tiles.bind_active(),
1511 },
1512 wgpu::BindGroupEntry {
1513 binding: 1,
1514 resource: workspace.tile64.zmax.bind_active(),
1515 },
1516 wgpu::BindGroupEntry {
1517 binding: 2,
1518 resource: workspace.tile64.strata.bind_active(),
1519 },
1520 ],
1521 })
1522 })
1523 }
1524
1525 fn interval16(
1526 &self,
1527 ctx: &Context,
1528 workspace: &Workspace,
1529 ) -> &wgpu::BindGroup {
1530 let strata_bytes =
1531 u64::try_from(workspace.strata_size_bytes()).unwrap();
1532 self.interval16.get_or_init(|| {
1533 ctx.gpu
1534 .device
1535 .create_bind_group(&wgpu::BindGroupDescriptor {
1536 label: Some("interval16 bind group"),
1537 layout: &ctx.interval_ctx.interval_bind_group_layout,
1538 entries: &[
1539 wgpu::BindGroupEntry {
1540 binding: 0,
1541 resource: workspace
1542 .tile64
1543 .strata
1544 .data()
1545 .slice(0..strata_bytes) .try_into()
1547 .unwrap(),
1548 },
1549 wgpu::BindGroupEntry {
1550 binding: 1,
1551 resource: workspace.tile64.zmin.bind_active(),
1552 },
1553 wgpu::BindGroupEntry {
1554 binding: 2,
1555 resource: workspace.tile16.tiles.bind_active(),
1556 },
1557 wgpu::BindGroupEntry {
1558 binding: 3,
1559 resource: workspace.tile16.zmin.bind_active(),
1560 },
1561 wgpu::BindGroupEntry {
1562 binding: 4,
1563 resource: workspace
1564 .z_hist_buf
1565 .slice(0..16)
1566 .try_into()
1567 .unwrap(),
1568 },
1569 ],
1570 })
1571 })
1572 }
1573
1574 fn sort16(&self, ctx: &Context, workspace: &Workspace) -> &wgpu::BindGroup {
1575 self.sort16.get_or_init(|| {
1576 Self::sort_bind_group(
1577 ctx,
1578 &workspace.tile16,
1579 workspace.z_hist_buf.slice(0..16).try_into().unwrap(),
1580 )
1581 })
1582 }
1583
1584 fn sort4(&self, ctx: &Context, workspace: &Workspace) -> &wgpu::BindGroup {
1585 self.sort4.get_or_init(|| {
1586 Self::sort_bind_group(
1587 ctx,
1588 &workspace.tile4,
1589 workspace.z_hist_buf.slice(256..320).try_into().unwrap(),
1590 )
1591 })
1592 }
1593
1594 fn sort_bind_group<const N: u64>(
1595 ctx: &Context,
1596 tile_bufs: &TileBuffers<N>,
1597 z_hist: wgpu::BindingResource,
1598 ) -> wgpu::BindGroup {
1599 ctx.gpu
1600 .device
1601 .create_bind_group(&wgpu::BindGroupDescriptor {
1602 label: Some(&format!("sort{N} bind group")),
1603 layout: &ctx.interval_ctx.sort_bind_group_layout,
1604 entries: &[
1605 wgpu::BindGroupEntry {
1606 binding: 0,
1607 resource: tile_bufs.tiles.bind_active(),
1608 },
1609 wgpu::BindGroupEntry {
1610 binding: 1,
1611 resource: z_hist,
1612 },
1613 wgpu::BindGroupEntry {
1614 binding: 2,
1615 resource: tile_bufs.sorted.bind_active(),
1616 },
1617 ],
1618 })
1619 }
1620
1621 fn interval4(
1622 &self,
1623 ctx: &Context,
1624 workspace: &Workspace,
1625 ) -> &wgpu::BindGroup {
1626 self.interval4.get_or_init(|| {
1627 ctx.gpu
1628 .device
1629 .create_bind_group(&wgpu::BindGroupDescriptor {
1630 label: Some("interval4 bind group"),
1631 layout: &ctx.interval_ctx.interval_bind_group_layout,
1632 entries: &[
1633 wgpu::BindGroupEntry {
1634 binding: 0,
1635 resource: workspace.tile16.sorted.bind_active(),
1636 },
1637 wgpu::BindGroupEntry {
1638 binding: 1,
1639 resource: workspace.tile16.zmin.bind_active(),
1640 },
1641 wgpu::BindGroupEntry {
1642 binding: 2,
1643 resource: workspace.tile4.tiles.bind_active(),
1644 },
1645 wgpu::BindGroupEntry {
1646 binding: 3,
1647 resource: workspace.tile4.zmin.bind_active(),
1648 },
1649 wgpu::BindGroupEntry {
1650 binding: 4,
1651 resource: workspace
1652 .z_hist_buf
1653 .slice(256..320)
1654 .try_into()
1655 .unwrap(),
1656 },
1657 ],
1658 })
1659 })
1660 }
1661
1662 fn voxel(&self, ctx: &Context, workspace: &Workspace) -> &wgpu::BindGroup {
1663 self.voxel.get_or_init(|| {
1664 ctx.gpu
1665 .device
1666 .create_bind_group(&wgpu::BindGroupDescriptor {
1667 label: Some("voxel bind group"),
1668 layout: &ctx.voxel_ctx.bind_group_layout,
1669 entries: &[
1670 wgpu::BindGroupEntry {
1671 binding: 0,
1672 resource: workspace.tile4.sorted.bind_active(),
1673 },
1674 wgpu::BindGroupEntry {
1675 binding: 1,
1676 resource: workspace.tile4.zmin.bind_active(),
1677 },
1678 wgpu::BindGroupEntry {
1679 binding: 2,
1680 resource: workspace.voxels.bind_active(),
1681 },
1682 ],
1683 })
1684 })
1685 }
1686
1687 fn normals(
1688 &self,
1689 ctx: &Context,
1690 workspace: &Workspace,
1691 ) -> &wgpu::BindGroup {
1692 self.normals.get_or_init(|| {
1693 ctx.gpu
1694 .device
1695 .create_bind_group(&wgpu::BindGroupDescriptor {
1696 label: Some("normals bind group"),
1697 layout: &ctx.normals_ctx.bind_group_layout,
1698 entries: &[
1699 wgpu::BindGroupEntry {
1700 binding: 0,
1701 resource: workspace.voxels.bind_active(),
1702 },
1703 wgpu::BindGroupEntry {
1704 binding: 1,
1705 resource: workspace.geom.bind_active(),
1706 },
1707 ],
1708 })
1709 })
1710 }
1711}
1712
1713impl Workspace {
1714 pub fn image_size(&self) -> VoxelSize {
1716 self.image_size
1717 }
1718
1719 pub fn output(&mut self) -> &FlexBuffer<GeomBufferTag> {
1726 &self.geom
1727 }
1728
1729 fn new(device: &wgpu::Device) -> Self {
1730 static_assertions::const_assert!(
1732 (std::mem::size_of::<Config>()
1733 + TAPE_DATA_CAPACITY * std::mem::size_of::<TapeWord>())
1734 as u64
1735 <= BufferType::Storage.max_size()
1736 );
1737
1738 let image_size = 64.into();
1739
1740 let config_buf = device.create_buffer(&wgpu::BufferDescriptor {
1741 label: Some("config"),
1742 size: (std::mem::size_of::<Config>()
1743 + TAPE_DATA_CAPACITY * std::mem::size_of::<TapeWord>())
1744 as u64,
1745 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
1746 mapped_at_creation: false,
1747 });
1748
1749 let render_size = TileRenderSize::from(image_size);
1750 let voxels = FlexBuffer::new(
1751 device,
1752 "voxels",
1753 Self::voxels_buf_size(render_size),
1754 )
1755 .unwrap();
1756 let tile_tapes = FlexBuffer::new(
1757 device,
1758 "tile tape",
1759 Self::tile_tapes_buf_size(render_size),
1760 )
1761 .unwrap();
1762
1763 let geom = FlexBuffer::new(device, "geom", image_size).unwrap();
1764
1765 let tile64 = RootTileBuffers::new(device, render_size).unwrap();
1766 let tile16 = TileBuffers::new(device, render_size).unwrap();
1767 let tile4 = TileBuffers::new(device, render_size).unwrap();
1768
1769 let z_hist_buf = device.create_buffer(&wgpu::BufferDescriptor {
1771 label: Some("tiles_zhist"),
1772 size: u64::try_from(
1773 (4 * std::mem::size_of::<u32>()).next_multiple_of(256)
1774 + (16 * std::mem::size_of::<u32>()),
1775 )
1776 .unwrap(),
1777 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
1778 mapped_at_creation: false,
1779 });
1780
1781 let vars_buf = FlexBuffer::new(device, "vars", 4).unwrap();
1782
1783 Self {
1784 config_buf,
1785 image_size,
1786 tile_tapes,
1787 tile64,
1788 tile16,
1789 tile4,
1790 voxels,
1791 geom,
1792 vars_buf,
1793 z_hist_buf,
1794 bind_groups: Default::default(),
1795 }
1796 }
1797
1798 fn render_size(&self) -> TileRenderSize {
1799 self.image_size.into()
1800 }
1801
1802 fn strata_size_bytes(&self) -> usize {
1804 strata_size_bytes(self.render_size())
1805 }
1806
1807 fn tile_tapes_buf_size(render_size: TileRenderSize) -> usize {
1829 let nx = usize::try_from(render_size.nx()).unwrap();
1830 let ny = usize::try_from(render_size.ny()).unwrap();
1831 let nz = usize::try_from(render_size.nz()).unwrap();
1832
1833 let xy_size = (64usize / 4).pow(3) + (64usize / 16).pow(3);
1835
1836 nx.checked_mul(ny)
1840 .unwrap()
1841 .checked_mul(nz.checked_add(xy_size).unwrap())
1842 .unwrap()
1843 }
1844
1845 fn voxels_buf_size(render_size: TileRenderSize) -> VoxelSize {
1846 VoxelSize::new(
1847 render_size.width(),
1848 render_size.height(),
1849 render_size.depth(),
1850 )
1851 }
1852
1853 fn set_image_size(
1857 &mut self,
1858 device: &wgpu::Device,
1859 image_size: VoxelSize,
1860 ) -> Result<(), BufferError> {
1861 let render_size = TileRenderSize::from(image_size);
1862 let Workspace {
1863 image_size: image_size_ref,
1864 config_buf: _,
1865 z_hist_buf: _,
1866 vars_buf: _,
1867 tile_tapes,
1868 tile64,
1869 tile16,
1870 tile4,
1871 voxels,
1872 geom,
1873 bind_groups,
1874 } = self;
1875 if *image_size_ref != image_size {
1877 *bind_groups = Default::default();
1880 }
1881 *image_size_ref = image_size;
1882 tile_tapes
1883 .grow_to_fit(device, Self::tile_tapes_buf_size(render_size))
1884 .map_err(|err| BufferError {
1885 buf: BufferName::TileTapes,
1886 err,
1887 })?;
1888 tile64
1889 .grow_to_fit(device, render_size)
1890 .map_err(|e| BufferError {
1891 buf: BufferName::Tile64(e.buf),
1892 err: e.err,
1893 })?;
1894 tile16
1895 .grow_to_fit(device, render_size)
1896 .map_err(|e| BufferError {
1897 buf: BufferName::Tile16(e.buf),
1898 err: e.err,
1899 })?;
1900 tile4
1901 .grow_to_fit(device, render_size)
1902 .map_err(|e| BufferError {
1903 buf: BufferName::Tile4(e.buf),
1904 err: e.err,
1905 })?;
1906
1907 voxels
1908 .grow_to_fit(device, Self::voxels_buf_size(render_size))
1909 .map_err(|err| BufferError {
1910 buf: BufferName::Voxels,
1911 err,
1912 })?;
1913 geom.grow_to_fit(device, image_size)
1914 .map_err(|err| BufferError {
1915 buf: BufferName::Geom,
1916 err,
1917 })?;
1918 Ok(())
1919 }
1920
1921 pub fn capacity(&self) -> u64 {
1923 let Workspace {
1925 image_size: _,
1926 config_buf,
1927 z_hist_buf,
1928 tile_tapes,
1929 vars_buf,
1930 tile64,
1931 tile16,
1932 tile4,
1933 voxels,
1934 geom,
1935 bind_groups: _,
1936 } = self;
1937 config_buf.size()
1938 + vars_buf.capacity()
1939 + z_hist_buf.size()
1940 + tile_tapes.capacity()
1941 + tile64.capacity()
1942 + tile16.capacity()
1943 + tile4.capacity()
1944 + voxels.capacity()
1945 + geom.capacity()
1946 }
1947
1948 pub fn size(&self) -> u64 {
1950 let Workspace {
1952 image_size: _,
1953 config_buf,
1954 vars_buf,
1955 z_hist_buf,
1956 tile_tapes,
1957 tile64,
1958 tile16,
1959 tile4,
1960 voxels,
1961 geom,
1962 bind_groups: _,
1963 } = self;
1964 config_buf.size()
1965 + vars_buf.size_bytes()
1966 + z_hist_buf.size()
1967 + tile_tapes.size_bytes()
1968 + tile64.size()
1969 + tile16.size()
1970 + tile4.size()
1971 + voxels.size_bytes()
1972 + geom.size_bytes()
1973 }
1974}
1975
1976impl Context {
1977 pub fn new(gpu: &Gpu) -> Self {
1979 let common_bind_group_layout = gpu.device.create_bind_group_layout(
1981 &wgpu::BindGroupLayoutDescriptor {
1982 label: Some("common bind group layout"),
1983 entries: &[
1984 buffer_rw(0), buffer_rw(1), buffer_ro(2), ],
1988 },
1989 );
1990
1991 let root_ctx = RootContext::new(&gpu.device, &common_bind_group_layout);
1992 let repack_ctx =
1993 RepackContext::new(&gpu.device, &common_bind_group_layout);
1994 let interval_ctx =
1995 IntervalContext::new(&gpu.device, &common_bind_group_layout);
1996 let voxel_ctx =
1997 VoxelContext::new(&gpu.device, &common_bind_group_layout);
1998 let normals_ctx =
1999 NormalsContext::new(&gpu.device, &common_bind_group_layout);
2000 let merge_ctx =
2001 MergeContext::new(&gpu.device, &common_bind_group_layout);
2002 let clear_ctx =
2003 ClearContext::new(&gpu.device, &common_bind_group_layout);
2004 let reset_ctx = ResetContext;
2005
2006 Self {
2007 gpu: gpu.clone(),
2008 common_bind_group_layout,
2009 root_ctx,
2010 repack_ctx,
2011 interval_ctx,
2012 voxel_ctx,
2013 normals_ctx,
2014 merge_ctx,
2015 reset_ctx,
2016 clear_ctx,
2017 }
2018 }
2019
2020 pub fn workspace(&self) -> Workspace {
2026 Workspace::new(&self.gpu.device)
2027 }
2028
2029 #[cfg(not(target_arch = "wasm32"))]
2033 pub fn run(
2034 &self,
2035 shape: &RenderShape,
2036 workspace: &mut Workspace,
2037 out: &mut ReadBuffer<GeomBufferTag>,
2038 settings: RenderConfig,
2039 ) -> Result<Image, SubmitError> {
2040 self.run_with_vars(shape, &Default::default(), workspace, out, settings)
2041 }
2042
2043 #[cfg(not(target_arch = "wasm32"))]
2047 pub fn run_with_vars(
2048 &self,
2049 shape: &RenderShape,
2050 vars: &ShapeVars<f32>,
2051 workspace: &mut Workspace,
2052 out: &mut ReadBuffer<GeomBufferTag>,
2053 settings: RenderConfig,
2054 ) -> Result<Image, SubmitError> {
2055 self.submit_with_vars(shape, vars, workspace, &settings)?;
2056 self.gpu.copy(&workspace.geom, out);
2057 let image = self.gpu.map_image(out);
2058 Ok(image.image())
2059 }
2060
2061 pub async fn run_async(
2065 &self,
2066 shape: &RenderShape,
2067 workspace: &mut Workspace,
2068 out: &mut ReadBuffer<GeomBufferTag>,
2069 settings: RenderConfig,
2070 ) -> Result<Image, SubmitError> {
2071 self.run_with_vars_async(
2072 shape,
2073 &Default::default(),
2074 workspace,
2075 out,
2076 settings,
2077 )
2078 .await
2079 }
2080
2081 pub async fn run_with_vars_async(
2085 &self,
2086 shape: &RenderShape,
2087 vars: &ShapeVars<f32>,
2088 workspace: &mut Workspace,
2089 out: &mut ReadBuffer<GeomBufferTag>,
2090 settings: RenderConfig,
2091 ) -> Result<Image, SubmitError> {
2092 self.submit_with_vars(shape, vars, workspace, &settings)?;
2093 self.gpu.copy(&workspace.geom, out);
2094 let image = self.gpu.map_image_async(out).await;
2095 Ok(image.image())
2096 }
2097
2098 pub fn submit(
2103 &self,
2104 shape: &RenderShape,
2105 workspace: &mut Workspace,
2106 settings: &RenderConfig,
2107 ) -> Result<(), SubmitError> {
2108 self.submit_with_vars(shape, &Default::default(), workspace, settings)
2109 }
2110
2111 pub fn submit_with_vars(
2115 &self,
2116 shape: &RenderShape,
2117 vars: &ShapeVars<f32>,
2118 workspace: &mut Workspace,
2119 settings: &RenderConfig,
2120 ) -> Result<(), SubmitError> {
2121 let mut encoder = self.gpu.device.create_command_encoder(
2123 &wgpu::CommandEncoderDescriptor { label: None },
2124 );
2125 self.encode_with_vars(shape, vars, workspace, settings, &mut encoder)?;
2126 self.gpu.queue.submit(Some(encoder.finish()));
2127 Ok(())
2128 }
2129
2130 pub fn encode_with_vars(
2134 &self,
2135 shape: &RenderShape,
2136 vars: &ShapeVars<f32>,
2137 workspace: &mut Workspace,
2138 settings: &RenderConfig,
2139 encoder: &mut wgpu::CommandEncoder,
2140 ) -> Result<(), SubmitError> {
2141 workspace.set_image_size(&self.gpu.device, settings.image_size)?;
2142 let render_size = TileRenderSize::from(workspace.image_size);
2143
2144 let mat =
2145 settings.world_to_model * workspace.image_size.screen_to_world();
2146
2147 let start_offset = u32::try_from(shape.bytecode.len()).unwrap() / 2;
2149 let config = Config {
2150 mat: mat.data.as_slice().try_into().unwrap(),
2151 axes: shape.axes(),
2152 render_size: [
2153 render_size.width(),
2154 render_size.height(),
2155 render_size.depth(),
2156 ],
2157 tape_data_capacity: TAPE_DATA_CAPACITY.try_into().unwrap(),
2158 image_size: [
2159 workspace.image_size.width(),
2160 workspace.image_size.height(),
2161 workspace.image_size.depth(),
2162 ],
2163 tape_data_offset: start_offset,
2164 root_tape_len: start_offset,
2165 };
2166
2167 {
2168 let config_len = std::mem::size_of_val(&config);
2170 let mut writer = self
2171 .gpu
2172 .queue
2173 .write_buffer_with(
2174 &workspace.config_buf,
2175 0,
2176 ((config_len + shape.bytecode.as_bytes().len()) as u64)
2177 .try_into()
2178 .unwrap(),
2179 )
2180 .unwrap();
2181 writer
2182 .slice(..config_len)
2183 .copy_from_slice(config.as_bytes());
2184 writer
2185 .slice(config_len..)
2186 .copy_from_slice(shape.bytecode.as_bytes());
2187 }
2188
2189 if matches!(
2192 shape.copy_vars(&self.gpu, vars, &mut workspace.vars_buf)?,
2193 CopyVarsChanged::BufferChanged
2194 ) {
2195 workspace.bind_groups.common = Default::default();
2196 }
2197
2198 self.reset_ctx.run(encoder, workspace);
2200
2201 let mut compute_pass =
2202 encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
2203 label: None,
2204 timestamp_writes: None,
2205 });
2206
2207 let common_bind_group = workspace.bind_groups.common(self, workspace);
2209 compute_pass.set_bind_group(0, common_bind_group, &[]);
2210
2211 self.root_ctx.run(
2213 self,
2214 workspace,
2215 shape.bytecode.reg_count(),
2216 render_size,
2217 &mut compute_pass,
2218 );
2219 self.repack_ctx
2221 .run(self, workspace, render_size, &mut compute_pass);
2222
2223 let strata_count = u64::from(render_size.depth()).div_ceil(64);
2225 for strata in 0..strata_count {
2226 self.interval_ctx.run(
2227 self,
2228 workspace,
2229 strata,
2230 shape.bytecode.reg_count(),
2231 &mut compute_pass,
2232 );
2233 self.voxel_ctx.run(
2234 self,
2235 workspace,
2236 shape.bytecode.reg_count(),
2237 &mut compute_pass,
2238 );
2239
2240 self.merge_ctx.run(self, workspace, &mut compute_pass);
2242 self.normals_ctx.run(
2243 self,
2244 workspace,
2245 shape.bytecode.reg_count(),
2246 &mut compute_pass,
2247 );
2248
2249 self.clear_ctx.run(self, workspace, &mut compute_pass);
2250 }
2251
2252 Ok(())
2254 }
2255}
2256
2257struct ClearContext {
2258 bind_group_layout: wgpu::BindGroupLayout,
2259 pipeline: wgpu::ComputePipeline,
2260}
2261
2262impl ClearContext {
2263 fn new(
2264 device: &wgpu::Device,
2265 common_bind_group_layout: &wgpu::BindGroupLayout,
2266 ) -> Self {
2267 let bind_group_layout =
2269 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
2270 label: Some("clear bind group layout"),
2271 entries: &[
2272 buffer_rw(0), buffer_rw(1), buffer_rw(2), buffer_rw(3), buffer_rw(4), ],
2278 });
2279
2280 let pipeline_layout =
2282 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
2283 label: Some("clear pipeline layout"),
2284 bind_group_layouts: &[
2285 Some(common_bind_group_layout),
2286 Some(&bind_group_layout),
2287 ],
2288 immediate_size: 0u32,
2289 });
2290
2291 let shader_code = clear_shader();
2293 let shader_module =
2294 device.create_shader_module(wgpu::ShaderModuleDescriptor {
2295 label: Some("clear shader module"),
2296 source: wgpu::ShaderSource::Wgsl(shader_code.into()),
2297 });
2298
2299 let pipeline =
2300 device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
2301 label: Some("clear"),
2302 layout: Some(&pipeline_layout),
2303 module: &shader_module,
2304 entry_point: Some("clear_main"),
2305 compilation_options: Default::default(),
2306 cache: None,
2307 });
2308
2309 Self {
2310 pipeline,
2311 bind_group_layout,
2312 }
2313 }
2314
2315 fn run(
2316 &self,
2317 ctx: &Context,
2318 workspace: &Workspace,
2319 compute_pass: &mut wgpu::ComputePass,
2320 ) {
2321 let bind_group = workspace.bind_groups.clear(ctx, workspace);
2322 compute_pass.set_pipeline(&self.pipeline);
2323 compute_pass.set_bind_group(1, bind_group, &[]);
2324 compute_pass.dispatch_workgroups(1, 1, 1);
2325 }
2326}
2327
2328struct MergeContext {
2329 bind_group_layout: wgpu::BindGroupLayout,
2330 pipeline: wgpu::ComputePipeline,
2331}
2332
2333impl MergeContext {
2334 fn new(
2335 device: &wgpu::Device,
2336 common_bind_group_layout: &wgpu::BindGroupLayout,
2337 ) -> Self {
2338 let shader_code = merge_shader();
2339
2340 let bind_group_layout =
2342 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
2343 label: Some("merge bind group layout"),
2344 entries: &[
2345 buffer_rw(0), buffer_rw(1), buffer_rw(2), buffer_rw(3), ],
2350 });
2351
2352 let pipeline_layout =
2354 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
2355 label: Some("merge pipeline layout"),
2356 bind_group_layouts: &[
2357 Some(common_bind_group_layout),
2358 Some(&bind_group_layout),
2359 ],
2360 immediate_size: 0u32,
2361 });
2362
2363 let shader_module =
2365 device.create_shader_module(wgpu::ShaderModuleDescriptor {
2366 label: Some("merge shader module"),
2367 source: wgpu::ShaderSource::Wgsl(shader_code.into()),
2368 });
2369
2370 let pipeline =
2371 device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
2372 label: Some("merge"),
2373 layout: Some(&pipeline_layout),
2374 module: &shader_module,
2375 entry_point: Some("merge_main"),
2376 compilation_options: Default::default(),
2377 cache: None,
2378 });
2379
2380 Self {
2381 pipeline,
2382 bind_group_layout,
2383 }
2384 }
2385
2386 fn run(
2387 &self,
2388 ctx: &Context,
2389 workspace: &Workspace,
2390 compute_pass: &mut wgpu::ComputePass,
2391 ) {
2392 let render_size = workspace.render_size();
2393 let bind_group = workspace.bind_groups.merge(ctx, workspace);
2394 compute_pass.set_pipeline(&self.pipeline);
2395 compute_pass.set_bind_group(1, bind_group, &[]);
2396 compute_pass.dispatch_workgroups(
2397 render_size.width().div_ceil(8),
2398 render_size.height().div_ceil(8),
2399 1,
2400 );
2401 }
2402}
2403
2404struct ResetContext;
2405
2406impl ResetContext {
2407 fn run(&self, encoder: &mut wgpu::CommandEncoder, workspace: &Workspace) {
2408 encoder.clear_buffer(workspace.tile64.tiles.data(), 12, Some(4));
2410
2411 let strata_size_bytes = workspace.strata_size_bytes();
2415 for s in 0..workspace.render_size().nz() {
2416 encoder.clear_buffer(
2417 workspace.tile64.strata.data(),
2418 u64::from(s) * u64::try_from(strata_size_bytes).unwrap(),
2419 Some(16),
2420 );
2421 }
2422
2423 workspace.tile64.zmin.clear(encoder);
2425 workspace.tile64.zmax.clear(encoder);
2426 workspace.tile16.zmin.clear(encoder);
2427 workspace.tile4.zmin.clear(encoder);
2428 workspace.voxels.clear(encoder);
2429 workspace.geom.clear(encoder);
2430
2431 workspace.tile_tapes.clear(encoder);
2433
2434 }
2436}
2437
2438#[cfg(test)]
2439mod test {
2440 use super::{effects::PackedVoxel, *};
2441 use crate::color::{ShapeColor, ShapeColorBuffers};
2442 use fidget_core::{context::Tree, vm::VmShape};
2443 use std::collections::HashSet;
2444
2445 #[test]
2446 fn compile_interval_root_shader() {
2447 crate::compile_shader(&interval_root_shader(16), "interval root");
2448 }
2449
2450 #[test]
2451 fn compile_interval_tiles_shader() {
2452 crate::compile_shader(&interval_tiles_shader(16), "interval tiles");
2453 }
2454
2455 #[test]
2456 fn compile_voxel_tiles_shader() {
2457 crate::compile_shader(&voxel_tiles_shader(16), "voxel tiles");
2458 }
2459
2460 #[test]
2461 fn compile_normals_shader() {
2462 crate::compile_shader(&normals_shader(16), "normals tiles");
2463 }
2464
2465 #[test]
2466 fn compile_repack_shader() {
2467 crate::compile_shader(&repack_shader(), "repack");
2468 }
2469
2470 #[test]
2471 fn compile_sort_shader() {
2472 crate::compile_shader(&sort_shader(), "sort");
2473 }
2474
2475 #[test]
2476 fn compile_merge_shader() {
2477 crate::compile_shader(&merge_shader(), "merge");
2478 }
2479
2480 #[test]
2481 fn compile_clear_shader() {
2482 crate::compile_shader(&clear_shader(), "clear");
2483 }
2484
2485 #[derive(
2486 Copy,
2487 Clone,
2488 Debug,
2489 IntoBytes,
2490 Immutable,
2491 FromBytes,
2492 KnownLayout,
2493 PartialEq,
2494 Eq,
2495 Hash,
2496 )]
2497 #[repr(C)]
2498 struct Rgba {
2499 r: u8,
2500 g: u8,
2501 b: u8,
2502 a: u8,
2503 }
2504
2505 struct RenderOutput {
2506 merged: Vec<PackedVoxel>,
2507 colors: Vec<Rgba>,
2508 shaded: fidget_raster::Image<u32, VoxelSize>,
2509 heightmap: fidget_raster::Image<u32, VoxelSize>,
2510 }
2511
2512 fn render(
2513 shapes: &[(Tree, ShapeColor<Tree>)],
2514 render_config: RenderConfig,
2515 ) -> RenderOutput {
2516 let gpu = pollster::block_on(Gpu::init_basic()).unwrap();
2517 let voxel_ctx = Context::new(&gpu);
2518 let effects_ctx = effects::Context::new(&gpu);
2519
2520 let mut buf = voxel_ctx.workspace();
2521 let mut merge_buf = effects_ctx.merge_workspace();
2522 let mut shade_buf = effects_ctx.shade_workspace();
2523
2524 for (shape, _) in shapes {
2526 let shape =
2527 RenderShape::new(&VmShape::from(shape.clone())).unwrap();
2528 voxel_ctx.submit(&shape, &mut buf, &render_config).unwrap();
2529 effects_ctx
2530 .submit_merge(buf.output(), Default::default(), &mut merge_buf)
2531 .unwrap();
2532 }
2533 let merged = gpu.read_vec(merge_buf.output());
2534 let shape_colors = shapes
2535 .iter()
2536 .map(|(_, c)| match c {
2537 ShapeColor::Rgb { r, g, b } => ShapeColor::Rgb {
2538 r: VmShape::from(r.clone()),
2539 g: VmShape::from(g.clone()),
2540 b: VmShape::from(b.clone()),
2541 },
2542 ShapeColor::Hsl { h, s, l } => ShapeColor::Hsl {
2543 h: VmShape::from(h.clone()),
2544 s: VmShape::from(s.clone()),
2545 l: VmShape::from(l.clone()),
2546 },
2547 })
2548 .collect::<Vec<_>>();
2549 let shape_colors = ShapeColorBuffers::new(&shape_colors).unwrap();
2550 let mut color_workspace = effects_ctx.color_workspace();
2551
2552 let mut ssao_buf = effects_ctx.ssao_workspace();
2554 effects_ctx.submit_ssao(&merge_buf, &mut ssao_buf).unwrap();
2555
2556 effects_ctx
2558 .submit_color(
2559 &merge_buf,
2560 &render_config.world_to_model,
2561 &shape_colors,
2562 &mut color_workspace,
2563 &mut shade_buf,
2564 )
2565 .unwrap();
2566
2567 let raw_colors = gpu.read_vec(shade_buf.output());
2570 let colors = <[Rgba]>::ref_from_bytes(raw_colors.as_bytes())
2571 .unwrap()
2572 .to_vec();
2573
2574 let mut shade_out = gpu.read_buffer_for(shade_buf.output());
2576 effects_ctx
2577 .submit_shade(&merge_buf, Some(&ssao_buf), &mut shade_buf)
2578 .unwrap();
2579 gpu.copy(shade_buf.output(), &mut shade_out);
2580 let shaded = gpu.map_image(&mut shade_out).image();
2581
2582 effects_ctx
2584 .submit_color(
2585 &merge_buf,
2586 &render_config.world_to_model,
2587 &shape_colors,
2588 &mut color_workspace,
2589 &mut shade_buf,
2590 )
2591 .unwrap();
2592 effects_ctx
2594 .submit_heightmap(&merge_buf, &mut shade_buf)
2595 .unwrap();
2596 gpu.copy(shade_buf.output(), &mut shade_out);
2597 let heightmap = gpu.map_image(&mut shade_out).image();
2598
2599 RenderOutput {
2600 merged,
2601 colors,
2602 shaded,
2603 heightmap,
2604 }
2605 }
2606
2607 #[test]
2608 fn voxel_pipeline() {
2609 #[cfg(not(target_os = "macos"))]
2612 if std::env::var("CI").is_ok() {
2613 return;
2614 }
2615
2616 let size = 128;
2617
2618 let (x, y, z) = Tree::axes();
2619 let x_ = x.clone() - 0.2;
2620 let sphere1 = (x_.square() + y.square() + z.square()).sqrt()
2621 - Tree::constant(0.5);
2622 let x_ = x + 0.2;
2623 let sphere2 = (x_.square() + y.square() + z.square()).sqrt()
2624 - Tree::constant(0.5);
2625 let spheres = sphere1.min(sphere2);
2626 let out = render(
2627 &[(
2628 spheres,
2629 ShapeColor::Rgb {
2630 r: Tree::constant(0.0),
2631 g: Tree::constant(1.0),
2632 b: Tree::constant(0.25),
2633 },
2634 )],
2635 RenderConfig::from_size(size.into()),
2636 );
2637
2638 let color_set = out.colors.iter().cloned().collect::<HashSet<_>>();
2639 assert!(
2640 color_set.contains(&Rgba {
2641 a: 0,
2642 r: 0xFF,
2643 g: 0xFF,
2644 b: 0xFF
2645 }) && color_set.contains(&Rgba {
2646 a: 0xFF,
2647 r: 0x00,
2648 g: 0xFF,
2649 b: 0x3F
2650 }),
2651 "invalid color set {color_set:X?} in {} pixels",
2652 out.colors.len()
2653 );
2654
2655 let (shaded, img_size) = out.shaded.take();
2656 assert_eq!(img_size.width(), size);
2657 assert_eq!(img_size.height(), size);
2658
2659 for (m, c) in out.merged.iter().zip(out.colors.iter()) {
2660 let p = m.z;
2661 if p == 0 {
2662 assert_eq!(c.a, 0);
2663 } else {
2664 assert_eq!(c.a, 0xFF);
2665 }
2666 }
2667 for (m, c) in out.merged.iter().zip(shaded.iter()) {
2668 let p = m.z;
2669 if p == 0 {
2670 assert_eq!(*c, 0);
2671 } else {
2672 assert_eq!(c & (0xFF << 24), (0xFF << 24));
2674 let g = (c >> 8) & 0xFF;
2676 let b = (c >> 16) & 0xFF;
2677 assert!(
2678 (g / 4).abs_diff(b) < 2,
2679 "invalid G/B ratio {g}/{b} (expected 4)"
2680 );
2681 }
2682 }
2683 for (m, c) in out.merged.iter().zip(out.heightmap.iter()) {
2684 let p = m.z;
2685 if p == 0 {
2686 assert_eq!(*c, 0);
2687 } else {
2688 assert_eq!(c & (0xFF << 24), (0xFF << 24));
2690 let g = (c >> 8) & 0xFF;
2692 let b = (c >> 16) & 0xFF;
2693 assert!(
2694 (g / 4).abs_diff(b) < 2,
2695 "invalid G/B ratio {g}/{b} (expected 4)"
2696 );
2697 }
2698 }
2699 assert!(out.heightmap.iter().zip(shaded.iter()).any(|(a, b)| a != b));
2701 }
2702
2703 #[test]
2704 fn voxel_transform() {
2705 #[cfg(not(target_os = "macos"))]
2708 if std::env::var("CI").is_ok() {
2709 return;
2710 }
2711
2712 let (x, y, z) = Tree::axes();
2713 let sphere =
2714 (x.square() + y.square() + z.square()).sqrt() - Tree::constant(0.5);
2715
2716 let size = 64;
2717 let cfg = RenderConfig {
2718 image_size: size.into(),
2719 world_to_model: nalgebra::Translation3::new(-0.5, 0.0, 0.0)
2720 .to_homogeneous(),
2721 };
2722 let out = render(
2723 &[(
2724 sphere.clone(),
2725 ShapeColor::Rgb {
2726 r: Tree::x() + 0.5, g: Tree::y() + 0.5, b: Tree::z(),
2729 },
2730 )],
2731 cfg,
2732 );
2733
2734 let mut iter = out.colors.iter();
2735 let mut pixels = String::with_capacity(out.colors.len());
2736 for _y in 0..size {
2737 for _x in 0..size {
2738 pixels += if iter.next().unwrap().a == 0 {
2739 "."
2740 } else {
2741 "#"
2742 };
2743 }
2744 pixels += "\n";
2745 }
2746 assert_eq!(
2747 pixels,
2748 "\
2749 ................................................................
2750 ................................................................
2751 ................................................................
2752 ................................................................
2753 ................................................................
2754 ................................................................
2755 ................................................................
2756 ................................................................
2757 ................................................................
2758 ................................................................
2759 ................................................................
2760 ................................................................
2761 ................................................................
2762 ................................................................
2763 ................................................................
2764 ................................................................
2765 ...........................................###########..........
2766 .........................................###############........
2767 .......................................###################......
2768 ......................................#####################.....
2769 .....................................#######################....
2770 ....................................#########################...
2771 ...................................###########################..
2772 ...................................###########################..
2773 ..................................#############################.
2774 ..................................#############################.
2775 .................................###############################
2776 .................................###############################
2777 .................................###############################
2778 .................................###############################
2779 .................................###############################
2780 .................................###############################
2781 .................................###############################
2782 .................................###############################
2783 .................................###############################
2784 .................................###############################
2785 .................................###############################
2786 ..................................#############################.
2787 ..................................#############################.
2788 ...................................###########################..
2789 ...................................###########################..
2790 ....................................#########################...
2791 .....................................#######################....
2792 ......................................#####################.....
2793 .......................................###################......
2794 .........................................###############........
2795 ...........................................###########..........
2796 ................................................................
2797 ................................................................
2798 ................................................................
2799 ................................................................
2800 ................................................................
2801 ................................................................
2802 ................................................................
2803 ................................................................
2804 ................................................................
2805 ................................................................
2806 ................................................................
2807 ................................................................
2808 ................................................................
2809 ................................................................
2810 ................................................................
2811 ................................................................
2812 ................................................................
2813 "
2814 .replace(" ", "")
2815 );
2816
2817 let (_out, img_size) = out.shaded.take();
2818 assert_eq!(img_size.width(), size);
2819 assert_eq!(img_size.height(), size);
2820
2821 for (m, c) in out.merged.iter().zip(out.colors.iter()) {
2822 let p = m.z;
2823 if p == 0 {
2824 assert_eq!(c.a, 0);
2825 } else {
2826 assert_eq!(c.a, 0xFF);
2827 }
2828 }
2829
2830 let color_set = out.colors.iter().cloned().collect::<HashSet<_>>();
2832 assert_eq!(
2833 color_set.len(),
2834 pixels.chars().filter(|c| *c == '#').count() + 1,
2835 "invalid color set {color_set:X?} in {} pixels",
2836 out.colors.len()
2837 );
2838
2839 let cfg = RenderConfig {
2843 image_size: size.into(),
2844 world_to_model: nalgebra::Translation3::new(0.5, 0.0, 0.0)
2845 .to_homogeneous(),
2846 };
2847 let out = render(
2848 &[(
2849 sphere,
2850 ShapeColor::Rgb {
2851 r: Tree::x() + 0.5, g: Tree::y() + 0.5, b: Tree::z(),
2854 },
2855 )],
2856 cfg,
2857 );
2858
2859 let mut iter = out.colors.iter();
2860 let mut pixels = String::with_capacity(out.colors.len());
2861 for _y in 0..size {
2862 for _x in 0..size {
2863 pixels += if iter.next().unwrap().a == 0 {
2864 "."
2865 } else {
2866 "#"
2867 };
2868 }
2869 pixels += "\n";
2870 }
2871 assert_eq!(
2872 pixels,
2873 "\
2874 ................................................................
2875 ................................................................
2876 ................................................................
2877 ................................................................
2878 ................................................................
2879 ................................................................
2880 ................................................................
2881 ................................................................
2882 ................................................................
2883 ................................................................
2884 ................................................................
2885 ................................................................
2886 ................................................................
2887 ................................................................
2888 ................................................................
2889 ................................................................
2890 ...........###########..........................................
2891 .........###############........................................
2892 .......###################......................................
2893 ......#####################.....................................
2894 .....#######################....................................
2895 ....#########################...................................
2896 ...###########################..................................
2897 ...###########################..................................
2898 ..#############################.................................
2899 ..#############################.................................
2900 .###############################................................
2901 .###############################................................
2902 .###############################................................
2903 .###############################................................
2904 .###############################................................
2905 .###############################................................
2906 .###############################................................
2907 .###############################................................
2908 .###############################................................
2909 .###############################................................
2910 .###############################................................
2911 ..#############################.................................
2912 ..#############################.................................
2913 ...###########################..................................
2914 ...###########################..................................
2915 ....#########################...................................
2916 .....#######################....................................
2917 ......#####################.....................................
2918 .......###################......................................
2919 .........###############........................................
2920 ...........###########..........................................
2921 ................................................................
2922 ................................................................
2923 ................................................................
2924 ................................................................
2925 ................................................................
2926 ................................................................
2927 ................................................................
2928 ................................................................
2929 ................................................................
2930 ................................................................
2931 ................................................................
2932 ................................................................
2933 ................................................................
2934 ................................................................
2935 ................................................................
2936 ................................................................
2937 ................................................................
2938 "
2939 .replace(" ", "")
2940 );
2941
2942 let (_out, img_size) = out.shaded.take();
2943 assert_eq!(img_size.width(), size);
2944 assert_eq!(img_size.height(), size);
2945
2946 for (m, c) in out.merged.iter().zip(out.colors.iter()) {
2947 let p = m.z;
2948 if p == 0 {
2949 assert_eq!(c.a, 0);
2950 } else {
2951 assert_eq!(c.a, 0xFF);
2952 }
2953 }
2954
2955 let color_set = out.colors.iter().cloned().collect::<HashSet<_>>();
2957 assert_eq!(
2958 color_set.len(),
2959 pixels.chars().filter(|c| *c == '#').count() + 1,
2960 "invalid color set {color_set:X?} in {} pixels",
2961 out.colors.len()
2962 );
2963 }
2964
2965 #[test]
2966 fn voxel_hsl() {
2967 #[cfg(not(target_os = "macos"))]
2970 if std::env::var("CI").is_ok() {
2971 return;
2972 }
2973
2974 let size = 128;
2975 let (x, y, z) = Tree::axes();
2976 let x_ = x.clone() - 0.2;
2977 let sphere1 = (x_.square() + y.square() + z.square()).sqrt()
2978 - Tree::constant(0.5);
2979 let x_ = x + 0.2;
2980 let sphere2 = (x_.square() + y.square() + z.square()).sqrt()
2981 - Tree::constant(0.5);
2982 let out = render(
2983 &[
2984 (
2985 sphere1,
2986 ShapeColor::Hsl {
2987 h: Tree::constant(0.0),
2988 s: Tree::constant(1.0),
2989 l: Tree::constant(0.5),
2990 },
2991 ),
2992 (
2993 sphere2,
2994 ShapeColor::Hsl {
2995 h: Tree::constant(0.5),
2996 s: Tree::constant(0.4),
2997 l: Tree::constant(0.7),
2998 },
2999 ),
3000 ],
3001 RenderConfig::from_size(size.into()),
3002 );
3003
3004 let color_set = out.colors.iter().cloned().collect::<HashSet<_>>();
3005 assert_eq!(color_set.len(), 3);
3006 assert!(
3007 color_set.contains(&Rgba {
3008 r: 0xFF,
3009 g: 0xFF,
3010 b: 0xFF,
3011 a: 0x00,
3012 }) && color_set.contains(&Rgba {
3013 r: 0xFF,
3014 g: 0x00,
3015 b: 0x00,
3016 a: 0xFF,
3017 }) && color_set.contains(&Rgba {
3018 r: 0x93,
3019 g: 0xD1,
3020 b: 0xD1,
3021 a: 0xFF
3022 }),
3023 "invalid color set {color_set:X?} in {} pixels",
3024 out.colors.len()
3025 );
3026 println!("{color_set:#x?}");
3027 }
3028
3029 #[test]
3030 fn voxel_config_layout() {
3031 crate::test::compare_struct_layout::<Config>(&merge_shader(), "Config");
3033 }
3034}