diff --git a/crates/engine/src/prefs.rs b/crates/engine/src/prefs.rs index 4ee6cc97..b17cb8d3 100644 --- a/crates/engine/src/prefs.rs +++ b/crates/engine/src/prefs.rs @@ -586,8 +586,18 @@ impl Prefs { /// budgets, scaled down so together they leave **RAM Reserved for Other Applications** free, /// and halved while the system is low on memory when **Reduce Cache Size When System Is Low /// on Memory** is on. `mem` = the system's memory (`None` = unknown: configured budgets). + /// In the browser the layer and footage caches also stay within [`WEB_LAYER_CACHE`] and + /// [`WEB_MEDIA_CACHE`]. pub fn cache_budgets(&self, mem: Option) -> CacheBudgets { + self.cache_budgets_in(mem, cfg!(target_arch = "wasm32")) + } + + /// [`Prefs::cache_budgets`], in the browser's memory (`web`) or not. + pub(crate) fn cache_budgets_in(&self, mem: Option, web: bool) -> CacheBudgets { let (mut l, mut m, mut p) = (self.layer_cache_bytes() as u64, self.media_cache_bytes() as u64, self.preview_cache_bytes() as u64); + if web { + (l, m) = (l.min(WEB_LAYER_CACHE), m.min(WEB_MEDIA_CACHE)); + } let floor = 64u64 << 20; let mut capped = false; let mut reduced = false; @@ -732,6 +742,15 @@ fn push_mru(list: &mut Vec, v: &str, max: usize) { list.truncate(max.max(1)); } +/// The most the layer cache holds in the browser. A wasm32 page addresses at most 4 GiB, which +/// these caches share with the app and with the decoders' own decoded frames (FilmCraft keeps +/// up to 384 MB per open movie): with the desktop's 1 GB defaults, eight stacked 1080p clips ran +/// the page out of memory a few seconds into playback. +pub const WEB_LAYER_CACHE: u64 = 512 << 20; + +/// The most the footage frame cache holds in the browser (see [`WEB_LAYER_CACHE`]). +pub const WEB_MEDIA_CACHE: u64 = 256 << 20; + /// Effective cache budgets in bytes (see [`Prefs::cache_budgets`]). #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] pub struct CacheBudgets { diff --git a/crates/engine/src/tests_m145.rs b/crates/engine/src/tests_m145.rs index a73527bb..b55442eb 100644 --- a/crates/engine/src/tests_m145.rs +++ b/crates/engine/src/tests_m145.rs @@ -202,6 +202,23 @@ fn cache_budgets_leave_reserved_ram_and_shrink_when_low() { assert!(!p.cache_budgets(Some(low)).reduced); } +/// Eight stacked 1080p clips ran the browser build out of its 4 GiB of memory: the footage and +/// layer caches kept their desktop sizes there, beside the decoders' own frames. +#[test] +fn web_cache_budgets_leave_room_for_the_decoders() { + use crate::prefs::{WEB_LAYER_CACHE, WEB_MEDIA_CACHE}; + let mut p = crate::prefs::Prefs::default(); + p.memory.layer_cache_mb = 4096; + p.memory.media_cache_mb = 4096; + let web = p.cache_budgets_in(None, true); + assert_eq!((web.layer as u64, web.media as u64), (WEB_LAYER_CACHE, WEB_MEDIA_CACHE)); + assert_eq!(web.preview, p.cache_budgets_in(None, false).preview, "the RAM preview keeps its budget"); + // Smaller settings stay as they are; the desktop is unchanged. + p.memory.media_cache_mb = 128; + assert_eq!(p.cache_budgets_in(None, true).media, 128 << 20); + assert_eq!(p.cache_budgets_in(None, false).layer, 4096 << 20); +} + #[test] fn motion_path_span_follows_the_composition_setting() { let mut p = crate::prefs::Prefs::default(); diff --git a/docs/preferences.md b/docs/preferences.md index 9e3d4147..e4185433 100644 --- a/docs/preferences.md +++ b/docs/preferences.md @@ -71,8 +71,8 @@ Commands (CLI, MCP, control channel): - `audio.outputDevice`: Default Output (device list from the desktop host) - `audio.outputLeft`: Left (Audio Output Mapping) - `audio.outputRight`: Right (Audio Output Mapping) -- `memory.layerCacheMb`: Layer Cache -- `memory.mediaCacheMb`: Footage Frame Cache +- `memory.layerCacheMb`: Layer Cache (in the browser at most 512 MB, see [web.md](web.md)) +- `memory.mediaCacheMb`: Footage Frame Cache (in the browser at most 256 MB) - `memory.previewCacheMb`: Preview (RAM) Cache - `threeD.defaultRenderer`: Default 3D Renderer - `labels.N.name` / `labels.N.color`: the 16 label names and colours, used by the Label menu, diff --git a/docs/web.md b/docs/web.md index b03086eb..e73f7a89 100644 --- a/docs/web.md +++ b/docs/web.md @@ -61,6 +61,7 @@ device of their own), `?nosw` (don't register the service worker). | config directory: settings, shortcut presets, recent projects, the recovery sentinel | `store::WebConfig` in browser storage | | auto-save folder | `/EffectCraft Auto-Save/` in browser storage (`ConfigStore::files`) | | media reads (`MediaPool`, `probe`) | `files::WebImporter`: bytes from the file table, handed to `MediaPool::add_bytes` / `probe_bytes` | +| Memory & CPU cache budgets, scaled to the system's RAM | the page's memory, at most 4 GiB (wasm32), which the caches share with the decoders' own decoded frames (FilmCraft keeps up to 384 MB per open movie): the Layer Cache stays within 512 MB and the Footage Frame Cache within 256 MB (`Prefs::cache_budgets`) | | export writes (`effectcraft-export`) | the job's `sink` (`effectcraft_host::FileExporter { sink }`): files land in the table and download after the render; several files (an image sequence) download as one stored `.zip` | | rfd file dialogs | `` for File ▸ Open / Import; drop files anywhere on the page (`.ecproj` opens, everything else imports); "Save As" / "Output To" pick a download name | | Media Browser on the file system | browser storage and folders opened with the File System Access API (below) |