runmat_runtime/builtins/plotting/core/
perf.rs1use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
4
5const DEFAULT_SCATTER_TARGET_POINTS: u32 = 250_000;
6const MIN_SCATTER_TARGET_POINTS: u32 = 16_384;
7const DEFAULT_SURFACE_VERTEX_BUDGET: u64 = 400_000;
8const MIN_SURFACE_VERTEX_BUDGET: u64 = 65_536;
9const SCATTER_EXTENT_REFERENCE: f32 = 250.0;
10const SURFACE_EXTENT_REFERENCE: f32 = 500.0;
11
12static SCATTER_TARGET_POINTS: AtomicU32 = AtomicU32::new(DEFAULT_SCATTER_TARGET_POINTS);
13static SURFACE_VERTEX_BUDGET: AtomicU64 = AtomicU64::new(DEFAULT_SURFACE_VERTEX_BUDGET);
14
15pub(crate) fn scatter_target_points() -> u32 {
19 SCATTER_TARGET_POINTS.load(Ordering::Relaxed)
20}
21
22pub fn set_scatter_target_points(value: u32) {
25 let clamped = value.max(MIN_SCATTER_TARGET_POINTS);
26 SCATTER_TARGET_POINTS.store(clamped, Ordering::Relaxed);
27}
28
29pub(crate) fn surface_vertex_budget() -> u64 {
33 SURFACE_VERTEX_BUDGET.load(Ordering::Relaxed)
34}
35
36pub fn set_surface_vertex_budget(value: u64) {
38 let clamped = value.max(MIN_SURFACE_VERTEX_BUDGET);
39 SURFACE_VERTEX_BUDGET.store(clamped, Ordering::Relaxed);
40}
41
42#[derive(Debug, Clone, Copy)]
43pub(crate) struct SurfaceLod {
44 pub stride_x: u32,
45 pub stride_y: u32,
46 pub lod_x_len: u32,
47 pub lod_y_len: u32,
48}
49
50impl SurfaceLod {
51 pub fn vertex_count(&self) -> usize {
52 (self.lod_x_len as usize) * (self.lod_y_len as usize)
53 }
54}
55
56fn adjust_for_extent<T>(base: T, extent_hint: f32, reference: f32) -> T
57where
58 T: num_traits::NumCast + Copy,
59{
60 if !extent_hint.is_finite() || extent_hint <= 0.0 {
61 return base;
62 }
63 let reference = reference.max(1.0);
64 let ratio = (reference / extent_hint).clamp(0.25, 4.0);
65 let adjusted = num_traits::cast::<_, f64>(base).unwrap_or(0.0) * ratio as f64;
66 num_traits::cast(adjusted).unwrap_or(base)
67}
68
69pub(crate) fn compute_surface_lod(x_len: usize, y_len: usize, extent_hint: f32) -> SurfaceLod {
74 let x_len = x_len.max(1);
75 let y_len = y_len.max(1);
76 let x_u32 = x_len as u32;
77 let y_u32 = y_len as u32;
78 let total_vertices = (x_len as u64) * (y_len as u64);
79 let mut budget = surface_vertex_budget().max(MIN_SURFACE_VERTEX_BUDGET);
80 if extent_hint.is_finite() && extent_hint > 0.0 {
81 let adjusted =
82 adjust_for_extent::<u64>(budget, extent_hint, SURFACE_EXTENT_REFERENCE).max(1);
83 budget = adjusted.max(MIN_SURFACE_VERTEX_BUDGET);
84 }
85
86 if total_vertices <= budget {
87 return SurfaceLod {
88 stride_x: 1,
89 stride_y: 1,
90 lod_x_len: x_u32,
91 lod_y_len: y_u32,
92 };
93 }
94
95 let stride_guess = ((total_vertices as f64 / budget as f64).sqrt().ceil() as u32).max(2);
96 let mut stride_x = stride_guess.min(x_u32);
97 let mut stride_y = stride_guess.min(y_u32);
98 let mut lod_x_len = ceil_div(x_u32, stride_x);
99 let mut lod_y_len = ceil_div(y_u32, stride_y);
100
101 for _ in 0..32 {
102 if (lod_x_len as u64) * (lod_y_len as u64) <= budget {
103 break;
104 }
105 if lod_x_len >= lod_y_len && stride_x < x_u32 {
106 stride_x = stride_x.saturating_add(1).min(x_u32);
107 lod_x_len = ceil_div(x_u32, stride_x);
108 } else if stride_y < y_u32 {
109 stride_y = stride_y.saturating_add(1).min(y_u32);
110 lod_y_len = ceil_div(y_u32, stride_y);
111 } else {
112 break;
113 }
114 }
115
116 SurfaceLod {
117 stride_x: stride_x.max(1),
118 stride_y: stride_y.max(1),
119 lod_x_len: lod_x_len.max(1),
120 lod_y_len: lod_y_len.max(1),
121 }
122}
123
124fn ceil_div(len: u32, stride: u32) -> u32 {
125 if stride == 0 {
126 return len;
127 }
128 len.div_ceil(stride)
129}
130
131pub(crate) fn scatter3_lod_stride(point_count: u32, extent_hint: f32) -> u32 {
135 let base = scatter_target_points();
136 let adjusted = adjust_for_extent::<u32>(base, extent_hint, SCATTER_EXTENT_REFERENCE)
137 .max(MIN_SCATTER_TARGET_POINTS);
138 if point_count <= adjusted {
139 1
140 } else {
141 point_count.div_ceil(adjusted)
142 }
143}
144
145#[cfg(test)]
146pub(crate) mod tests {
147 use super::*;
148
149 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
150 #[test]
151 fn scatter_target_env_override() {
152 set_scatter_target_points(300_000);
153 assert_eq!(scatter_target_points(), 300_000);
154 }
155
156 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
157 #[test]
158 fn surface_lod_identity_when_small() {
159 let lod = compute_surface_lod(32, 64, 10.0);
160 assert_eq!(lod.stride_x, 1);
161 assert_eq!(lod.stride_y, 1);
162 assert_eq!(lod.lod_x_len, 32);
163 assert_eq!(lod.lod_y_len, 64);
164 }
165
166 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
167 #[test]
168 fn surface_lod_downsamples_large_grid() {
169 let lod = compute_surface_lod(4096, 4096, 10_000.0);
170 assert!(lod.stride_x > 1);
171 assert!(lod.stride_y > 1);
172 assert!((lod.lod_x_len as u64) * (lod.lod_y_len as u64) <= surface_vertex_budget());
173 }
174
175 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
176 #[test]
177 fn scatter3_stride_scales_with_extent() {
178 set_scatter_target_points(100_000);
179 let dense = scatter3_lod_stride(1_000_000, 50.0);
180 let sparse = scatter3_lod_stride(1_000_000, 5_000.0);
181 assert!(dense < sparse, "{dense} vs {sparse}");
182 }
183}