loam_render/passes/
mod.rs1mod hyperslice;
2mod line;
3mod point;
4mod raymarch;
5mod sky_ground;
6
7pub use hyperslice::HyperslicePass;
8pub use line::LinePass;
9pub use point::PointPass;
10pub use raymarch::RaymarchPass;
11pub use sky_ground::SkyGroundPass;
12
13#[cfg(test)]
14mod tests {
15 use loam_runtime::{Eye, PointRecord};
16 use wgpu::{
17 BackendOptions, Backends, Extent3d, Instance, InstanceDescriptor, NoopBackendOptions,
18 TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, TextureView,
19 TextureViewDescriptor,
20 };
21
22 use super::*;
23 use crate::device::{FeatureRequest, GpuContext};
24 use crate::pass::{FrameFormat, FramePass, FrameTarget, PassSchedule};
25 use crate::view::DEPTH_FORMAT;
26 use crate::DepthConvention;
27
28 const FORMAT: TextureFormat = TextureFormat::Rgba8Unorm;
29 const SIZE: (u32, u32) = (32, 24);
30
31 fn noop_gpu() -> GpuContext {
32 let instance = Instance::new(&InstanceDescriptor {
33 backends: Backends::NOOP,
34 backend_options: BackendOptions {
35 noop: NoopBackendOptions { enable: true },
36 ..Default::default()
37 },
38 ..Default::default()
39 });
40 pollster::block_on(GpuContext::new(instance, FeatureRequest::default(), None))
41 .expect("the noop backend always yields a context")
42 }
43
44 fn frame() -> FrameFormat {
45 FrameFormat {
46 color: FORMAT,
47 depth: DEPTH_FORMAT,
48 }
49 }
50
51 fn attachment(gpu: &GpuContext, format: TextureFormat) -> TextureView {
52 gpu.device
53 .create_texture(&TextureDescriptor {
54 label: None,
55 size: Extent3d {
56 width: SIZE.0,
57 height: SIZE.1,
58 depth_or_array_layers: 1,
59 },
60 mip_level_count: 1,
61 sample_count: 1,
62 dimension: TextureDimension::D2,
63 format,
64 usage: TextureUsages::RENDER_ATTACHMENT,
65 view_formats: &[],
66 })
67 .create_view(&TextureViewDescriptor::default())
68 }
69
70 fn record_once(gpu: &GpuContext, schedule: &mut PassSchedule) {
71 let color = attachment(gpu, FORMAT);
72 let depth = attachment(gpu, DEPTH_FORMAT);
73 let mut encoder = gpu
74 .device
75 .create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
76 let target = FrameTarget {
77 color: &color,
78 depth: Some(&depth),
79 size: SIZE,
80 };
81 schedule.begin_frame();
82 schedule
83 .record(crate::PassStage::Background, &mut encoder, &target)
84 .expect("background recorded");
85 schedule
86 .record(crate::PassStage::Scene, &mut encoder, &target)
87 .expect("scene recorded");
88 schedule
89 .record(crate::PassStage::Overlay, &mut encoder, &target)
90 .expect("overlays recorded");
91 schedule.end_frame(&mut encoder);
92 gpu.queue.submit(Some(encoder.finish()));
93 }
94
95 #[test]
96 fn a_publish_that_changes_only_a_color_still_reaches_the_gpu() {
97 let gpu = noop_gpu();
98 let mut point = PointPass::new("vertices");
99 let record = PointRecord {
100 position: [0.0, 0.0, -2.0],
101 radius_px: 4.0,
102 color: [1.0, 0.0, 0.0, 1.0],
103 };
104 point.publish(&Eye::default(), &[record]);
105 point.attach(&gpu, frame()).expect("attach");
106
107 let mut schedule = PassSchedule::new(DepthConvention::ReversedZ);
108 schedule
109 .register(Box::new(point.clone()))
110 .expect("registered");
111 record_once(&gpu, &mut schedule);
112 let after_first = point.uploads();
113
114 point.publish(
115 &Eye::default(),
116 &[PointRecord {
117 color: [0.0, 1.0, 0.0, 1.0],
118 ..record
119 }],
120 );
121 record_once(&gpu, &mut schedule);
122 assert_eq!(
123 point.uploads(),
124 after_first + 1,
125 "the color change never left the CPU: the pass uploaded {} times across both frames",
126 point.uploads()
127 );
128 }
129}