From 9fc2e69328cdbeafa32f3eb366fbc312088e97d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=91=E6=B4=8B=20=E9=99=88?= <205215139@qq.com> Date: Mon, 12 Oct 2026 13:18:21 +0800 Subject: [PATCH] gpu: read all mask overlay alphas in a single GPU->CPU readback For Overlay::AllMasks, the masks buffer already holds each visible mask's alpha back-to-back in the same order the single-mask overlay uses (cx.slice(all, mi*n, n)). Read the whole buffer once with cx.read instead of one synchronous read_plane per mask, then split it into per-mask Planes on the CPU. Same bytes, but the pipeline stalls once instead of N times. --- crates/gpu/src/render.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/crates/gpu/src/render.rs b/crates/gpu/src/render.rs index 12c7ec75..828d2ba5 100644 --- a/crates/gpu/src/render.rs +++ b/crates/gpu/src/render.rs @@ -712,14 +712,21 @@ pub fn render( _ => Err(m), } }); - // every visible mask's alpha, for `Overlay::AllMasks` (one tint over all masks at once) + // every visible mask's alpha, for `Overlay::AllMasks` (one tint over all masks at once). + // The masks buffer holds each visible mask's alpha back-to-back in the same `list` order the + // single-mask overlay uses, so read the whole buffer back in ONE GPU->CPU transfer and split it + // on the CPU, instead of one synchronous readback per mask (which stalls the pipeline N times). let all_overlay_alphas: Option> = if matches!(req.overlay, lightcraft_pipeline::Overlay::AllMasks { .. }) { masks.as_ref().map(|all| { let list: Vec<_> = s.masks.iter().filter(|m| m.visible && !m.components.is_empty()).collect(); + if list.is_empty() { + return Vec::new(); + } + let raw: Vec = cx.read(all, n * list.len()); let mut alphas = Vec::with_capacity(list.len()); - for (mi, _) in list.iter().enumerate() { - let b = cx.slice(all, mi * n, n); - alphas.push(cx.read_plane(&b, w, h)); + for mi in 0..list.len() { + let s = mi * n; + alphas.push(Plane { width: w, height: h, data: raw[s..s + n].to_vec() }); } alphas })