Skip to content

perf(cg): unify picture cache variants for effects-free nodes#614

Merged
softmarshmallow merged 1 commit intomainfrom
feature/heuristic-golick
Mar 30, 2026
Merged

perf(cg): unify picture cache variants for effects-free nodes#614
softmarshmallow merged 1 commit intomainfrom
feature/heuristic-golick

Conversation

@softmarshmallow
Copy link
Copy Markdown
Member

@softmarshmallow softmarshmallow commented Mar 30, 2026

Summary

  • When a node has no effects (blur, shadow, noise, etc.), the SkPicture recorded under the reduced-quality policy (unstable frames) is identical to the full-quality policy (stable/settle frames). This stores such nodes under variant_key=0 regardless of active render policy, so settle reuses pictures already cached during interaction instead of re-recording them.
  • Adds PainterPictureLayer::effects_empty() and RenderPolicy::is_effect_only_variant() to detect when variant unification is safe
  • Applied in both prefill_picture_cache_for_plan (settle path) and Painter draw loop (cache lookup)

Benchmark results (135k.perf.grida, 135K nodes)

Metric Baseline Optimized Change
rt_pan_slow_fit settle 3,571 µs 2,443 µs -32%
circle_small_fit settle 1,681 µs 969 µs -42%
pan_slow_fit settle 1,240 µs 949 µs -23%
Pan avg frame time 14 µs 13 µs no change
Frameloop scenarios all identical all identical no regression

Zero regressions across all scenarios and fixture files.

Test plan

  • cargo test -p cg — all tests pass
  • cargo check -p cg -p grida-canvas-wasm -p grida-dev — all crates compile
  • cargo clippy -p cg --no-deps — no new warnings
  • Criterion benchmarks (viewport_culling, rectangles) — no regressions
  • GPU bench on yrr-main.perf.grida — 20-40% settle reduction verified

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Performance & Optimization
    • Enhanced rendering efficiency through improved cache reuse for layers without effects, reducing redundant processing when switching between different render configurations.

When a node has no effects (blur, shadow, noise, etc.), the SkPicture
recorded under the reduced-quality policy (unstable/interaction frames)
is identical to the full-quality policy (stable/settle frames). Store
such nodes under variant_key=0 regardless of the active render policy,
so the settle pass reuses pictures already cached during interaction
instead of re-recording them.

On yrr-main.perf.grida (135K nodes, 0 effects), this eliminates ~800µs
of redundant LayerEntry clones + SkPicture recordings per settle frame,
yielding 20-40% settle-time reduction across pan/zoom scenarios.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 30, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Mar 30, 2026 6:56am
6 Skipped Deployments
Project Deployment Actions Updated (UTC)
code Ignored Ignored Mar 30, 2026 6:56am
legacy Ignored Ignored Mar 30, 2026 6:56am
backgrounds Skipped Skipped Mar 30, 2026 6:56am
blog Skipped Skipped Mar 30, 2026 6:56am
grida Skipped Skipped Mar 30, 2026 6:56am
viewer Skipped Skipped Mar 30, 2026 6:56am

Request Review

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 30, 2026

Walkthrough

The changes implement a cache unification optimization where render policies with only effect-related differences can reuse cached pictures from nodes that have no effects, by conditionally switching their variant key to zero during cache lookups.

Changes

Cohort / File(s) Summary
New Query Methods
crates/grida-canvas/src/painter/layer.rs, crates/grida-canvas/src/runtime/render_policy.rs
Added PainterPictureLayer::effects_empty() to check effect collection emptiness across layer variants, and RenderPolicy::is_effect_only_variant() to identify policies varying only in effect configuration.
Cache Unification Logic
crates/grida-canvas/src/painter/painter.rs, crates/grida-canvas/src/runtime/scene.rs
Precomputes whether variant unification is possible and conditionally switches variant key to 0 for effect-free nodes during cache lookup and recording, enabling cache reuse across effect-only policy variants.

Sequence Diagram

sequenceDiagram
    participant Painter as Painter
    participant Policy as RenderPolicy
    participant Layer as Layer
    participant Cache as SceneCache

    rect rgba(100, 150, 200, 0.5)
    Note over Painter: Construction Phase
    Painter->>Policy: is_effect_only_variant()?
    Policy-->>Painter: can_unify_variant (bool)
    end

    rect rgba(150, 100, 200, 0.5)
    Note over Painter: Draw Loop Phase
    Painter->>Layer: effects_empty()?
    Layer-->>Painter: has_effects (bool)
    Painter->>Painter: compute effective_key<br/>(switch to 0 if can_unify && !has_effects)
    end

    rect rgba(200, 150, 100, 0.5)
    Note over Painter: Cache Retrieval Phase
    Painter->>Cache: get_node_picture_variant(effective_key)
    Cache-->>Painter: cached_picture or None
    Painter->>Painter: draw cached_picture or render layer
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'perf(cg): unify picture cache variants for effects-free nodes' clearly and specifically describes the main optimization: unifying picture cache variants for nodes without effects. It accurately summarizes the core performance improvement.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/heuristic-golick

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/grida-canvas/src/runtime/scene.rs (1)

141-141: ⚠️ Potential issue | 🟡 Minor

Fix the typo-lint failure reported by CI.

typos flags Line 141 (LOD). If this acronym is intentional, add it to the typo allowlist; otherwise rename to unblock the pipeline.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/grida-canvas/src/runtime/scene.rs` at line 141, The CI typo-lint
flagged the acronym "LOD" in the doc comment, so either add "LOD" to the typos
allowlist or rename the acronym to its full form to satisfy the linter; locate
the doc comment in the Scene struct/file (the line containing "LOD" in
crates/grida-canvas/src/runtime/scene.rs) and either add "LOD" to the project's
typo allowlist configuration or replace "LOD" with the spelled-out phrase (e.g.,
"level of detail") in that comment to unblock the pipeline.
🧹 Nitpick comments (1)
crates/grida-canvas/src/painter/painter.rs (1)

2275-2280: Consider centralizing the effective variant-key rule.

This branch now exists in both cache lookup paths. A tiny helper would make future tweaks to the unification rule less likely to drift.

♻️ Suggested cleanup
+    #[inline]
+    fn effective_variant_key(&self, layer: &PainterPictureLayer) -> u64 {
+        if self.can_unify_variant && layer.effects_empty() {
+            0
+        } else {
+            self.variant_key
+        }
+    }
+
     fn draw_render_commands(&self, commands: &[PainterRenderCommand]) {
         for command in commands {
             match command {
@@
-                        let ek = if self.can_unify_variant && layer.effects_empty() {
-                            0
-                        } else {
-                            self.variant_key
-                        };
+                        let ek = self.effective_variant_key(layer);
                         if let Some(pic) = scene_cache.get_node_picture_variant(layer.id(), ek) {
                             self.canvas.draw_picture(pic, None, None);
                             self.cache_hits.set(self.cache_hits.get() + 1);
@@
         for entry in &list.layers {
             if let Some(scene_cache) = self.scene_cache {
-                let ek = if self.can_unify_variant && entry.layer.effects_empty() {
-                    0
-                } else {
-                    self.variant_key
-                };
+                let ek = self.effective_variant_key(&entry.layer);
                 if let Some(pic) = scene_cache.get_node_picture_variant(&entry.id, ek) {
                     self.canvas.draw_picture(pic, None, None);
                     self.cache_hits.set(self.cache_hits.get() + 1);

Also applies to: 2604-2609

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/grida-canvas/src/painter/painter.rs` around lines 2275 - 2280, The
logic computing the "effective variant key" is duplicated around cache lookups
(see uses of self.can_unify_variant, layer.effects_empty(), self.variant_key and
scene_cache.get_node_picture_variant); extract that into a small helper method
(e.g., fn effective_variant_key(&self, layer: &Layer) -> VariantKey) and replace
both branches to call it before calling
scene_cache.get_node_picture_variant(layer.id(), ek); ensure the helper
implements the rule: return 0 when can_unify_variant && layer.effects_empty(),
otherwise return self.variant_key so future changes centralize the unification
rule.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@crates/grida-canvas/src/runtime/scene.rs`:
- Line 141: The CI typo-lint flagged the acronym "LOD" in the doc comment, so
either add "LOD" to the typos allowlist or rename the acronym to its full form
to satisfy the linter; locate the doc comment in the Scene struct/file (the line
containing "LOD" in crates/grida-canvas/src/runtime/scene.rs) and either add
"LOD" to the project's typo allowlist configuration or replace "LOD" with the
spelled-out phrase (e.g., "level of detail") in that comment to unblock the
pipeline.

---

Nitpick comments:
In `@crates/grida-canvas/src/painter/painter.rs`:
- Around line 2275-2280: The logic computing the "effective variant key" is
duplicated around cache lookups (see uses of self.can_unify_variant,
layer.effects_empty(), self.variant_key and
scene_cache.get_node_picture_variant); extract that into a small helper method
(e.g., fn effective_variant_key(&self, layer: &Layer) -> VariantKey) and replace
both branches to call it before calling
scene_cache.get_node_picture_variant(layer.id(), ek); ensure the helper
implements the rule: return 0 when can_unify_variant && layer.effects_empty(),
otherwise return self.variant_key so future changes centralize the unification
rule.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 85919148-6008-4954-a2af-372f393a5d25

📥 Commits

Reviewing files that changed from the base of the PR and between b0eddd0 and 8dca32c.

📒 Files selected for processing (4)
  • crates/grida-canvas/src/painter/layer.rs
  • crates/grida-canvas/src/painter/painter.rs
  • crates/grida-canvas/src/runtime/render_policy.rs
  • crates/grida-canvas/src/runtime/scene.rs

@softmarshmallow softmarshmallow merged commit 1a94db1 into main Mar 30, 2026
14 checks passed
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