1use crate::infer::GraphExt;
8use crate::op::Op;
9use crate::{DType, Graph, NodeId, Shape};
10
11#[derive(Clone, Debug)]
13pub struct GaussianSplatInputs {
14 pub positions: NodeId,
15 pub scales: NodeId,
16 pub rotations: NodeId,
17 pub opacities: NodeId,
18 pub colors: NodeId,
19 pub sh_coeffs: NodeId,
20 pub meta: NodeId,
21}
22
23#[derive(Clone, Copy, Debug)]
25pub struct GaussianSplatRenderParams {
26 pub width: u32,
27 pub height: u32,
28 pub tile_size: u32,
29 pub radius_scale: f32,
30 pub alpha_cutoff: f32,
31 pub max_splat_steps: u32,
32 pub transmittance_threshold: f32,
33 pub max_list_entries: u32,
34}
35
36#[derive(Clone, Copy, Debug)]
38pub struct GaussianSplatBackwardParams {
39 pub render: GaussianSplatRenderParams,
40 pub loss_grad_clip: f32,
41 pub sh_band: u32,
42 pub max_anisotropy: f32,
43}
44
45impl Default for GaussianSplatBackwardParams {
46 fn default() -> Self {
47 Self {
48 render: GaussianSplatRenderParams::default(),
49 loss_grad_clip: 1.0,
50 sh_band: 0,
51 max_anisotropy: 10.0,
52 }
53 }
54}
55
56pub const GAUSSIAN_SPLAT_PREP_RASTER_PARAMS_FLOATS: usize = 11;
58
59pub fn gaussian_splat_tile_count(width: u32, height: u32, tile_size: u32) -> u32 {
61 let tw = width.div_ceil(tile_size);
62 let th = height.div_ceil(tile_size);
63 tw * th
64}
65
66pub fn gaussian_splat_prep_packed_len(
68 count: usize,
69 max_list_entries: u32,
70 width: u32,
71 height: u32,
72 tile_size: u32,
73) -> usize {
74 let n = count.max(1);
75 let max_list = max_list_entries as usize;
76 let tiles = gaussian_splat_tile_count(width, height, tile_size) as usize;
77 let pixels = (width as usize).saturating_mul(height as usize).max(1);
78 n * 4
79 + n
80 + n * 3
81 + n * 3
82 + n * 4
83 + max_list
84 + tiles * 2
85 + pixels * 3
86 + GAUSSIAN_SPLAT_PREP_RASTER_PARAMS_FLOATS
87}
88
89pub fn gaussian_splat_packed_grad_len(count: usize, sh_coeff_count: usize) -> usize {
91 count * (3 + 3 + 4 + 1 + 3) + count * sh_coeff_count.max(1) * 3
92}
93
94pub fn unpack_gaussian_splat_packed_grads(
96 g: &mut Graph,
97 packed: NodeId,
98 count: usize,
99 sh_coeff_count: usize,
100) -> GaussianSplatInputs {
101 let mut off = 0usize;
102 let mut take = |len: usize| -> NodeId {
103 let id = g.narrow_(packed, 0, off, len);
104 off += len;
105 id
106 };
107 let positions = take(count * 3);
108 let scales = take(count * 3);
109 let rotations = take(count * 4);
110 let opacities = take(count);
111 let colors = take(count * 3);
112 let sh_coeffs = take(count * sh_coeff_count.max(1) * 3);
113 let _ = off;
114 GaussianSplatInputs {
115 positions,
116 scales,
117 rotations,
118 opacities,
119 colors,
120 sh_coeffs,
121 meta: packed,
122 }
123}
124
125impl Default for GaussianSplatRenderParams {
126 fn default() -> Self {
127 Self {
128 width: 64,
129 height: 64,
130 tile_size: 16,
131 radius_scale: 1.6,
132 alpha_cutoff: 1.0 / 255.0,
133 max_splat_steps: 32,
134 transmittance_threshold: 0.01,
135 max_list_entries: 18 * 32,
136 }
137 }
138}
139
140impl Graph {
141 pub fn gaussian_splat_render(
146 &mut self,
147 inputs: GaussianSplatInputs,
148 params: GaussianSplatRenderParams,
149 ) -> NodeId {
150 let out_elems = (params.width as usize) * (params.height as usize) * 4;
151 let dtype = self.shape(inputs.positions).dtype();
152 let out_shape = Shape::new(&[out_elems], dtype);
153 self.push(
154 Op::GaussianSplatRender {
155 width: params.width,
156 height: params.height,
157 tile_size: params.tile_size,
158 radius_scale: params.radius_scale,
159 alpha_cutoff: params.alpha_cutoff,
160 max_splat_steps: params.max_splat_steps,
161 transmittance_threshold: params.transmittance_threshold,
162 max_list_entries: params.max_list_entries,
163 },
164 vec![
165 inputs.positions,
166 inputs.scales,
167 inputs.rotations,
168 inputs.opacities,
169 inputs.colors,
170 inputs.sh_coeffs,
171 inputs.meta,
172 ],
173 out_shape,
174 None,
175 )
176 }
177
178 pub fn gaussian_splat_render_meta(
180 &mut self,
181 camera_position: [f32; 3],
182 camera_target: [f32; 3],
183 camera_up: [f32; 3],
184 fov_y_degrees: f32,
185 near: f32,
186 far: f32,
187 background: [f32; 3],
188 params: GaussianSplatRenderParams,
189 ) -> NodeId {
190 let values = vec![
191 camera_position[0],
192 camera_position[1],
193 camera_position[2],
194 camera_target[0],
195 camera_target[1],
196 camera_target[2],
197 camera_up[0],
198 camera_up[1],
199 camera_up[2],
200 fov_y_degrees,
201 near,
202 far,
203 background[0],
204 background[1],
205 background[2],
206 params.width as f32,
207 params.height as f32,
208 params.tile_size as f32,
209 params.radius_scale,
210 params.alpha_cutoff,
211 params.max_splat_steps as f32,
212 params.transmittance_threshold,
213 params.max_list_entries as f32,
214 ];
215 let bytes: Vec<u8> = values.iter().flat_map(|v| v.to_le_bytes()).collect();
216 self.add_node(
217 Op::Constant { data: bytes },
218 vec![],
219 Shape::new(&[23], DType::F32),
220 )
221 }
222
223 pub fn gaussian_splat_prepare(
225 &mut self,
226 inputs: GaussianSplatInputs,
227 params: GaussianSplatRenderParams,
228 ) -> NodeId {
229 let count = self.shape(inputs.positions).num_elements().unwrap_or(0) / 3;
230 let packed_len = gaussian_splat_prep_packed_len(
231 count,
232 params.max_list_entries,
233 params.width,
234 params.height,
235 params.tile_size,
236 );
237 let dtype = self.shape(inputs.positions).dtype();
238 self.push(
239 Op::GaussianSplatPrepare {
240 width: params.width,
241 height: params.height,
242 tile_size: params.tile_size,
243 radius_scale: params.radius_scale,
244 alpha_cutoff: params.alpha_cutoff,
245 max_splat_steps: params.max_splat_steps,
246 transmittance_threshold: params.transmittance_threshold,
247 max_list_entries: params.max_list_entries,
248 },
249 vec![
250 inputs.positions,
251 inputs.scales,
252 inputs.rotations,
253 inputs.opacities,
254 inputs.colors,
255 inputs.sh_coeffs,
256 inputs.meta,
257 ],
258 Shape::new(&[packed_len], dtype),
259 None,
260 )
261 }
262
263 pub fn gaussian_splat_rasterize(
265 &mut self,
266 prep: NodeId,
267 meta: NodeId,
268 params: GaussianSplatRenderParams,
269 ) -> NodeId {
270 let out_elems = (params.width as usize) * (params.height as usize) * 4;
271 let dtype = self.shape(prep).dtype();
272 self.push(
273 Op::GaussianSplatRasterize {
274 width: params.width,
275 height: params.height,
276 tile_size: params.tile_size,
277 alpha_cutoff: params.alpha_cutoff,
278 max_splat_steps: params.max_splat_steps,
279 transmittance_threshold: params.transmittance_threshold,
280 max_list_entries: params.max_list_entries,
281 },
282 vec![prep, meta],
283 Shape::new(&[out_elems], dtype),
284 None,
285 )
286 }
287
288 pub fn gaussian_splat_render_decomposed(
290 &mut self,
291 inputs: GaussianSplatInputs,
292 params: GaussianSplatRenderParams,
293 ) -> NodeId {
294 let meta = inputs.meta;
295 let prep = self.gaussian_splat_prepare(inputs, params);
296 self.gaussian_splat_rasterize(prep, meta, params)
297 }
298
299 pub fn gaussian_splat_render_backward(
301 &mut self,
302 inputs: GaussianSplatInputs,
303 d_loss_rgba: NodeId,
304 params: GaussianSplatBackwardParams,
305 ) -> NodeId {
306 let count = self.shape(inputs.positions).num_elements().unwrap_or(0) / 3;
307 let sh_len = self.shape(inputs.sh_coeffs).num_elements().unwrap_or(0);
308 let sh_coeff_count = if count == 0 {
309 1
310 } else {
311 (sh_len / (count * 3)).max(1)
312 };
313 let packed_len = gaussian_splat_packed_grad_len(count, sh_coeff_count);
314 let dtype = self.shape(inputs.positions).dtype();
315 let r = params.render;
316 self.push(
317 Op::GaussianSplatRenderBackward {
318 width: r.width,
319 height: r.height,
320 tile_size: r.tile_size,
321 radius_scale: r.radius_scale,
322 alpha_cutoff: r.alpha_cutoff,
323 max_splat_steps: r.max_splat_steps,
324 transmittance_threshold: r.transmittance_threshold,
325 max_list_entries: r.max_list_entries,
326 loss_grad_clip: params.loss_grad_clip,
327 sh_band: params.sh_band,
328 max_anisotropy: params.max_anisotropy,
329 },
330 vec![
331 inputs.positions,
332 inputs.scales,
333 inputs.rotations,
334 inputs.opacities,
335 inputs.colors,
336 inputs.sh_coeffs,
337 inputs.meta,
338 d_loss_rgba,
339 ],
340 Shape::new(&[packed_len], dtype),
341 None,
342 )
343 }
344}