Skip to content

feat: add LRU-backed render context with cache metrics#9

Merged
AsPulse merged 2 commits into
mainfrom
feat/render-context-memoization
May 25, 2026
Merged

feat: add LRU-backed render context with cache metrics#9
AsPulse merged 2 commits into
mainfrom
feat/render-context-memoization

Conversation

@AsPulse
Copy link
Copy Markdown
Member

@AsPulse AsPulse commented May 25, 2026

Adds a RenderContext abstraction threaded through RasterComponent::render and Timeline::build, plus a concrete CachingRenderContext that memoizes RasterImage outputs keyed by component content hash. Lets time-invariant subtrees (e.g. a DropShadow's static blur) skip re-rendering across frames.

  • tellur-core::dyn_compare (new): DynEq / DynHash super-traits with blanket impls over PartialEq + Hash + 'static, plus a hash_f32 bit-pattern helper. Wires up impl PartialEq + Hash for dyn RasterComponent / dyn VectorComponent so Box<dyn _> and Vec<Box<dyn _>> get structural equality and hashing through the standard library blanket impls.
  • tellur-core::render_context (new): RenderContext trait (fn render(&mut self, c: &dyn RasterComponent, size, target) -> RasterImage) and a PassThrough cache-less implementation. RasterComponent::render and Timeline::build now take &mut dyn RenderContext.
  • Value-type integration: Vec2 / Color / Transform / Anchor / EdgeInsets / TimelineTime / LocalTime get hand-rolled Hash (bits via f32::to_bits); Path / Group / Paint / Fill / Stroke get PartialEq + Hash; layout containers (Padding / Sized / Place / Frame / Stack / DecoratedBox, plus DropShadow) get hand-written PartialEq + Hash because derive can't see through Box<dyn _> fields.
  • tellur-macros: #[raster_component] / #[vector_component] now emit PartialEq (derived) and a hand-rolled Hash impl that routes raw f32 / f64 fields through the bit-pattern hasher, so user-defined component bodies are cache-compatible out of the box.
  • tellur-renderer::render_context (new): CachingRenderContext with a byte-bounded LRU (default 8 GiB) and a system-memory-pressure guard (skips admission / sheds entries above 90% utilization via sysinfo). Keys are (TypeId, content_hash, size_bits, target). Pixel data is bytes::Bytes (Arc-backed) so cache hits are essentially free.
  • CacheMetrics + TypeStats: per-type hits / misses / inclusive vs self time, plus aggregate hit rate / bytes-cached / bytes-evicted / pressure-skips / oversize-skips. Display impl sorts by self_time descending so the actual bottleneck floats to the top. FfmpegEncoder also prints a loop-phase breakdown (build vs ffmpeg-write vs other) at the end of an export.
  • DropShadow::render and composite_children route child renders through ctx.render(&*child, ...) instead of calling child.render(...) directly, so one RenderContext instance can observe every level of the tree.
  • tellur-renderer/examples/memoization_benchmark.rs (new): renders the timeline_to_mp4 scene twice (PassThrough vs CachingRenderContext) without ffmpeg in the loop. On 1920×1080, 5s @ 60fps the cache saves ~6.5% of wall-clock time (DropShadow's 3-pass blur drops from 1200 invocations to 1); the remaining cost is dominated by per-pixel alpha compositing in composite_at, which memoization can't reach.

@AsPulse AsPulse merged commit d71ea9e into main May 25, 2026
14 checks passed
@AsPulse AsPulse deleted the feat/render-context-memoization branch May 25, 2026 11:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant