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:
sergio
2026-05-13 03:12:02 +00:00
parent ab03a61db4
commit 2b8e990cf9
4 changed files with 315 additions and 49 deletions
+14 -2
View File
@@ -14,7 +14,7 @@ use gpui::{
MouseMoveEvent, MouseUpEvent, Point, Render, ScrollDelta, ScrollWheelEvent, Window,
};
use lapaloma_cartesian::{ChartViewport, LapalomaChartElement};
use lapaloma_cartesian::{chart_cache, ChartCacheHandle, ChartViewport, LapalomaChartElement};
use lapaloma_core::buffer::DataBuffer;
use lapaloma_render::{Color, StrokeStyle};
use yahweh_launcher::launch_app;
@@ -38,6 +38,7 @@ struct Demo {
viewport: ChartViewport,
initial_viewport: ChartViewport,
drag: Option<DragAnchor>,
chart_cache: ChartCacheHandle,
}
#[derive(Clone, Copy)]
@@ -65,6 +66,7 @@ impl Demo {
viewport,
initial_viewport: viewport,
drag: None,
chart_cache: chart_cache(),
}
}
@@ -145,6 +147,7 @@ impl Render for Demo {
let plot_bg = Color::rgba(0.10, 0.12, 0.16, 1.0);
let chart = LapalomaChartElement::new(self.viewport)
.background(plot_bg)
.with_cache(self.chart_cache.clone())
.add_series_named(
self.series_sin.clone(),
StrokeStyle::new(2.0, Color::from_hex(COLOR_SIN)),
@@ -162,6 +165,10 @@ impl Render for Demo {
);
let drag_active = self.drag.is_some();
let (pan_blits, rebuilds) = {
let c = self.chart_cache.lock().unwrap();
(c.pan_blits(), c.rebuilds())
};
div()
.id("lapaloma-demo-root")
@@ -200,7 +207,12 @@ impl Render for Demo {
.child(
div()
.text_color(theme.fg_muted)
.child(if drag_active { "· dragging" } else { "" }),
.child(format!(
"· cache: {} pan-blits / {} rebuilds {}",
pan_blits,
rebuilds,
if drag_active { "· dragging" } else { "" },
)),
),
)
.child(