Ticket 014 V1 — bake per-mesh unsigned distance fields via the compute pipeline. Drains `scene.pending_sdf_bakes` with a per-frame budget; expensive workload (O(voxels × triangles) per mesh), so the rate-limit keeps first-frame stutter bounded. Static scenes amortise and never re-bake. Ticket 022 — after each dispatch, encode a copy_texture_to_buffer against a fresh staging buffer and stash (hash
(
&mut self,
scene: &mut crate::scene::SceneGraph,
encoder: &mut wgpu::CommandEncoder,
)
| 7199 | /// persists each buffer to the on-disk cache so the next launch |
| 7200 | /// hits the load path in scene.rs and skips the bake entirely. |
| 7201 | fn bake_pending_sdfs( |
| 7202 | &mut self, |
| 7203 | scene: &mut crate::scene::SceneGraph, |
| 7204 | encoder: &mut wgpu::CommandEncoder, |
| 7205 | ) { |
| 7206 | if !self.hw_rt_enabled || scene.pending_sdf_bakes.is_empty() { |
| 7207 | return; |
| 7208 | } |
| 7209 | const SDF_BAKE_MAX_PER_FRAME: usize = 8; |
| 7210 | let take = scene.pending_sdf_bakes.len().min(SDF_BAKE_MAX_PER_FRAME); |
| 7211 | let pending: Vec<f64> = scene.pending_sdf_bakes.drain(..take).collect(); |
| 7212 | |
| 7213 | for handle in pending { |
| 7214 | let (sdf_tex, sdf_view, vb_ptr, ib_ptr, bmin, bmax, index_count, mesh_hash) = { |
| 7215 | let Some(node) = scene.nodes.get(handle) else { continue; }; |
| 7216 | let Some(sdf_tex) = node.mesh_sdf.as_ref() else { continue; }; |
| 7217 | let Some(sdf_view) = node.mesh_sdf_view.as_ref() else { continue; }; |
| 7218 | let Some(vb) = node.gpu_vb.as_ref() else { continue; }; |
| 7219 | let Some(ib) = node.gpu_ib.as_ref() else { continue; }; |
| 7220 | ( |
| 7221 | sdf_tex.clone(), |
| 7222 | sdf_view.clone(), |
| 7223 | vb.clone(), |
| 7224 | ib.clone(), |
| 7225 | node.bounds_min, |
| 7226 | node.bounds_max, |
| 7227 | node.gpu_index_count, |
| 7228 | node.mesh_hash, |
| 7229 | ) |
| 7230 | }; |
| 7231 | if index_count == 0 { |
| 7232 | continue; |
| 7233 | } |
| 7234 | let tri_count = index_count / 3; |
| 7235 | let params = SdfBakeParams { |
| 7236 | aabb_min: [bmin[0], bmin[1], bmin[2], 0.0], |
| 7237 | aabb_max: [bmax[0], bmax[1], bmax[2], 0.0], |
| 7238 | counts: [tri_count, MESH_SDF_RES, 0, 0], |
| 7239 | }; |
| 7240 | self.queue.write_buffer( |
| 7241 | &self.sdf_bake_uniform, |
| 7242 | 0, |
| 7243 | bytemuck::bytes_of(¶ms), |
| 7244 | ); |
| 7245 | let bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor { |
| 7246 | label: Some("sdf_bake_bg"), |
| 7247 | layout: &self.sdf_bake_layout, |
| 7248 | entries: &[ |
| 7249 | wgpu::BindGroupEntry { binding: 0, resource: self.sdf_bake_uniform.as_entire_binding() }, |
| 7250 | wgpu::BindGroupEntry { binding: 1, resource: vb_ptr.as_entire_binding() }, |
| 7251 | wgpu::BindGroupEntry { binding: 2, resource: ib_ptr.as_entire_binding() }, |
| 7252 | wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(&sdf_view) }, |
| 7253 | ], |
| 7254 | }); |
| 7255 | { |
| 7256 | let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { |
| 7257 | label: Some("sdf_bake_pass"), |
| 7258 | timestamp_writes: None, |
no test coverage detected