diff --git a/Cargo.lock b/Cargo.lock index 5abe6b35d..734ccc628 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6230,6 +6230,7 @@ dependencies = [ "vello_cpu", "wat", "weezl 0.2.1", + "winsafe", ] [[package]] diff --git a/crates/render/Cargo.toml b/crates/render/Cargo.toml index ad8696e1c..d8d56da1d 100644 --- a/crates/render/Cargo.toml +++ b/crates/render/Cargo.toml @@ -29,6 +29,11 @@ log = { workspace = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] vello_cpu = { workspace = true, features = ["multithreading"] } +# The shared image store's budget (src/images.rs) follows the installed memory, which it reads with GlobalMemoryStatusEx +# through winsafe's safe wrappers over Win32 (MIT), which vectorcraft-cli's src/perf.rs also uses. +[target.'cfg(windows)'.dependencies] +winsafe = { version = "0.0.29", default-features = false, features = ["kernel"] } + [dev-dependencies] vectorcraft-testkit = { workspace = true } # A variable font built in code (no font file is committed). diff --git a/crates/render/src/images.rs b/crates/render/src/images.rs index 364b3faff..271278b48 100644 --- a/crates/render/src/images.rs +++ b/crates/render/src/images.rs @@ -31,10 +31,17 @@ fn default_budget() -> usize { #[cfg(not(target_arch = "wasm32"))] fn default_budget() -> usize { - physical_memory().map_or(IMAGE_CACHE_MAX, |m| usize::try_from(m / 8).unwrap_or(usize::MAX).min(IMAGE_CACHE_MAX)) + physical_memory().map_or(IMAGE_CACHE_MAX, budget_for) } -/// Installed memory in bytes, where the system says without a native call (Linux). +/// The default budget on a machine with `memory` bytes installed: an eighth of it, at most +/// [`IMAGE_CACHE_MAX`]. +#[cfg(not(target_arch = "wasm32"))] +fn budget_for(memory: u64) -> usize { + usize::try_from(memory / 8).unwrap_or(usize::MAX).min(IMAGE_CACHE_MAX) +} + +/// Installed memory in bytes (`MemTotal` in `/proc/meminfo`). #[cfg(target_os = "linux")] fn physical_memory() -> Option { let info = std::fs::read_to_string("/proc/meminfo").ok()?; @@ -43,7 +50,28 @@ fn physical_memory() -> Option { kib.checked_mul(1024) } -#[cfg(not(any(target_os = "linux", target_arch = "wasm32")))] +/// Installed memory in bytes (`GlobalMemoryStatusEx`). +#[cfg(windows)] +fn physical_memory() -> Option { + winsafe::GlobalMemoryStatusEx().ok().map(|m| m.ullTotalPhys) +} + +/// Installed memory in bytes, as `sysctl` prints it (`hw.memsize` on macOS, `hw.physmem` on +/// FreeBSD). +#[cfg(any(target_os = "macos", target_os = "freebsd"))] +fn physical_memory() -> Option { + let (sysctl, name) = if cfg!(target_os = "macos") { ("/usr/sbin/sysctl", "hw.memsize") } else { ("/sbin/sysctl", "hw.physmem") }; + let out = std::process::Command::new(sysctl).args(["-n", name]).output().ok().filter(|o| o.status.success())?; + sysctl_bytes(&out.stdout) +} + +/// The byte count `sysctl -n` prints for a memory size. +#[cfg(any(target_os = "macos", target_os = "freebsd", test))] +fn sysctl_bytes(out: &[u8]) -> Option { + std::str::from_utf8(out).ok()?.trim().parse().ok() +} + +#[cfg(not(any(target_os = "linux", windows, target_os = "macos", target_os = "freebsd", target_arch = "wasm32")))] fn physical_memory() -> Option { None } @@ -330,6 +358,27 @@ mod tests { assert_eq!(s.bytes(), 400); } + #[test] + #[cfg(not(target_arch = "wasm32"))] + fn default_budget_is_an_eighth_of_installed_memory_at_most_1_gib() { + assert_eq!(budget_for(4 << 30), 512 << 20); + assert_eq!(budget_for(8 << 30), IMAGE_CACHE_MAX); + assert_eq!(budget_for(64 << 30), IMAGE_CACHE_MAX); + } + + #[test] + #[cfg(any(target_os = "linux", windows, target_os = "macos", target_os = "freebsd"))] + fn installed_memory_is_read() { + assert!(physical_memory().is_some_and(|m| m >= 256 << 20), "{:?}", physical_memory()); + } + + #[test] + fn sysctl_prints_the_memory_size_in_bytes() { + assert_eq!(sysctl_bytes(b"17179869184\n"), Some(16 << 30)); + assert_eq!(sysctl_bytes(b""), None); + assert_eq!(sysctl_bytes(b"unknown oid 'hw.memsize'\n"), None); + } + #[test] fn half_averages_and_rounds_up_odd_sizes() { let px = |v: u8| PremulRgba8 { r: v, g: v, b: v, a: 255 };