mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-30 21:16:52 -06:00
M13.30: web depth and polish
- GPU in browser job workers: each job worker opens its own WebGPU device (deferred readbacks); the Render Queue export and the analyses' frame loops are futures (render::passes, offload::run_request_async) whose frames render in passes, awaiting the device between them. GPU particles and Advanced 3D read back under keys too. workers.jobs reports passes and readbacks; the smoke test measures them. - Layer buffers in the browser's OPFS disk cache: frame workers back their layer caches with a PrefetchStore; the page plans what to fetch before a request from recent misses (LayerPrefetch over the shared DiskIndex). - Templates embed footage (default on, size-capped with a warning) and creating from a template extracts it next to the new project / into browser storage. - ICC LUT profiles (lut8/lut16/lutAtoB/lutBtoA, from the public ICC spec) in Simulate Output > My Custom RGB, baked into a 3D LUT for the viewer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d39c0e818c
commit
f3715aac32
@@ -38,7 +38,7 @@ extern "C" {
|
||||
#[wasm_bindgen(js_name = frameWorkerPost)]
|
||||
fn frame_worker_post(index: u32, msg: &JsValue, transfer: &js_sys::Array);
|
||||
#[wasm_bindgen(js_name = hasWebGpu)]
|
||||
fn has_web_gpu() -> bool;
|
||||
pub(crate) fn has_web_gpu() -> bool;
|
||||
}
|
||||
|
||||
/// One frame worker as the page sees it.
|
||||
@@ -134,7 +134,10 @@ impl WebFrames {
|
||||
frame_worker_post(index, &obj(&[("type", "frame".into()), ("json", t.into())]), &js_sys::Array::new());
|
||||
}
|
||||
let disk = job.disk_key.map(|k| format!("{k:032x}"));
|
||||
let req = FrameMsg::Render { id, revision: job.revision, comp: job.comp, time: job.t, opts: Box::new(job.opts), disk };
|
||||
// Layer buffers from the disk cache: what to read before rendering.
|
||||
let layers = crate::diskcache::active();
|
||||
let prefetch = crate::diskcache::layer_prefetch(64);
|
||||
let req = FrameMsg::Render { id, revision: job.revision, comp: job.comp, time: job.t, opts: Box::new(job.opts), disk, layers, prefetch };
|
||||
frame_worker_post(
|
||||
index,
|
||||
&obj(&[("type", "frame".into()), ("json", serde_json::to_string(&req).unwrap_or_default().into())]),
|
||||
@@ -210,7 +213,8 @@ fn on_message(index: u32, json: JsValue, bytes: JsValue, error: JsValue) {
|
||||
}
|
||||
let reply: FrameReply = serde_json::from_str(&json.as_string()?).ok()?;
|
||||
match reply {
|
||||
FrameReply::Frame { id, width, height, ms, passes, gpu } => {
|
||||
FrameReply::Frame { id, width, height, ms, passes, gpu, layer_misses, layer_hits } => {
|
||||
crate::diskcache::note_layers(&layer_misses, &layer_hits);
|
||||
let (_, d) = w.busy.take_if(|b| b.0 == id)?;
|
||||
let rgba = bytes.dyn_into::<js_sys::Uint8Array>().map(|b| b.to_vec()).unwrap_or_default();
|
||||
st.rendered += 1;
|
||||
@@ -231,9 +235,9 @@ fn on_message(index: u32, json: JsValue, bytes: JsValue, error: JsValue) {
|
||||
w.mirror.reset();
|
||||
None
|
||||
}
|
||||
FrameReply::Stored { key, bytes } => {
|
||||
FrameReply::Stored { key, bytes, layer } => {
|
||||
if let Ok(k) = u128::from_str_radix(&key, 16) {
|
||||
crate::diskcache::stored(k, bytes);
|
||||
crate::diskcache::stored(k, bytes, layer);
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -410,7 +414,9 @@ fn post_reply(r: &FrameReply, px: Option<&[u8]>) {
|
||||
}
|
||||
|
||||
fn new_server() -> FrameServer {
|
||||
FrameServer::new(crate::worker::pool(), Some(Arc::new(effectcraft_expr::Expressions)))
|
||||
let mut s = FrameServer::new(crate::worker::pool(), Some(Arc::new(effectcraft_expr::Expressions)));
|
||||
s.set_layer_store(Some(Arc::new(effectcraft_engine::render::PrefetchStore::default())));
|
||||
s
|
||||
}
|
||||
|
||||
/// Set up a frame worker (called once by `web/worker.js` after `workerInit`): its frame
|
||||
@@ -450,9 +456,14 @@ pub async fn worker_frame(json: String) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
let disk = match &msg {
|
||||
FrameMsg::Render { disk, .. } => disk.clone(),
|
||||
_ => None,
|
||||
let (disk, layers) = match &msg {
|
||||
FrameMsg::Render { disk, layers, prefetch, .. } => {
|
||||
if *layers {
|
||||
prefetch_layers(prefetch).await;
|
||||
}
|
||||
(disk.clone(), *layers)
|
||||
}
|
||||
_ => (None, false),
|
||||
};
|
||||
let mut replies = SERVER.with(|c| c.borrow_mut().get_or_insert_with(new_server).handle(msg));
|
||||
while SERVER.with(|c| c.borrow().as_ref().is_some_and(|s| s.waiting())) {
|
||||
@@ -466,9 +477,41 @@ pub async fn worker_frame(json: String) {
|
||||
let entry = effectcraft_engine::render::disk_cache::frame_entry(*width, *height, px);
|
||||
let name = format!("{key}.ecc");
|
||||
match JsFuture::from(crate::diskcache::cache_write(&name, js_sys::Uint8Array::from(&entry[..]))).await {
|
||||
Ok(n) => post_reply(&FrameReply::Stored { key: key.clone(), bytes: n.as_f64().unwrap_or(entry.len() as f64) as u64 }, None),
|
||||
Ok(n) => post_reply(&FrameReply::Stored { key: key.clone(), bytes: n.as_f64().unwrap_or(entry.len() as f64) as u64, layer: false }, None),
|
||||
Err(e) => log::warn!("frame worker: disk cache write failed: {e:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
if layers {
|
||||
store_layers().await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Read the layer entries the page named into the server's store (those it lacks).
|
||||
async fn prefetch_layers(keys: &[String]) {
|
||||
let Some(store) = SERVER.with(|c| c.borrow().as_ref().and_then(|s| s.layer_store().cloned())) else { return };
|
||||
for k in keys {
|
||||
let Ok(key) = u128::from_str_radix(k, 16) else { continue };
|
||||
if store.has(key) {
|
||||
continue;
|
||||
}
|
||||
let name = crate::diskcache::name_of(effectcraft_engine::render::disk_cache::Kind::Layer, key);
|
||||
if let Ok(b) = JsFuture::from(crate::diskcache::cache_read(&name)).await
|
||||
&& let Ok(b) = b.dyn_into::<js_sys::Uint8Array>()
|
||||
{
|
||||
store.provide(key, &b.to_vec());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Write the slow layer buffers of the frame just rendered and report them.
|
||||
async fn store_layers() {
|
||||
let Some(store) = SERVER.with(|c| c.borrow().as_ref().and_then(|s| s.layer_store().cloned())) else { return };
|
||||
for (key, entry) in store.take_writes() {
|
||||
let name = crate::diskcache::name_of(effectcraft_engine::render::disk_cache::Kind::Layer, key);
|
||||
match JsFuture::from(crate::diskcache::cache_write(&name, js_sys::Uint8Array::from(&entry[..]))).await {
|
||||
Ok(n) => post_reply(&FrameReply::Stored { key: format!("{key:032x}"), bytes: n.as_f64().unwrap_or(entry.len() as f64) as u64, layer: true }, None),
|
||||
Err(e) => log::warn!("frame worker: layer cache write failed: {e:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user