Fix onion ghosts: show on blank cels + fault in on seek

- Removed the early `continue` that skipped a layer with no resident content — it
  also skipped the active raster layer's onion ghosts when the current cel was blank
  (the exact case you trace a new cel from neighbours). Each render arm already
  guards its own empty case, so the skip was redundant.
- Ghost texture resolution now falls through cache → resident raw_pixels (upload) →
  in-memory proxy → request fault-in, so neighbours that were paged out (e.g. after a
  seek, or saved-this-session with no decoded proxy) page in and ghost in, instead of
  silently rendering nothing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Skyler Lehmkuhl 2026-06-20 23:18:24 -04:00
parent 4dc937f9c0
commit 78109cda93
1 changed files with 48 additions and 30 deletions

View File

@ -1250,12 +1250,6 @@ impl egui_wgpu::CallbackTrait for VelloCallback {
None None
}; };
if !rendered_layer.has_content && gpu_canvas_kf.is_none() && raster_cache_kf.is_none()
&& raster_proxy_blit.is_none()
{
continue;
}
match &rendered_layer.layer_type { match &rendered_layer.layer_type {
RenderedLayerType::Vector => { RenderedLayerType::Vector => {
// Vector/group layer — render Vello scene → sRGB → linear → composite. // Vector/group layer — render Vello scene → sRGB → linear → composite.
@ -1343,26 +1337,44 @@ impl egui_wgpu::CallbackTrait for VelloCallback {
&instance_resources.hdr_texture_view, &instance_resources.hdr_texture_view,
) { ) {
let mut drew = false; let mut drew = false;
let mut need_fault = false;
if let Ok(mut gpu_brush) = shared.gpu_brush.lock() { if let Ok(mut gpu_brush) = shared.gpu_brush.lock() {
let has_full = gpu_brush.raster_layer_cache.contains_key(&kf_id); // Resolve a texture, in priority order:
if !has_full { // 1) cached full texture
// Upload the proxy on demand from the keyframe's pixels. // 2) resident raw_pixels → upload full (e.g. an
if let Some(lightningbeam_core::layer::AnyLayer::Raster(rl)) = // unsaved neighbour whose GPU texture evicted)
self.ctx.document.get_layer(&rendered_layer.layer_id) // 3) in-memory proxy → upload proxy
{ // 4) none → request a fault-in (seek to a paged-out frame)
if let Some(p) = rl.keyframes.iter().find(|k| k.id == kf_id) let resolved: Option<(u32, u32, bool)> =
.and_then(|k| k.proxy.as_ref()) if gpu_brush.raster_layer_cache.contains_key(&kf_id) {
{ gpu_brush.raster_layer_cache.get(&kf_id).map(|c| (c.width, c.height, false))
gpu_brush.ensure_proxy_texture(device, queue, kf_id, &p.pixels, p.width, p.height);
}
}
}
let src = if has_full {
gpu_brush.raster_layer_cache.get(&kf_id).map(|c| (c, c.width, c.height, false))
} else { } else {
gpu_brush.get_proxy_texture(&kf_id).map(|c| (c, lw, lh, true)) let kf = match self.ctx.document.get_layer(&rendered_layer.layer_id) {
Some(lightningbeam_core::layer::AnyLayer::Raster(rl)) =>
rl.keyframes.iter().find(|k| k.id == kf_id),
_ => None,
}; };
if let Some((canvas, sw, sh, is_proxy)) = src { match kf {
Some(kf) if kf.raw_pixels.len() == (kf.width * kf.height * 4) as usize => {
gpu_brush.ensure_layer_texture(device, queue, kf_id, &kf.raw_pixels, kf.width, kf.height, false);
Some((kf.width, kf.height, false))
}
Some(kf) if kf.proxy.is_some() => {
let p = kf.proxy.as_ref().unwrap();
gpu_brush.ensure_proxy_texture(device, queue, kf_id, &p.pixels, p.width, p.height);
Some((lw, lh, true))
}
Some(kf) if kf.needs_fault_in => { need_fault = true; None }
_ => None,
}
};
if let Some((sw, sh, is_proxy)) = resolved {
let tex = if is_proxy {
gpu_brush.get_proxy_texture(&kf_id)
} else {
gpu_brush.raster_layer_cache.get(&kf_id)
};
if let Some(canvas) = tex {
let bt = crate::gpu_brush::BlitTransform::new(*layer_transform, sw, sh, width, height) let bt = crate::gpu_brush::BlitTransform::new(*layer_transform, sw, sh, width, height)
.with_tint(tint[0], tint[1], tint[2]); .with_tint(tint[0], tint[1], tint[2]);
if is_proxy { if is_proxy {
@ -1373,6 +1385,12 @@ impl egui_wgpu::CallbackTrait for VelloCallback {
drew = true; drew = true;
} }
} }
}
if need_fault {
if let Ok(mut reqs) = self.ctx.raster_fault_requests.lock() {
reqs.insert(kf_id);
}
}
if drew { if drew {
let cl = lightningbeam_core::gpu::CompositorLayer::new( let cl = lightningbeam_core::gpu::CompositorLayer::new(
gh, rendered_layer.opacity * opacity, rendered_layer.blend_mode, gh, rendered_layer.opacity * opacity, rendered_layer.blend_mode,