1use crate::{
6 CopyVarsChanged, CopyVarsError, Gpu, RegPipeline, RenderShape,
7 TAPE_DATA_CAPACITY, TapeWord,
8 buf::{
9 BufferSizeError, BufferType, FlexBuffer, ReadBuffer, buffer_ro,
10 buffer_rw,
11 },
12 shaders, tag,
13 voxel::VarsBufferTag,
14};
15use fidget_core::{
16 render::ImageSize,
17 shape::{MissingVar, ShapeVars},
18};
19use fidget_raster::pixel::{Image, RawDistancePixel};
20use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
21
22pub use fidget_raster::pixel::{RenderConfig, RenderSize};
23pub mod effects;
24
25const COMMON_SHADER: &str = include_str!("shaders/common.wgsl");
26const DISTANCE_PIXEL_SHADER: &str = include_str!("shaders/distance_pixel.wgsl");
27const INTERVAL_INPUT: &str = include_str!("shaders/interval_input.wgsl");
28const TRANSFORM_INPUT: &str = include_str!("shaders/transform_input.wgsl");
29const INTERVAL_ROOT_SHADER: &str = include_str!("shaders/interval_root.wgsl");
30const INTERVAL_TILES_SHADER: &str = include_str!("shaders/interval_tiles.wgsl");
31const PIXEL_TILES_SHADER: &str = include_str!("shaders/pixel_tiles.wgsl");
32const MERGE_SHADER: &str = include_str!("shaders/merge.wgsl");
33
34fn interval_root_shader(reg_count: u8) -> String {
36 let mut shader_code = shaders::opcode_constants();
37 shader_code += &format!("const REG_COUNT: u32 = {reg_count};");
38 shader_code += COMMON_SHADER;
39 shader_code += DISTANCE_PIXEL_SHADER;
40 shader_code += INTERVAL_ROOT_SHADER;
41 shader_code += INTERVAL_INPUT;
42 shader_code += TRANSFORM_INPUT;
43 shader_code += shaders::INTERVAL_OPS;
44 shader_code += shaders::COMMON;
45 shader_code += shaders::TAPE_INTERPRETER;
46 shader_code += shaders::STACK;
47 shader_code += shaders::TAPE_SIMPLIFY;
48 shader_code
49}
50
51fn interval_tiles_shader(reg_count: u8) -> String {
53 let mut shader_code = shaders::opcode_constants();
54 shader_code += &format!("const REG_COUNT: u32 = {reg_count};");
55 shader_code += COMMON_SHADER;
56 shader_code += DISTANCE_PIXEL_SHADER;
57 shader_code += INTERVAL_TILES_SHADER;
58 shader_code += INTERVAL_INPUT;
59 shader_code += TRANSFORM_INPUT;
60 shader_code += shaders::INTERVAL_OPS;
61 shader_code += shaders::COMMON;
62 shader_code += shaders::TAPE_INTERPRETER;
63 shader_code += shaders::STACK;
64 shader_code += shaders::TAPE_SIMPLIFY;
65 shader_code
66}
67
68fn pixel_tiles_shader(reg_count: u8) -> String {
70 let mut shader_code = shaders::opcode_constants();
71 shader_code += &format!("const REG_COUNT: u32 = {reg_count};");
72 shader_code += PIXEL_TILES_SHADER;
73 shader_code += TRANSFORM_INPUT;
74 shader_code += COMMON_SHADER;
75 shader_code += DISTANCE_PIXEL_SHADER;
76 shader_code += shaders::FLOAT_OPS;
77 shader_code += shaders::COMMON;
78 shader_code += shaders::TAPE_INTERPRETER;
79 shader_code += shaders::DUMMY_STACK;
80 shader_code
81}
82
83fn merge_shader() -> String {
85 MERGE_SHADER.to_owned()
86 + COMMON_SHADER
87 + DISTANCE_PIXEL_SHADER
88 + shaders::COMMON
89}
90
91#[derive(Copy, Clone, Debug)]
98struct TileRenderSize(ImageSize);
99
100impl From<ImageSize> for TileRenderSize {
101 fn from(image_size: ImageSize) -> Self {
102 let nx = image_size.width().div_ceil(64);
103 let ny = image_size.height().div_ceil(64);
104 Self(ImageSize::new(nx, ny))
105 }
106}
107
108impl TileRenderSize {
109 fn nx(&self) -> u32 {
111 self.0.width()
112 }
113
114 fn ny(&self) -> u32 {
116 self.0.height()
117 }
118
119 fn width(&self) -> u32 {
121 self.0.width() * 64
122 }
123
124 fn height(&self) -> u32 {
126 self.0.height() * 64
127 }
128}
129
130struct RootContext {
134 root_pipeline: RegPipeline,
136
137 bind_group_layout: wgpu::BindGroupLayout,
139}
140
141impl RootContext {
142 fn new(
143 device: &wgpu::Device,
144 common_bind_group_layout: &wgpu::BindGroupLayout,
145 ) -> Self {
146 let bind_group_layout =
148 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
149 label: None,
150 entries: &[
151 buffer_rw(0), buffer_rw(1), ],
154 });
155 let pipeline_layout =
156 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
157 label: None,
158 bind_group_layouts: &[
159 Some(common_bind_group_layout),
160 Some(&bind_group_layout),
161 ],
162 immediate_size: 0u32,
163 });
164
165 let device_ = device.clone();
166 let root_pipeline = RegPipeline::build(Box::new(move |reg_count| {
167 let shader_code = interval_root_shader(reg_count);
168 let shader_module =
169 device_.create_shader_module(wgpu::ShaderModuleDescriptor {
170 label: None,
171 source: wgpu::ShaderSource::Wgsl(shader_code.into()),
172 });
173 device_.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
174 label: Some(&format!("interval root ({reg_count})")),
175 layout: Some(&pipeline_layout),
176 module: &shader_module,
177 entry_point: Some("interval_root_main"),
178 compilation_options: Default::default(),
179 cache: None,
180 })
181 }));
182
183 Self {
184 bind_group_layout,
185 root_pipeline,
186 }
187 }
188
189 fn run(
190 &self,
191 ctx: &Context,
192 workspace: &Workspace,
193 reg_count: u8,
194 render_size: TileRenderSize,
195 compute_pass: &mut wgpu::ComputePass,
196 ) {
197 let bind_group = workspace.bind_groups.root_tiles(ctx, workspace);
198 compute_pass.set_pipeline(self.root_pipeline.get(reg_count));
199 compute_pass.set_bind_group(1, bind_group, &[]);
200
201 let nx = render_size.nx().div_ceil(8);
203 let ny = render_size.ny().div_ceil(8);
204 compute_pass.dispatch_workgroups(nx, ny, 1);
205 }
206}
207struct IntervalTilesContext {
211 tiles_pipeline: RegPipeline,
213
214 bind_group_layout: wgpu::BindGroupLayout,
216}
217
218impl IntervalTilesContext {
219 fn new(
220 device: &wgpu::Device,
221 common_bind_group_layout: &wgpu::BindGroupLayout,
222 ) -> Self {
223 let bind_group_layout =
225 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
226 label: None,
227 entries: &[
228 buffer_ro(0), buffer_rw(1), buffer_rw(2), ],
232 });
233 let pipeline_layout =
234 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
235 label: None,
236 bind_group_layouts: &[
237 Some(common_bind_group_layout),
238 Some(&bind_group_layout),
239 ],
240 immediate_size: 0u32,
241 });
242
243 let device_ = device.clone();
244 let tiles_pipeline = RegPipeline::build(Box::new(move |reg_count| {
245 let shader_code = interval_tiles_shader(reg_count);
246 let shader_module =
247 device_.create_shader_module(wgpu::ShaderModuleDescriptor {
248 label: None,
249 source: wgpu::ShaderSource::Wgsl(shader_code.into()),
250 });
251 device_.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
252 label: Some(&format!("interval tiles ({reg_count})")),
253 layout: Some(&pipeline_layout),
254 module: &shader_module,
255 entry_point: Some("interval_tiles_main"),
256 compilation_options: Default::default(),
257 cache: None,
258 })
259 }));
260
261 Self {
262 bind_group_layout,
263 tiles_pipeline,
264 }
265 }
266
267 fn run(
268 &self,
269 ctx: &Context,
270 workspace: &Workspace,
271 reg_count: u8,
272 compute_pass: &mut wgpu::ComputePass,
273 ) {
274 let bind_group = workspace.bind_groups.interval_tiles(ctx, workspace);
275 compute_pass.set_pipeline(self.tiles_pipeline.get(reg_count));
276 compute_pass.set_bind_group(1, bind_group, &[]);
277
278 compute_pass
280 .dispatch_workgroups_indirect(workspace.tile64.tiles.data(), 0);
281 }
282}
283
284struct PixelTilesContext {
288 tiles_pipeline: RegPipeline,
290
291 bind_group_layout: wgpu::BindGroupLayout,
293}
294
295impl PixelTilesContext {
296 fn new(
297 device: &wgpu::Device,
298 common_bind_group_layout: &wgpu::BindGroupLayout,
299 ) -> Self {
300 let bind_group_layout =
302 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
303 label: None,
304 entries: &[
305 buffer_ro(0), buffer_rw(1), ],
308 });
309 let pipeline_layout =
310 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
311 label: None,
312 bind_group_layouts: &[
313 Some(common_bind_group_layout),
314 Some(&bind_group_layout),
315 ],
316 immediate_size: 0u32,
317 });
318
319 let device_ = device.clone();
320 let tiles_pipeline = RegPipeline::build(Box::new(move |reg_count| {
321 let shader_code = pixel_tiles_shader(reg_count);
322 let shader_module =
323 device_.create_shader_module(wgpu::ShaderModuleDescriptor {
324 label: None,
325 source: wgpu::ShaderSource::Wgsl(shader_code.into()),
326 });
327 device_.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
328 label: Some(&format!("pixel tiles ({reg_count})")),
329 layout: Some(&pipeline_layout),
330 module: &shader_module,
331 entry_point: Some("pixel_tiles_main"),
332 compilation_options: Default::default(),
333 cache: None,
334 })
335 }));
336
337 Self {
338 bind_group_layout,
339 tiles_pipeline,
340 }
341 }
342
343 fn run(
344 &self,
345 ctx: &Context,
346 workspace: &Workspace,
347 reg_count: u8,
348 compute_pass: &mut wgpu::ComputePass,
349 ) {
350 let bind_group = workspace.bind_groups.pixel_tiles(ctx, workspace);
351 compute_pass.set_pipeline(self.tiles_pipeline.get(reg_count));
352 compute_pass.set_bind_group(1, bind_group, &[]);
353
354 compute_pass
356 .dispatch_workgroups_indirect(workspace.tile8.tiles.data(), 0);
357 }
358}
359
360#[derive(Debug, IntoBytes, Immutable, FromBytes, KnownLayout)]
367#[cfg_attr(test, derive(facet::Facet))]
368#[repr(C)]
369struct Config {
370 mat: [f32; 12],
375
376 axes: [u32; 3],
380
381 tape_data_offset: u32,
383
384 render_size: [u32; 2],
386
387 image_size: [u32; 2],
389
390 z: f32,
392
393 pixel_perfect: u32,
395
396 tape_data_capacity: u32,
398
399 _pad: u32,
401 }
403
404tag!(TileTapesBufferTag, u32, usize, STORAGE | COPY_DST);
405tag!(pub PixelBufferTag, RawDistancePixel, ImageSize, STORAGE | COPY_SRC | COPY_DST,
406 "Tag for a on-GPU buffer storing [`RawDistancePixel`] values");
407
408pub struct Workspace {
413 image_size: ImageSize,
418
419 config_buf: wgpu::Buffer,
421
422 vars_buf: FlexBuffer<VarsBufferTag>,
424
425 tile_tapes: FlexBuffer<TileTapesBufferTag>,
427
428 tile64: TileBuffers<64>,
430
431 tile8: TileBuffers<8>,
433
434 pixels: FlexBuffer<PixelBufferTag>,
436
437 bind_groups: BindGroups,
439}
440
441impl Workspace {
442 fn new(device: &wgpu::Device) -> Self {
446 static_assertions::const_assert!(
448 (std::mem::size_of::<Config>()
449 + TAPE_DATA_CAPACITY * std::mem::size_of::<TapeWord>())
450 as u64
451 <= BufferType::Storage.max_size()
452 );
453
454 let image_size = 64.into();
456
457 let config_buf = device.create_buffer(&wgpu::BufferDescriptor {
458 label: Some("config"),
459 size: (std::mem::size_of::<Config>()
460 + TAPE_DATA_CAPACITY * std::mem::size_of::<TapeWord>())
461 as u64,
462 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
463 mapped_at_creation: false,
464 });
465
466 let render_size = TileRenderSize::from(image_size);
467 let tile_tapes = FlexBuffer::new(
468 device,
469 "tile tape",
470 Self::tile_tapes_buf_size(render_size),
471 )
472 .unwrap();
473
474 let pixels = FlexBuffer::new(device, "pixels", image_size).unwrap();
475
476 let tile64 = TileBuffers::new(device, render_size).unwrap();
477 let tile8 = TileBuffers::new(device, render_size).unwrap();
478 let vars_buf = FlexBuffer::new(device, "vars", 4).unwrap();
479
480 Self {
481 config_buf,
482 vars_buf,
483 image_size,
484 tile_tapes,
485 tile64,
486 tile8,
487 pixels,
488 bind_groups: Default::default(),
489 }
490 }
491
492 fn tile_tapes_buf_size(render_size: TileRenderSize) -> usize {
504 let nx = usize::try_from(render_size.nx()).unwrap();
505 let ny = usize::try_from(render_size.ny()).unwrap();
506
507 nx.checked_mul(ny).unwrap().checked_mul(65).unwrap()
511 }
512
513 fn set_image_size(
517 &mut self,
518 device: &wgpu::Device,
519 image_size: ImageSize,
520 ) -> Result<(), BufferError> {
521 let render_size = TileRenderSize::from(image_size);
522 let Workspace {
523 image_size: image_size_ref,
524 tile_tapes,
525 tile64,
526 tile8,
527 config_buf: _,
528 vars_buf: _,
529 pixels,
530 bind_groups,
531 } = self;
532 if *image_size_ref != image_size {
534 *bind_groups = Default::default();
535 }
536 *image_size_ref = image_size;
537 tile_tapes
538 .grow_to_fit(device, Self::tile_tapes_buf_size(render_size))
539 .map_err(|err| BufferError {
540 buf: BufferName::TileTapes,
541 err,
542 })?;
543 tile64
544 .grow_to_fit(device, render_size)
545 .map_err(|e| BufferError {
546 buf: BufferName::Tile64(e.buf),
547 err: e.err,
548 })?;
549 tile8
550 .grow_to_fit(device, render_size)
551 .map_err(|e| BufferError {
552 buf: BufferName::Tile8(e.buf),
553 err: e.err,
554 })?;
555 pixels
556 .grow_to_fit(device, image_size)
557 .map_err(|err| BufferError {
558 buf: BufferName::Pixel,
559 err,
560 })?;
561 Ok(())
562 }
563
564 pub fn capacity(&self) -> u64 {
566 let Workspace {
568 image_size: _,
569 config_buf,
570 vars_buf,
571 tile_tapes,
572 tile64,
573 tile8,
574 pixels,
575 bind_groups: _,
576 } = self;
577 config_buf.size()
578 + vars_buf.capacity()
579 + tile_tapes.capacity()
580 + tile64.capacity()
581 + tile8.capacity()
582 + pixels.capacity()
583 }
584
585 pub fn size(&self) -> u64 {
587 let Workspace {
589 image_size: _,
590 vars_buf,
591 config_buf,
592 tile_tapes,
593 tile64,
594 tile8,
595 pixels,
596 bind_groups: _,
597 } = self;
598 config_buf.size()
599 + vars_buf.size_bytes()
600 + tile_tapes.size_bytes()
601 + tile64.size()
602 + tile8.size()
603 + pixels.size_bytes()
604 }
605
606 pub fn output(&mut self) -> &FlexBuffer<PixelBufferTag> {
613 &self.pixels
614 }
615}
616
617#[derive(Debug, thiserror::Error)]
619#[error("failed to build {buf} buffer")]
620pub struct BufferError {
621 pub buf: BufferName,
623 #[source]
625 pub err: BufferSizeError,
626}
627
628#[derive(Debug)]
630pub enum BufferName {
631 Tile64(TileBufferName),
633 Tile8(TileBufferName),
635 TileTapes,
637 Pixel,
639 Image,
641 Vars,
643}
644
645impl std::fmt::Display for BufferName {
646 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
647 match self {
648 BufferName::Tile64(buf) => write!(f, "`{buf}` tile64"),
649 BufferName::Tile8(buf) => write!(f, "`{buf}` tile8"),
650 BufferName::TileTapes => write!(f, "`tile tapes`"),
651 BufferName::Pixel => write!(f, "`pixel`"),
652 BufferName::Image => write!(f, "`image`"),
653 BufferName::Vars => write!(f, "`vars`"),
654 }
655 }
656}
657
658#[derive(Debug, thiserror::Error)]
660pub enum SubmitError {
661 #[error(transparent)]
663 MissingVar(#[from] MissingVar),
664 #[error(transparent)]
666 Buffers(#[from] BufferError),
667}
668
669impl From<CopyVarsError> for SubmitError {
670 fn from(value: CopyVarsError) -> Self {
671 match value {
672 CopyVarsError::MissingVar(v) => Self::MissingVar(v),
673 CopyVarsError::BufferSize(err) => Self::Buffers(BufferError {
674 buf: BufferName::Vars,
675 err,
676 }),
677 }
678 }
679}
680
681#[derive(Default)]
683struct BindGroups {
684 common: std::cell::OnceCell<wgpu::BindGroup>,
685 root_tiles: std::cell::OnceCell<wgpu::BindGroup>,
686 interval_tiles: std::cell::OnceCell<wgpu::BindGroup>,
687 pixel_tiles: std::cell::OnceCell<wgpu::BindGroup>,
688 merge: std::cell::OnceCell<wgpu::BindGroup>,
689}
690
691impl BindGroups {
692 fn common(&self, ctx: &Context, workspace: &Workspace) -> &wgpu::BindGroup {
693 self.common.get_or_init(|| {
694 ctx.gpu
695 .device
696 .create_bind_group(&wgpu::BindGroupDescriptor {
697 label: Some("common bind group"),
698 layout: &ctx.common_bind_group_layout,
699 entries: &[
700 wgpu::BindGroupEntry {
701 binding: 0,
702 resource: workspace.config_buf.as_entire_binding(),
703 },
704 wgpu::BindGroupEntry {
705 binding: 1,
706 resource: workspace.tile_tapes.bind_active(),
707 },
708 wgpu::BindGroupEntry {
709 binding: 2,
710 resource: workspace.vars_buf.bind_active(),
711 },
712 ],
713 })
714 })
715 }
716
717 fn root_tiles(
718 &self,
719 ctx: &Context,
720 workspace: &Workspace,
721 ) -> &wgpu::BindGroup {
722 self.root_tiles.get_or_init(|| {
723 ctx.gpu
724 .device
725 .create_bind_group(&wgpu::BindGroupDescriptor {
726 label: Some("interval root bind group"),
727 layout: &ctx.root_ctx.bind_group_layout,
728 entries: &[
729 wgpu::BindGroupEntry {
730 binding: 0,
731 resource: workspace.tile64.tiles.bind_active(),
732 },
733 wgpu::BindGroupEntry {
734 binding: 1,
735 resource: workspace.tile64.values.bind_active(),
736 },
737 ],
738 })
739 })
740 }
741
742 fn interval_tiles(
743 &self,
744 ctx: &Context,
745 workspace: &Workspace,
746 ) -> &wgpu::BindGroup {
747 self.interval_tiles.get_or_init(|| {
748 ctx.gpu
749 .device
750 .create_bind_group(&wgpu::BindGroupDescriptor {
751 label: Some("interval tiles bind group"),
752 layout: &ctx.tiles_ctx.bind_group_layout,
753 entries: &[
754 wgpu::BindGroupEntry {
755 binding: 0,
756 resource: workspace.tile64.tiles.bind_active(),
757 },
758 wgpu::BindGroupEntry {
759 binding: 1,
760 resource: workspace.tile8.tiles.bind_active(),
761 },
762 wgpu::BindGroupEntry {
763 binding: 2,
764 resource: workspace.tile8.values.bind_active(),
765 },
766 ],
767 })
768 })
769 }
770
771 fn pixel_tiles(
772 &self,
773 ctx: &Context,
774 workspace: &Workspace,
775 ) -> &wgpu::BindGroup {
776 self.pixel_tiles.get_or_init(|| {
777 ctx.gpu
778 .device
779 .create_bind_group(&wgpu::BindGroupDescriptor {
780 label: Some("pixel tiles bind group"),
781 layout: &ctx.pixels_ctx.bind_group_layout,
782 entries: &[
783 wgpu::BindGroupEntry {
784 binding: 0,
785 resource: workspace.tile8.tiles.bind_active(),
786 },
787 wgpu::BindGroupEntry {
788 binding: 1,
789 resource: workspace.pixels.bind_active(),
790 },
791 ],
792 })
793 })
794 }
795
796 fn merge(&self, ctx: &Context, workspace: &Workspace) -> &wgpu::BindGroup {
797 self.merge.get_or_init(|| {
798 ctx.gpu
799 .device
800 .create_bind_group(&wgpu::BindGroupDescriptor {
801 label: Some("merge bind group"),
802 layout: &ctx.merge_ctx.bind_group_layout,
803 entries: &[
804 wgpu::BindGroupEntry {
805 binding: 0,
806 resource: workspace.tile64.values.bind_active(),
807 },
808 wgpu::BindGroupEntry {
809 binding: 1,
810 resource: workspace.tile8.values.bind_active(),
811 },
812 wgpu::BindGroupEntry {
813 binding: 2,
814 resource: workspace.pixels.bind_active(),
815 },
816 ],
817 })
818 })
819 }
820}
821
822tag!(TilesBufferTag, u32, usize, STORAGE | COPY_DST | INDIRECT);
825tag!(ValuesBufferTag, u32, ImageSize, STORAGE | COPY_DST);
826
827struct TileBuffers<const N: usize> {
829 tiles: FlexBuffer<TilesBufferTag>,
831
832 values: FlexBuffer<ValuesBufferTag>,
834}
835
836#[derive(Debug, thiserror::Error)]
838#[error("failed to resize `{buf}` root tile buffer")]
839pub struct TileBufferError {
840 pub buf: TileBufferName,
842 #[source]
844 pub err: BufferSizeError,
845}
846
847#[derive(Debug)]
849#[expect(missing_docs)]
850pub enum TileBufferName {
851 Tiles,
852 Values,
853}
854
855impl std::fmt::Display for TileBufferName {
856 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
857 let s = match self {
858 TileBufferName::Tiles => "tiles",
859 TileBufferName::Values => "values",
860 };
861 s.fmt(f)
862 }
863}
864
865impl<const N: usize> TileBuffers<N> {
866 fn new(
868 device: &wgpu::Device,
869 render_size: TileRenderSize,
870 ) -> Result<Self, TileBufferError> {
871 let tiles = FlexBuffer::new(
873 device,
874 format!("tiles_out{N}"),
875 Self::tiles_buf_size(render_size),
876 )
877 .map_err(|err| TileBufferError {
878 buf: TileBufferName::Tiles,
879 err,
880 })?;
881
882 let values_buf_size = Self::values_buf_size(render_size);
883 let values =
884 FlexBuffer::new(device, format!("tile{N}_values"), values_buf_size)
885 .map_err(|err| TileBufferError {
886 buf: TileBufferName::Values,
887 err,
888 })?;
889 Ok(Self { tiles, values })
890 }
891
892 fn tiles_buf_size(render_size: TileRenderSize) -> usize {
893 let nx = usize::try_from(render_size.nx()).unwrap();
894 let ny = usize::try_from(render_size.ny()).unwrap();
895 4 + nx
898 .checked_mul(ny)
899 .unwrap()
900 .checked_mul((64 / N) * (64 / N))
901 .unwrap()
902 }
903
904 fn values_buf_size(render_size: TileRenderSize) -> ImageSize {
905 ImageSize::new(
906 render_size.nx().checked_mul(64 / N as u32).unwrap(),
907 render_size.ny().checked_mul(64 / N as u32).unwrap(),
908 )
909 }
910
911 fn grow_to_fit(
913 &mut self,
914 device: &wgpu::Device,
915 render_size: TileRenderSize,
916 ) -> Result<(), TileBufferError> {
917 let TileBuffers { tiles, values } = self;
919 tiles
920 .grow_to_fit(device, Self::tiles_buf_size(render_size))
921 .map_err(|err| TileBufferError {
922 buf: TileBufferName::Tiles,
923 err,
924 })?;
925 values
926 .grow_to_fit(device, Self::values_buf_size(render_size))
927 .map_err(|err| TileBufferError {
928 buf: TileBufferName::Values,
929 err,
930 })?;
931
932 Ok(())
933 }
934
935 pub fn size(&self) -> u64 {
937 let TileBuffers { tiles, values } = self;
939 tiles.size_bytes() + values.size_bytes()
940 }
941
942 pub fn capacity(&self) -> u64 {
944 let TileBuffers { tiles, values } = self;
946 tiles.capacity() + values.capacity()
947 }
948}
949
950pub struct Context {
954 gpu: Gpu,
955
956 common_bind_group_layout: wgpu::BindGroupLayout,
958
959 root_ctx: RootContext,
961
962 tiles_ctx: IntervalTilesContext,
964
965 pixels_ctx: PixelTilesContext,
967
968 reset_ctx: ResetContext,
970
971 merge_ctx: MergeContext,
973}
974
975impl Context {
976 pub fn new(gpu: &Gpu) -> Self {
981 let common_bind_group_layout = gpu.device.create_bind_group_layout(
983 &wgpu::BindGroupLayoutDescriptor {
984 label: Some("common bind group layout"),
985 entries: &[
986 buffer_rw(0), buffer_rw(1), buffer_ro(2), ],
990 },
991 );
992 let root_ctx = RootContext::new(&gpu.device, &common_bind_group_layout);
993 let tiles_ctx =
994 IntervalTilesContext::new(&gpu.device, &common_bind_group_layout);
995 let pixels_ctx =
996 PixelTilesContext::new(&gpu.device, &common_bind_group_layout);
997 let merge_ctx =
998 MergeContext::new(&gpu.device, &common_bind_group_layout);
999
1000 Self {
1001 gpu: gpu.clone(),
1002 common_bind_group_layout,
1003 root_ctx,
1004 tiles_ctx,
1005 pixels_ctx,
1006 merge_ctx,
1007 reset_ctx: ResetContext,
1008 }
1009 }
1010
1011 pub fn workspace(&self) -> Workspace {
1017 Workspace::new(&self.gpu.device)
1018 }
1019
1020 #[cfg(not(target_arch = "wasm32"))]
1024 pub fn run(
1025 &self,
1026 shape: &RenderShape,
1027 workspace: &mut Workspace,
1028 out: &mut ReadBuffer<PixelBufferTag>,
1029 settings: RenderConfig,
1030 ) -> Result<Image, SubmitError> {
1031 self.run_with_vars(shape, &Default::default(), workspace, out, settings)
1032 }
1033
1034 #[cfg(not(target_arch = "wasm32"))]
1038 pub fn run_with_vars(
1039 &self,
1040 shape: &RenderShape,
1041 vars: &ShapeVars<f32>,
1042 workspace: &mut Workspace,
1043 out: &mut ReadBuffer<PixelBufferTag>,
1044 settings: RenderConfig,
1045 ) -> Result<Image, SubmitError> {
1046 self.submit_with_vars(shape, vars, workspace, &settings)?;
1047 self.gpu.copy(workspace.output(), out);
1048 let image = self.gpu.map_image(out);
1049 Ok(image.image())
1050 }
1051
1052 pub async fn run_async(
1054 &self,
1055 shape: &RenderShape,
1056 workspace: &mut Workspace,
1057 out: &mut ReadBuffer<PixelBufferTag>,
1058 settings: RenderConfig,
1059 ) -> Result<Image, SubmitError> {
1060 self.run_with_vars_async(
1061 shape,
1062 &Default::default(),
1063 workspace,
1064 out,
1065 settings,
1066 )
1067 .await
1068 }
1069
1070 pub async fn run_with_vars_async(
1072 &self,
1073 shape: &RenderShape,
1074 vars: &ShapeVars<f32>,
1075 workspace: &mut Workspace,
1076 out: &mut ReadBuffer<PixelBufferTag>,
1077 settings: RenderConfig,
1078 ) -> Result<Image, SubmitError> {
1079 self.submit_with_vars(shape, vars, workspace, &settings)?;
1080 self.gpu.copy(workspace.output(), out);
1081 let image = self.gpu.map_image_async(out).await;
1082 Ok(image.image())
1083 }
1084
1085 pub fn submit(
1090 &self,
1091 shape: &RenderShape,
1092 workspace: &mut Workspace,
1093 settings: &RenderConfig,
1094 ) -> Result<(), SubmitError> {
1095 self.submit_with_vars(shape, &Default::default(), workspace, settings)
1096 }
1097
1098 pub fn submit_with_vars(
1102 &self,
1103 shape: &RenderShape,
1104 vars: &ShapeVars<f32>,
1105 workspace: &mut Workspace,
1106 settings: &RenderConfig,
1107 ) -> Result<(), SubmitError> {
1108 workspace.set_image_size(&self.gpu.device, settings.image_size)?;
1109 let render_size = TileRenderSize::from(workspace.image_size);
1110
1111 let mat =
1114 settings.world_to_model * workspace.image_size.screen_to_world();
1115 let mut mat4 = nalgebra::Matrix4x3::<f32>::identity();
1116 mat4.fixed_view_mut::<3, 3>(0, 0).copy_from(&mat);
1117
1118 let start_offset = u32::try_from(shape.bytecode.len()).unwrap() / 2;
1120 let config = Config {
1121 mat: mat4.data.as_slice().try_into().unwrap(),
1122 axes: shape.axes(),
1123 render_size: [render_size.width(), render_size.height()],
1124 tape_data_capacity: TAPE_DATA_CAPACITY.try_into().unwrap(),
1125 image_size: [
1126 workspace.image_size.width(),
1127 workspace.image_size.height(),
1128 ],
1129 tape_data_offset: start_offset,
1130 z: settings.z,
1131 pixel_perfect: settings.pixel_perfect as u32,
1132 _pad: 0,
1133 };
1134
1135 {
1136 let config_len = std::mem::size_of_val(&config);
1138 let mut writer = self
1139 .gpu
1140 .queue
1141 .write_buffer_with(
1142 &workspace.config_buf,
1143 0,
1144 ((config_len + shape.bytecode.as_bytes().len()) as u64)
1145 .try_into()
1146 .unwrap(),
1147 )
1148 .unwrap();
1149 writer
1150 .slice(..config_len)
1151 .copy_from_slice(config.as_bytes());
1152 writer
1153 .slice(config_len..)
1154 .copy_from_slice(shape.bytecode.as_bytes());
1155 }
1156
1157 if matches!(
1160 shape.copy_vars(&self.gpu, vars, &mut workspace.vars_buf)?,
1161 CopyVarsChanged::BufferChanged
1162 ) {
1163 workspace.bind_groups.common = Default::default();
1164 }
1165
1166 let mut encoder = self.gpu.device.create_command_encoder(
1168 &wgpu::CommandEncoderDescriptor { label: None },
1169 );
1170
1171 self.reset_ctx.run(&mut encoder, workspace);
1173
1174 let mut compute_pass =
1175 encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
1176 label: None,
1177 timestamp_writes: None,
1178 });
1179
1180 let common_bind_group = workspace.bind_groups.common(self, workspace);
1182 compute_pass.set_bind_group(0, common_bind_group, &[]);
1183
1184 self.root_ctx.run(
1186 self,
1187 workspace,
1188 shape.bytecode.reg_count(),
1189 render_size,
1190 &mut compute_pass,
1191 );
1192 self.tiles_ctx.run(
1193 self,
1194 workspace,
1195 shape.bytecode.reg_count(),
1196 &mut compute_pass,
1197 );
1198 self.pixels_ctx.run(
1199 self,
1200 workspace,
1201 shape.bytecode.reg_count(),
1202 &mut compute_pass,
1203 );
1204
1205 self.merge_ctx.run(
1207 self,
1208 workspace,
1209 settings.image_size,
1210 &mut compute_pass,
1211 );
1212 drop(compute_pass);
1213
1214 self.gpu.queue.submit(Some(encoder.finish()));
1216 Ok(())
1217 }
1218}
1219
1220struct MergeContext {
1223 bind_group_layout: wgpu::BindGroupLayout,
1224 pipeline: wgpu::ComputePipeline,
1225}
1226
1227impl MergeContext {
1228 fn new(
1229 device: &wgpu::Device,
1230 common_bind_group_layout: &wgpu::BindGroupLayout,
1231 ) -> Self {
1232 let shader_code = merge_shader();
1233
1234 let bind_group_layout =
1236 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1237 label: Some("merge bind group layout"),
1238 entries: &[
1239 buffer_ro(0), buffer_ro(1), buffer_rw(2), ],
1243 });
1244
1245 let pipeline_layout =
1247 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1248 label: Some("merge pipeline layout"),
1249 bind_group_layouts: &[
1250 Some(common_bind_group_layout),
1251 Some(&bind_group_layout),
1252 ],
1253 immediate_size: 0u32,
1254 });
1255
1256 let shader_module =
1258 device.create_shader_module(wgpu::ShaderModuleDescriptor {
1259 label: Some("merge shader module"),
1260 source: wgpu::ShaderSource::Wgsl(shader_code.into()),
1261 });
1262
1263 let pipeline =
1264 device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
1265 label: Some("merge"),
1266 layout: Some(&pipeline_layout),
1267 module: &shader_module,
1268 entry_point: Some("merge_main"),
1269 compilation_options: Default::default(),
1270 cache: None,
1271 });
1272
1273 Self {
1274 pipeline,
1275 bind_group_layout,
1276 }
1277 }
1278
1279 fn run(
1280 &self,
1281 ctx: &Context,
1282 workspace: &Workspace,
1283 render_size: ImageSize,
1284 compute_pass: &mut wgpu::ComputePass,
1285 ) {
1286 let bind_group = workspace.bind_groups.merge(ctx, workspace);
1287 compute_pass.set_pipeline(&self.pipeline);
1288 compute_pass.set_bind_group(1, bind_group, &[]);
1289 compute_pass.dispatch_workgroups(
1290 render_size.width().div_ceil(8),
1291 render_size.height().div_ceil(8),
1292 1,
1293 );
1294 }
1295}
1296struct ResetContext;
1299
1300impl ResetContext {
1301 fn run(&self, encoder: &mut wgpu::CommandEncoder, workspace: &Workspace) {
1302 encoder.clear_buffer(workspace.tile64.tiles.data(), 0, Some(16));
1304 encoder.clear_buffer(workspace.tile8.tiles.data(), 0, Some(16));
1305 workspace.tile64.values.clear(encoder);
1306 workspace.tile8.values.clear(encoder);
1307 workspace.pixels.clear(encoder);
1308
1309 workspace.tile_tapes.clear(encoder);
1311 }
1312}
1313
1314#[cfg(test)]
1317mod test {
1318 use super::effects::ColorSettings;
1319 use super::*;
1320 use crate::color::{ShapeColor, ShapeColorBuffers};
1321
1322 use fidget_core::{context::Tree, vm::VmShape};
1323
1324 #[test]
1325 fn compile_interval_root_shader() {
1326 crate::compile_shader(&interval_root_shader(16), "interval root");
1327 }
1328
1329 #[test]
1330 fn compile_interval_tiles_shader() {
1331 crate::compile_shader(&interval_tiles_shader(16), "interval tiles");
1332 }
1333
1334 #[test]
1335 fn compile_pixel_tiles_shader() {
1336 crate::compile_shader(&pixel_tiles_shader(16), "pixel tiles");
1337 }
1338
1339 #[test]
1340 fn compile_merge_shader() {
1341 crate::compile_shader(&merge_shader(), "merge");
1342 }
1343
1344 struct RenderOutput {
1345 distance: Image,
1346 color: fidget_raster::Image<u32, ImageSize>,
1347 }
1348
1349 fn render(
1350 shapes: &[(Tree, ShapeColor<Tree>)],
1351 render_config: RenderConfig,
1352 vars: &fidget_core::shape::ShapeVars<f32>,
1353 ) -> RenderOutput {
1354 let gpu = pollster::block_on(Gpu::init_basic()).unwrap();
1355 let pixel_ctx = Context::new(&gpu);
1356 let effects_ctx = effects::Context::new(&gpu);
1357
1358 let mut buf = pixel_ctx.workspace();
1359 let mut merge_buf = effects_ctx.merge_workspace();
1360
1361 for (shape, _) in shapes {
1363 let shape =
1364 RenderShape::new(&VmShape::from(shape.clone())).unwrap();
1365 pixel_ctx
1366 .submit_with_vars(&shape, vars, &mut buf, &render_config)
1367 .unwrap();
1368 effects_ctx
1369 .submit_merge(buf.output(), true, &mut merge_buf)
1370 .unwrap();
1371 }
1372 let shape_colors = shapes
1373 .iter()
1374 .map(|(_, c)| match c {
1375 ShapeColor::Rgb { r, g, b } => ShapeColor::Rgb {
1376 r: VmShape::from(r.clone()),
1377 g: VmShape::from(g.clone()),
1378 b: VmShape::from(b.clone()),
1379 },
1380 ShapeColor::Hsl { h, s, l } => ShapeColor::Hsl {
1381 h: VmShape::from(h.clone()),
1382 s: VmShape::from(s.clone()),
1383 l: VmShape::from(l.clone()),
1384 },
1385 })
1386 .collect::<Vec<_>>();
1387 let shape_colors = ShapeColorBuffers::new(&shape_colors).unwrap();
1388 let mut color_workspace = effects_ctx.color_workspace();
1389
1390 effects_ctx
1392 .submit_color_with_vars(
1393 &mut merge_buf,
1394 ColorSettings {
1395 z: 0.0,
1396 only_filled: false,
1397 world_to_model: render_config.world_to_model,
1398 },
1399 &shape_colors,
1400 &mut color_workspace,
1401 vars,
1402 )
1403 .unwrap();
1404
1405 let mut distance_out = gpu.read_buffer_for(merge_buf.output_distance());
1406 let mut color_out =
1407 gpu.read_buffer_for(merge_buf.output_color().unwrap());
1408 gpu.copy(merge_buf.output_distance(), &mut distance_out);
1409 gpu.copy(merge_buf.output_color().unwrap(), &mut color_out);
1410
1411 RenderOutput {
1412 color: gpu.map_image(&mut color_out).image(),
1413 distance: gpu.map_image(&mut distance_out).image(),
1414 }
1415 }
1416
1417 #[test]
1418 fn pixel_pipeline() {
1419 #[cfg(not(target_os = "macos"))]
1422 if std::env::var("CI").is_ok() {
1423 return;
1424 }
1425
1426 let (x, y, _z) = Tree::axes();
1427 let circle = (x.square() + y.square()).sqrt() - Tree::constant(0.5);
1428
1429 for image_size in [
1431 RenderSize::new(64, 64),
1432 RenderSize::new(128, 64),
1433 RenderSize::new(64, 128),
1434 RenderSize::new(27, 51),
1435 ] {
1436 let out = render(
1437 &[(
1438 circle.clone(),
1439 ShapeColor::Rgb {
1440 r: Tree::x(),
1441 g: Tree::y(),
1442 b: Tree::constant(0.5),
1443 },
1444 )],
1445 RenderConfig {
1446 image_size,
1447 world_to_model: nalgebra::Matrix3::identity(),
1448 pixel_perfect: false,
1449 z: 0.0,
1450 },
1451 &Default::default(),
1452 );
1453 assert_eq!(out.color.size(), image_size);
1454 assert_eq!(out.distance.size(), image_size);
1455
1456 let mat = image_size.screen_to_world();
1458 for j in 0..image_size.height() {
1459 for i in 0..image_size.width() {
1460 let pos = mat.transform_point(&nalgebra::Point2::new(
1461 i as f32, j as f32,
1462 ));
1463 let p = out.distance[(j as usize, i as usize)];
1464 let r = (pos.x.powi(2) + pos.y.powi(2)).sqrt();
1465 if r < 0.5 {
1466 assert!(
1467 p.inside(),
1468 "pixel should be inside at pixel ({i}, {j}) \
1469 (pos {pos}) with radius {r}"
1470 );
1471 } else {
1472 assert!(
1473 !p.inside(),
1474 "pixel should be outside at pixel ({i}, {j}) \
1475 (pos {pos}) with radius {r}"
1476 );
1477 }
1478 }
1479 }
1480
1481 for j in 0..image_size.height() {
1482 for i in 0..image_size.width() {
1483 let pos = mat.transform_point(&nalgebra::Point2::new(
1484 i as f32, j as f32,
1485 ));
1486 let p = out.color[(j as usize, i as usize)];
1487 let r = (pos.x.powi(2) + pos.y.powi(2)).sqrt();
1488 let alpha = if r < 0.5 { 255 } else { 0 };
1489 let expected_color = u32::from_ne_bytes([
1490 (pos.x.clamp(0.0, 1.0) * 255.0) as u8,
1491 (pos.y.clamp(0.0, 1.0) * 255.0) as u8,
1492 127,
1493 alpha,
1494 ]);
1495 assert_eq!(
1496 p, expected_color,
1497 "color mismatch at {i}, {j} ({pos})"
1498 );
1499 }
1500 }
1501 }
1502 }
1503
1504 #[test]
1505 fn pixel_multiple_images() {
1506 #[cfg(not(target_os = "macos"))]
1509 if std::env::var("CI").is_ok() {
1510 return;
1511 }
1512
1513 let circle_a = ((Tree::x() - 0.5).square() + Tree::y().square()).sqrt()
1514 - Tree::constant(0.25);
1515 let circle_b = ((Tree::x() + 0.5).square() + Tree::y().square()).sqrt()
1516 - Tree::constant(0.25);
1517
1518 let image_size = RenderSize::new(64, 64);
1520 let out = render(
1521 &[
1522 (
1523 circle_a,
1524 ShapeColor::Rgb {
1525 r: Tree::constant(0.0),
1526 g: Tree::constant(0.0),
1527 b: Tree::constant(1.0),
1528 },
1529 ),
1530 (
1531 circle_b,
1532 ShapeColor::Rgb {
1533 r: Tree::constant(0.0),
1534 g: Tree::constant(1.0),
1535 b: Tree::constant(0.0),
1536 },
1537 ),
1538 ],
1539 RenderConfig {
1540 image_size,
1541 world_to_model: nalgebra::Matrix3::identity(),
1542 pixel_perfect: false,
1543 z: 0.0,
1544 },
1545 &Default::default(),
1546 );
1547 assert_eq!(out.color.size(), image_size);
1548 assert_eq!(out.distance.size(), image_size);
1549
1550 let mut pixels = String::new();
1551 for j in 0..image_size.height() {
1552 for i in 0..image_size.width() {
1553 let p = out.color[(j as usize, i as usize)];
1554 let c = match p.to_ne_bytes() {
1555 [0, 0, 255, 0] => "b",
1556 [0, 0, 255, 255] => "B",
1557 [0, 255, 0, 0] => "g",
1558 [0, 255, 0, 255] => "G",
1559 _ => panic!("invalid color {p:?}"),
1560 };
1561 pixels += c;
1562 }
1563 pixels += "\n";
1564 }
1565
1566 assert_eq!(
1567 pixels,
1568 "\
1569 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1570 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1571 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1572 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1573 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1574 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1575 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1576 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1577 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1578 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1579 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1580 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1581 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1582 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1583 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1584 gggggggggggggggggggggggggggggggggggggggbbbbbbbbbbbbbbbbbbggggggg
1585 gggggggggggggggggggggggggggggggggggggggbbbbbbbbbbbbbbbbbbggggggg
1586 gggggggggggggggggggggggggggggggggggggggbbbbbbbbbbbbbbbbbbggggggg
1587 gggggggggggggggggggggggggggggggggggggggbbbbbbbbbbbbbbbbbbggggggg
1588 gggggggggggggggggggggggggggggggggggggggbbbbbbbbbbbbbbbbbbggggggg
1589 gggggggggggggggggggggggggggggggggggggggbbbbbbbbbbbbbbbbbbggggggg
1590 gggggggggggggggggggggggggggggggggggggggbbbbbbbbbbbbbbbbbbggggggg
1591 gggggggggggggggggggggggggggggggggggggggbbbbbbbbbbbbbbbbbbggggggg
1592 gggggggggggggggggggggggggggggggggbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
1593 gggggggggggggGGGGGGGgggggggggggggbbbbbbbbbbbbBBBBBBBbbbbbbbbbbbb
1594 gggggggggggGGGGGGGGGGGgggggggggggbbbbbbbbbbBBBBBBBBBBBbbbbbbbbbb
1595 ggggggggggGGGGGGGGGGGGGggggggggggbbbbbbbbbBBBBBBBBBBBBBbbbbbbbbb
1596 ggggggggggGGGGGGGGGGGGGggggggggggbbbbbbbbbBBBBBBBBBBBBBbbbbbbbbb
1597 gggggggggGGGGGGGGGGGGGGGgggggggggbbbbbbbbBBBBBBBBBBBBBBBbbbbbbbb
1598 gggggggggGGGGGGGGGGGGGGGgggggggggbbbbbbbbBBBBBBBBBBBBBBBbbbbbbbb
1599 gggggggggGGGGGGGGGGGGGGGgggggggggbbbbbbbbBBBBBBBBBBBBBBBbbbbbbbb
1600 gggggggggGGGGGGGGGGGGGGGgggggggggbbbbbbbbBBBBBBBBBBBBBBBbbbbbbbb
1601 gggggggggGGGGGGGGGGGGGGGgggggggggbbbbbbbbBBBBBBBBBBBBBBBbbbbbbbb
1602 gggggggggGGGGGGGGGGGGGGGgggggggggggggggbbBBBBBBBBBBBBBBBgggggggg
1603 gggggggggGGGGGGGGGGGGGGGgggggggggggggggbbBBBBBBBBBBBBBBBbggggggg
1604 ggggggggggGGGGGGGGGGGGGggggggggggggggggbbbBBBBBBBBBBBBBbbggggggg
1605 ggggggggggGGGGGGGGGGGGGggggggggggggggggbbbBBBBBBBBBBBBBbbggggggg
1606 gggggggggggGGGGGGGGGGGgggggggggggggggggbbbbBBBBBBBBBBBbbbggggggg
1607 gggggggggggggGGGGGGGgggggggggggggggggggbbbbbbBBBBBBBbbbbbggggggg
1608 gggggggggggggggggggggggggggggggggggggggbbbbbbbbbbbbbbbbbbggggggg
1609 gggggggggggggggggggggggggggggggggggggggbbbbbbbbbbbbbbbbbbggggggg
1610 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1611 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1612 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1613 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1614 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1615 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1616 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1617 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1618 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1619 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1620 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1621 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1622 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1623 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1624 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1625 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1626 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1627 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1628 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1629 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1630 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1631 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1632 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1633 "
1634 .replace(" ", "")
1635 );
1636 }
1637
1638 #[test]
1639 fn pixel_vars() {
1640 #[cfg(not(target_os = "macos"))]
1643 if std::env::var("CI").is_ok() {
1644 return;
1645 }
1646
1647 let va = fidget_core::var::Var::new();
1648 let vb = fidget_core::var::Var::new();
1649 let vc = fidget_core::var::Var::new();
1650 let vd = fidget_core::var::Var::new();
1651 let circle_a = ((Tree::x() - 0.5).square() + Tree::y().square()).sqrt()
1652 - Tree::from(va);
1653 let circle_b = ((Tree::x() + va).square() + Tree::y().square()).sqrt()
1654 - Tree::from(vb);
1655
1656 let image_size = RenderSize::new(64, 64);
1658 let mut vars = fidget_core::shape::ShapeVars::new();
1659 vars.insert(va.index().unwrap(), 0.25);
1660 vars.insert(vb.index().unwrap(), 0.55);
1661 vars.insert(vc.index().unwrap(), 0.75);
1662 vars.insert(vd.index().unwrap(), 0.5);
1663 let out = render(
1664 &[
1665 (
1666 circle_a,
1667 ShapeColor::Rgb {
1668 r: Tree::constant(0.0),
1669 g: Tree::constant(0.0),
1670 b: Tree::from(vc),
1671 },
1672 ),
1673 (
1674 circle_b,
1675 ShapeColor::Rgb {
1676 r: Tree::constant(0.0),
1677 g: Tree::from(vd),
1678 b: Tree::constant(0.0),
1679 },
1680 ),
1681 ],
1682 RenderConfig {
1683 image_size,
1684 world_to_model: nalgebra::Matrix3::identity(),
1685 pixel_perfect: false,
1686 z: 0.0,
1687 },
1688 &vars,
1689 );
1690 assert_eq!(out.color.size(), image_size);
1691 assert_eq!(out.distance.size(), image_size);
1692
1693 let mut pixels = String::new();
1694 for j in 0..image_size.height() {
1695 for i in 0..image_size.width() {
1696 let p = out.color[(j as usize, i as usize)];
1697 let c = match p.to_ne_bytes() {
1698 [0, 0, 191, 0] => "b",
1699 [0, 0, 191, 255] => "B",
1700 [0, 127, 0, 0] => "g",
1701 [0, 127, 0, 255] => "G",
1702 _ => panic!("invalid color {:?}", p.to_ne_bytes()),
1703 };
1704 pixels += c;
1705 }
1706 pixels += "\n";
1707 }
1708
1709 assert_eq!(
1710 pixels,
1711 "\
1712 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1713 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1714 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1715 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1716 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1717 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1718 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1719 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1720 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1721 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1722 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1723 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1724 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1725 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1726 ggggggggggggggggggggGGGGGGGGGggggggggggggggggggggggggggggggggggg
1727 gggggggggggggggggGGGGGGGGGGGGGGGgggggggggggggbbbbbbbbbbbbggggggg
1728 gggggggggggggggGGGGGGGGGGGGGGGGGGGgggggggggggbbbbbbbbbbbbggggggg
1729 ggggggggggggggGGGGGGGGGGGGGGGGGGGGGgggggggggbbbbbbbbbbbbbggggggg
1730 gggggggggggggGGGGGGGGGGGGGGGGGGGGGGGggggggggbbbbbbbbbbbbbggggggg
1731 ggggggggggggGGGGGGGGGGGGGGGGGGGGGGGGGgggggggbbbbbbbbbbbbbggggggg
1732 gggggggggggGGGGGGGGGGGGGGGGGGGGGGGGGGGgggggbbbbbbbbbbbbbbggggggg
1733 ggggggggggGGGGGGGGGGGGGGGGGGGGGGGGGGGGGggggbbbbbbbbbbbbbbggggggg
1734 gggggggggGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGgggbbbbbbbbbbbbbbggggggg
1735 gggggggggGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGggbbbbbbbbbbbbbbbbbbbbbb
1736 ggggggggGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGgbbbBBBBBBBbbbbbbbbbbbb
1737 ggggggggGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGgbBBBBBBBBBBBbbbbbbbbbb
1738 ggggggggGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGgBBBBBBBBBBBBBbbbbbbbbb
1739 gggggggGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGBBBBBBBBBBBBBbbbbbbbbb
1740 gggggggGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGBBBBBBBBBBBBBBbbbbbbbb
1741 gggggggGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGBBBBBBBBBBBBBBbbbbbbbb
1742 gggggggGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGBBBBBBBBBBBBBBbbbbbbbb
1743 gggggggGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGBBBBBBBBBBBBBBbbbbbbbb
1744 gggggggGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGBBBBBBBBBBBBBBbbbbbbbb
1745 gggggggGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGBBBBBBBBBBBBBBgggggggg
1746 gggggggGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGBBBBBBBBBBBBBBbggggggg
1747 gggggggGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGBBBBBBBBBBBBBbbggggggg
1748 ggggggggGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGgBBBBBBBBBBBBBbbggggggg
1749 ggggggggGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGgbBBBBBBBBBBBbbbggggggg
1750 ggggggggGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGgbbbBBBBBBBbbbbbggggggg
1751 gggggggggGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGggbbbbbbbbbbbbbbbggggggg
1752 gggggggggGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGggbbbbbbbbbbbbbbbggggggg
1753 ggggggggggGGGGGGGGGGGGGGGGGGGGGGGGGGGGGggggggggggggggggggggggggg
1754 gggggggggggGGGGGGGGGGGGGGGGGGGGGGGGGGGgggggggggggggggggggggggggg
1755 ggggggggggggGGGGGGGGGGGGGGGGGGGGGGGGGggggggggggggggggggggggggggg
1756 gggggggggggggGGGGGGGGGGGGGGGGGGGGGGGgggggggggggggggggggggggggggg
1757 ggggggggggggggGGGGGGGGGGGGGGGGGGGGGggggggggggggggggggggggggggggg
1758 gggggggggggggggGGGGGGGGGGGGGGGGGGGgggggggggggggggggggggggggggggg
1759 gggggggggggggggggGGGGGGGGGGGGGGGgggggggggggggggggggggggggggggggg
1760 ggggggggggggggggggggGGGGGGGGGggggggggggggggggggggggggggggggggggg
1761 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1762 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1763 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1764 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1765 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1766 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1767 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1768 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1769 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1770 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1771 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1772 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1773 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1774 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1775 gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
1776 "
1777 .replace(" ", "")
1778 );
1779 }
1780
1781 #[test]
1782 fn pixel_hsl() {
1783 #[cfg(not(target_os = "macos"))]
1786 if std::env::var("CI").is_ok() {
1787 return;
1788 }
1789
1790 let circle_a = ((Tree::x() - 0.5).square() + Tree::y().square()).sqrt()
1791 - Tree::constant(0.25);
1792 let circle_b = ((Tree::x() + 0.5).square() + Tree::y().square()).sqrt()
1793 - Tree::constant(0.25);
1794
1795 let image_size = RenderSize::new(64, 64);
1797 let out = render(
1798 &[
1799 (
1800 circle_a,
1801 ShapeColor::Hsl {
1802 h: Tree::constant(0.0),
1803 s: Tree::constant(1.0),
1804 l: Tree::constant(0.5),
1805 },
1806 ),
1807 (
1808 circle_b,
1809 ShapeColor::Hsl {
1810 h: Tree::constant(0.5),
1811 s: Tree::constant(1.0),
1812 l: Tree::constant(0.5),
1813 },
1814 ),
1815 ],
1816 RenderConfig {
1817 image_size,
1818 world_to_model: nalgebra::Matrix3::identity(),
1819 pixel_perfect: false,
1820 z: 0.0,
1821 },
1822 &Default::default(),
1823 );
1824 assert_eq!(out.color.size(), image_size);
1825 assert_eq!(out.distance.size(), image_size);
1826
1827 let mut pixels = String::new();
1828 for j in 0..image_size.height() {
1829 for i in 0..image_size.width() {
1830 let p = out.color[(j as usize, i as usize)];
1831 let c = match p.to_ne_bytes() {
1832 [255, 0, 0, 0] => "r",
1833 [255, 0, 0, 255] => "R",
1834 [0, 255, 255, 0] => "c",
1835 [0, 255, 255, 255] => "C",
1836 _ => panic!("invalid color {:?}", p.to_ne_bytes()),
1837 };
1838 pixels += c;
1839 }
1840 pixels += "\n";
1841 }
1842
1843 assert_eq!(
1844 pixels,
1845 "\
1846 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1847 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1848 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1849 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1850 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1851 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1852 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1853 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1854 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1855 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1856 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1857 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1858 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1859 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1860 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1861 cccccccccccccccccccccccccccccccccccccccrrrrrrrrrrrrrrrrrrccccccc
1862 cccccccccccccccccccccccccccccccccccccccrrrrrrrrrrrrrrrrrrccccccc
1863 cccccccccccccccccccccccccccccccccccccccrrrrrrrrrrrrrrrrrrccccccc
1864 cccccccccccccccccccccccccccccccccccccccrrrrrrrrrrrrrrrrrrccccccc
1865 cccccccccccccccccccccccccccccccccccccccrrrrrrrrrrrrrrrrrrccccccc
1866 cccccccccccccccccccccccccccccccccccccccrrrrrrrrrrrrrrrrrrccccccc
1867 cccccccccccccccccccccccccccccccccccccccrrrrrrrrrrrrrrrrrrccccccc
1868 cccccccccccccccccccccccccccccccccccccccrrrrrrrrrrrrrrrrrrccccccc
1869 cccccccccccccccccccccccccccccccccrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr
1870 cccccccccccccCCCCCCCcccccccccccccrrrrrrrrrrrrRRRRRRRrrrrrrrrrrrr
1871 cccccccccccCCCCCCCCCCCcccccccccccrrrrrrrrrrRRRRRRRRRRRrrrrrrrrrr
1872 ccccccccccCCCCCCCCCCCCCccccccccccrrrrrrrrrRRRRRRRRRRRRRrrrrrrrrr
1873 ccccccccccCCCCCCCCCCCCCccccccccccrrrrrrrrrRRRRRRRRRRRRRrrrrrrrrr
1874 cccccccccCCCCCCCCCCCCCCCcccccccccrrrrrrrrRRRRRRRRRRRRRRRrrrrrrrr
1875 cccccccccCCCCCCCCCCCCCCCcccccccccrrrrrrrrRRRRRRRRRRRRRRRrrrrrrrr
1876 cccccccccCCCCCCCCCCCCCCCcccccccccrrrrrrrrRRRRRRRRRRRRRRRrrrrrrrr
1877 cccccccccCCCCCCCCCCCCCCCcccccccccrrrrrrrrRRRRRRRRRRRRRRRrrrrrrrr
1878 cccccccccCCCCCCCCCCCCCCCcccccccccrrrrrrrrRRRRRRRRRRRRRRRrrrrrrrr
1879 cccccccccCCCCCCCCCCCCCCCcccccccccccccccrrRRRRRRRRRRRRRRRcccccccc
1880 cccccccccCCCCCCCCCCCCCCCcccccccccccccccrrRRRRRRRRRRRRRRRrccccccc
1881 ccccccccccCCCCCCCCCCCCCccccccccccccccccrrrRRRRRRRRRRRRRrrccccccc
1882 ccccccccccCCCCCCCCCCCCCccccccccccccccccrrrRRRRRRRRRRRRRrrccccccc
1883 cccccccccccCCCCCCCCCCCcccccccccccccccccrrrrRRRRRRRRRRRrrrccccccc
1884 cccccccccccccCCCCCCCcccccccccccccccccccrrrrrrRRRRRRRrrrrrccccccc
1885 cccccccccccccccccccccccccccccccccccccccrrrrrrrrrrrrrrrrrrccccccc
1886 cccccccccccccccccccccccccccccccccccccccrrrrrrrrrrrrrrrrrrccccccc
1887 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1888 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1889 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1890 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1891 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1892 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1893 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1894 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1895 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1896 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1897 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1898 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1899 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1900 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1901 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1902 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1903 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1904 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1905 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1906 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1907 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1908 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1909 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
1910 "
1911 .replace(" ", "")
1912 );
1913 }
1914
1915 #[test]
1916 fn pixel_config_layout() {
1917 crate::test::compare_struct_layout::<Config>(&merge_shader(), "Config");
1919 }
1920}