feat(lapaloma-cartesian): picture cache pan-blit
- ChartCache + ChartCacheHandle (Arc<Mutex<...>>) cacheable entre frames. El Render host crea uno con chart_cache() y lo pasa al Element con .with_cache(handle). Sin handle, cada frame rebuild completo (correcto pero sin la optimización). - Hash estructural: plot rect + viewport.span (no x_min/y_min) + per-series (data.revision + data.len + stroke). 5 tests cubren estabilidad, pan no invalida, zoom invalida, data revision invalida, plot rect invalida. - En paint: si el hash matches, pan-blit = copia las coords cacheadas con offset (dx_px, dy_px) calculado del diff entre viewport.x_min cached vs actual. Salteamos LTTB + projection. - LineSeries::compute_projected expone el pipe LTTB + project_buffer como método público para que el Element pueda cachear sin pasar por paint(). - Demo multi-series usa el cache; header muestra "cache: N pan-blits / M rebuilds" en vivo para que se vea la métrica al draguear (pan-blits crece) y al zoomear (rebuilds crece). Limitación v0.1 anotada en código: el doc canónico (sección 4.4) usa una textura offscreen blitable; GPUI 0.2 no expone esa primitiva directa. La impl actual cachea coords proyectadas y emite las polilíneas con offset — mismo ahorro de CPU (saltea LTTB) sin GPU texture cache. 51 tests verdes (28 cartesian incluyendo 5 nuevos del structural_hash, 20 core, 3 render). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -58,35 +58,42 @@ impl<'a> LineSeries<'a> {
|
||||
Self { data, stroke, lttb_target: None }
|
||||
}
|
||||
|
||||
fn effective_target(&self, plot_w: f32) -> usize {
|
||||
pub fn effective_target(&self, plot_w: f32) -> usize {
|
||||
self.lttb_target.unwrap_or_else(|| (plot_w as usize).saturating_mul(3))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Series for LineSeries<'a> {
|
||||
fn paint(&self, ctx: &mut PaintCtx<'_>, canvas: &mut dyn Canvas) {
|
||||
/// Materializa las coords proyectadas a pixel space en `out`,
|
||||
/// aplicando LTTB cuando densidad > target. `out` se clearea.
|
||||
///
|
||||
/// Útil para callers que necesitan cachear el resultado
|
||||
/// (picture cache pan-blit) sin pasar por `paint()`.
|
||||
pub fn compute_projected(&self, cs: &CoordinateSystem, out: &mut Vec<f32>) {
|
||||
out.clear();
|
||||
if self.data.len() < 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
let target = self.effective_target(ctx.cs.plot.w);
|
||||
|
||||
ctx.scratch.clear();
|
||||
|
||||
let target = self.effective_target(cs.plot.w);
|
||||
if self.data.len() > target {
|
||||
// Decimar primero (en coords de dominio), proyectar después.
|
||||
let mut idx = Vec::with_capacity(target);
|
||||
let mut idx: Vec<usize> = Vec::with_capacity(target);
|
||||
lttb::lttb_indices(self.data.coords(), target, &mut idx);
|
||||
let mut decimated: Vec<f32> = Vec::with_capacity(idx.len() * 2);
|
||||
for i in idx {
|
||||
decimated.push(self.data.coords()[i * 2]);
|
||||
decimated.push(self.data.coords()[i * 2 + 1]);
|
||||
}
|
||||
ctx.cs.project_buffer(&decimated, ctx.scratch);
|
||||
cs.project_buffer(&decimated, out);
|
||||
} else {
|
||||
ctx.cs.project_buffer(self.data.coords(), ctx.scratch);
|
||||
cs.project_buffer(self.data.coords(), out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Series for LineSeries<'a> {
|
||||
fn paint(&self, ctx: &mut PaintCtx<'_>, canvas: &mut dyn Canvas) {
|
||||
self.compute_projected(&ctx.cs, ctx.scratch);
|
||||
if ctx.scratch.len() < 4 {
|
||||
return;
|
||||
}
|
||||
canvas.stroke_polyline(ctx.scratch, self.stroke);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user