1#[macro_export]
12macro_rules! or_return {
13 ($expr:expr) => {
14 match $expr {
15 ::core::option::Option::Some(value) => value,
16 ::core::option::Option::None => return,
17 }
18 };
19 ($expr:expr, $ret:expr) => {
20 match $expr {
21 ::core::option::Option::Some(value) => value,
22 ::core::option::Option::None => return $ret,
23 }
24 };
25}
26
27#[macro_export]
42macro_rules! bind_mat {
43 ($pass:expr, $materials:expr, $instances:expr, $handle:expr) => {{
44 let instance = $crate::or_return!($instances.get($handle));
45 let material = $crate::or_return!($materials.get(instance.target));
46 $pass.set_pipeline(&material.pipeline);
47 $pass.set_bind_group(0, &instance.bind_group, &[]);
48 instance
49 }};
50}
51
52#[macro_export]
59macro_rules! bind_comp {
60 ($pass:expr, $computes:expr, $instances:expr, $handle:expr) => {{
61 let instance = $crate::or_return!($instances.get($handle));
62 let compute = $crate::or_return!($computes.get(instance.target));
63 $pass.set_pipeline(&compute.pipeline);
64 $pass.set_bind_group(0, &instance.bind_group, &[]);
65 instance
66 }};
67}
68
69#[macro_export]
79macro_rules! draw_mesh {
80 ($pass:expr, $meshes:expr, $handle:expr) => {
81 $crate::draw_mesh!($pass, $meshes, $handle, 0..1)
82 };
83 ($pass:expr, $meshes:expr, $handle:expr, $instances:expr) => {{
84 let mesh = $crate::or_return!($meshes.get($handle));
85 $pass.set_vertex_buffer(0, &mesh.vertex_buffer);
86 $pass.set_index_buffer(&mesh.index_buffer, $crate::graphics::types::IndexFormat::Uint32);
87 $pass.draw_indexed(0..mesh.index_count, 0, $instances);
88 }};
89}
90
91#[cfg(test)]
92mod tests {
93 use crate::graphics::types::IndexFormat;
94
95 struct Assets<T>(Option<T>);
96
97 impl<T> Assets<T> {
98 fn get(&self, _handle: u32) -> Option<&T> {
99 self.0.as_ref()
100 }
101 }
102
103 struct GPUMaterial {
104 pipeline: &'static str,
105 }
106
107 struct GPUMaterialInstance {
108 target: u32,
109 bind_group: &'static str,
110 }
111
112 #[derive(Default)]
113 struct RecordingPass {
114 pipeline: Option<&'static str>,
115 bind_group: Option<&'static str>,
116 vertex_buffer: Option<&'static str>,
117 index_buffer: Option<(&'static str, IndexFormat)>,
118 drawn: Option<(std::ops::Range<u32>, i32, std::ops::Range<u32>)>,
119 }
120
121 impl RecordingPass {
122 fn set_pipeline(&mut self, pipeline: &&'static str) {
123 self.pipeline = Some(pipeline);
124 }
125
126 fn set_bind_group(&mut self, _index: u32, bind_group: &&'static str, _offsets: &[u32]) {
127 self.bind_group = Some(bind_group);
128 }
129
130 fn set_vertex_buffer(&mut self, _slot: u32, buffer: &&'static str) {
131 self.vertex_buffer = Some(buffer);
132 }
133
134 fn set_index_buffer(&mut self, buffer: &&'static str, format: IndexFormat) {
135 self.index_buffer = Some((buffer, format));
136 }
137
138 fn draw_indexed(&mut self, indices: std::ops::Range<u32>, base_vertex: i32, instances: std::ops::Range<u32>) {
139 self.drawn = Some((indices, base_vertex, instances));
140 }
141 }
142
143 fn bind_missing(pass: &mut RecordingPass) {
144 let materials: Assets<GPUMaterial> = Assets(None);
145 let instances: Assets<GPUMaterialInstance> = Assets(None);
146 bind_mat!(pass, materials, instances, 0u32);
147 }
148
149 #[test]
150 fn bind_mat_returns_early_when_instance_missing() {
151 let mut pass = RecordingPass::default();
152 bind_missing(&mut pass);
153 assert!(pass.pipeline.is_none());
154 assert!(pass.bind_group.is_none());
155 }
156
157 fn bind_present(pass: &mut RecordingPass) {
158 let materials = Assets(Some(GPUMaterial { pipeline: "pipeline" }));
159 let instances = Assets(Some(GPUMaterialInstance { target: 0, bind_group: "bind_group" }));
160 let instance = bind_mat!(pass, materials, instances, 0u32);
161 assert_eq!(instance.bind_group, "bind_group");
162 }
163
164 #[test]
165 fn bind_mat_sets_pipeline_and_bind_group() {
166 let mut pass = RecordingPass::default();
167 bind_present(&mut pass);
168 assert_eq!(pass.pipeline, Some("pipeline"));
169 assert_eq!(pass.bind_group, Some("bind_group"));
170 }
171
172 struct GPUMesh {
173 vertex_buffer: &'static str,
174 index_buffer: &'static str,
175 index_count: u32,
176 }
177
178 #[test]
179 fn draw_mesh_defaults_to_a_single_instance() {
180 let mut pass = RecordingPass::default();
181 let meshes = Assets(Some(GPUMesh { vertex_buffer: "vbo", index_buffer: "ibo", index_count: 6 }));
182 draw_mesh!(pass, meshes, 0u32);
183
184 assert_eq!(pass.vertex_buffer, Some("vbo"));
185 assert_eq!(pass.index_buffer, Some(("ibo", IndexFormat::Uint32)));
186 assert_eq!(pass.drawn, Some((0..6, 0, 0..1)));
187 }
188
189 #[test]
190 fn draw_mesh_accepts_an_explicit_instance_range() {
191 let mut pass = RecordingPass::default();
192 let meshes = Assets(Some(GPUMesh { vertex_buffer: "vbo", index_buffer: "ibo", index_count: 6 }));
193 draw_mesh!(pass, meshes, 0u32, 0..12);
194
195 assert_eq!(pass.drawn, Some((0..6, 0, 0..12)));
196 }
197
198 #[test]
199 fn draw_mesh_returns_early_when_missing() {
200 let mut pass = RecordingPass::default();
201 let meshes: Assets<GPUMesh> = Assets(None);
202 draw_mesh!(pass, meshes, 0u32);
203
204 assert!(pass.drawn.is_none());
205 }
206}