Skip to content

Extract GPU canvas infrastructure into appthere-canvas crate#14

Merged
kevincarlson merged 4 commits into
mainfrom
claude/audit-canvas-extraction-FbOWp
May 19, 2026
Merged

Extract GPU canvas infrastructure into appthere-canvas crate#14
kevincarlson merged 4 commits into
mainfrom
claude/audit-canvas-extraction-FbOWp

Conversation

@kevincarlson

Copy link
Copy Markdown
Member

Summary

This PR extracts generic GPU canvas infrastructure (render cache, scroll state, texture management, and Dioxus integration) from loki-render-cache and loki-text into a new shared appthere-canvas crate. This enables code reuse across the AppThere suite (Loki Text, Loki Calc, Loki Slides, Iris Photo, Iris Draw) while keeping Loki-specific rendering logic in loki-renderer.

Key Changes

New crate: appthere-canvas

  • cache/ — Generic tiered page-render cache (tier assignment, retier logic, metadata store)
  • scroll.rs — Scroll phase tracking and hot-zone geometry
  • key.rsCacheKey marker trait for generic cache keys
  • source.rsPageSource trait and RenderError for render queue
  • texture.rs — GPU texture allocation utilities
  • blit.rs — Cached bilinear-blit pipeline for texture downsampling
  • font_cache.rs — Font data cache (moved from loki-vello)
  • dioxus/scroll_driver.rs — Event-driven scroll-settle detector for Dioxus

Removed from loki-text

  • src/components/document_source.rs (1210 lines) — GPU paint source; logic moved to loki-renderer
  • src/components/wgpu_surface.rs (355 lines) — WGPU canvas component; replaced by loki_renderer::DocumentView

New in loki-text

  • src/editing/state.rs — Editing-layer DocumentState (document, layout, cursor, page count) — separated from GPU rendering state

Refactored in loki-renderer

  • src/page_paint_source.rs — Now imports PageCache, CacheTier, PageIndex from appthere-canvas
  • src/renderer_state.rs — Simplified to hold render cache, scroll signal, and shared Vello renderer
  • src/document_view.rs — Replaces WgpuSurface; uses appthere-canvas::PageCache
  • src/doc_page_source.rs — Uses appthere_canvas::texture::GpuTexture and PageSource trait

Refactored in loki-render-cache

  • Added key.rsCacheKey marker trait (re-exported from appthere-canvas)
  • page_cache.rs — Now generic over K: CacheKey instead of hardcoded PageIndex
  • tier_policy.rs — Updated to use CacheKey trait
  • retier.rs — Updated to use CacheKey trait

Refactored in loki-vello

  • src/font_cache.rs — Now re-exports appthere_canvas::FontDataCache for API stability

Updated imports across workspace

  • loki-text/src/routes/editor/ — Import DocumentState from crate::editing::state instead of components::document_source
  • loki-renderer/ — Import cache types from appthere-canvas instead of loki-render-cache
  • loki-renderer/Cargo.toml — Added appthere-canvas dependency with gpu and dioxus features

Implementation Details

  • Separation of concerns: Editing state (document, layout, cursor) is now in loki-text::editing::state; GPU rendering state is in loki-renderer::RendererState
  • Generic cache: PageCache<K: CacheKey> allows different key types (currently PageIndex in Loki, extensible for other apps)
  • Event-driven scroll: appthere-canvas::dioxus::scroll_driver uses watch channels instead of polling, eliminating the 16ms poll loop
  • **Texture management

https://claude.ai/code/session_017aWU3s4V3bdXh9vjBonxvk

claude added 4 commits May 19, 2026 01:35
Phase A — Generalise PageCache over key type:
- Add CacheKey marker trait (src/key.rs) with blanket impl
- Generalise PageCache<K>, PageGeometry<K>, RetierResult<K>
- Update PageSource trait: type Key: CacheKey associated type
- RenderError::NoSuchPage(String) — doc-model-agnostic
- Add #![forbid(unsafe_code)] to lib.rs
- Update loki-renderer to use explicit PageCache<PageIndex> type params

Phase B — Cache BlitPipeline to avoid per-call pipeline compilation:
- Add BlitPipeline struct (src/blit.rs): new(device) builds pipeline once,
  downsample(device, queue, src, scale) reuses it per call
- Slim downsample_texture free function to delegate to BlitPipeline::new
- Export BlitPipeline from lib.rs under #[cfg(feature = "gpu")]

All 25 unit tests pass; cargo check --workspace clean.

https://claude.ai/code/session_017aWU3s4V3bdXh9vjBonxvk
…enderer, loki-vello, loki-text

Phase A: new appthere-canvas crate with CacheKey trait, PageCache<K>, CacheTier,
PageGeometry, RetierResult, ScrollState/ScrollPhase, PageSource trait, GpuTexture,
BlitPipeline, FontDataCache, and event-driven use_settle_detector (tokio watch channel,
no polling loop). Full Apache-2.0 headers, #![forbid(unsafe_code)], 300-line ceiling.

Phase B: loki-renderer now depends on appthere-canvas instead of loki-render-cache.
Shared vello::Renderer via Arc<Mutex<Option<vello::Renderer>>> across LokiPageSource
instances. RendererState stores Arc<watch::Sender<ScrollPhase>> for event-driven
settling. All imports migrated to appthere_canvas::.

Phase C: loki-vello re-exports FontDataCache from appthere-canvas for API stability.

Phase D: loki-text migrated to loki-renderer::DocumentView. DocumentState moved to
loki-text/src/editing/state.rs with GPU fields removed. All editor modules updated
to import from crate::editing::state. WgpuSurface replaced with DocumentView in
editor_canvas.rs. document_source.rs and wgpu_surface.rs deleted. Zero grep hits
for document_source|wgpu_surface|LokiDocumentSource|WgpuSurface. cargo check,
cargo test, cargo clippy -D warnings all pass.

https://claude.ai/code/session_017aWU3s4V3bdXh9vjBonxvk
…View

RendererState::new was calling use_signal() inside a use_hook() closure,
causing "hook list already borrowed: BorrowMutError" at document open.

use_hook mutably borrows the hook list for its entire closure body; any
nested hook call (including use_signal) triggers a double-borrow panic.

Fix: use_signal is now called unconditionally at the top level of
DocumentView and the resulting Signal passed into RendererState::new,
which no longer calls any Dioxus hooks itself.

https://claude.ai/code/session_017aWU3s4V3bdXh9vjBonxvk
Fix 1 — document_view.rs: apply PTS_TO_CSS_PX (96.0/72.0) to every use of
p.page_size.width/height in the page geometry loop. loki-layout stores page
dimensions in typographic points; they were being used directly as CSS pixels,
making every canvas element ~25% too small in each dimension and causing the
rightmost/bottom 25% of each page to be clipped by the render target.

Fix 2 — document_view.rs: replace position:absolute (confirmed unsupported in
Blitz) with block flow + margin:auto for horizontal centring. Remove the
accumulated `top` offset and `total_height` from the inner container; block
flow determines the container height automatically. Inter-page gap moved from
absolute-top arithmetic to margin-bottom on each PageTile. Remove the `top`
field from PageTileProps.

Fix 3 — renderer_state.rs: apply the same PTS_TO_CSS_PX conversion in
on_settle() when building PageGeometry top_px/bottom_px. Without this, tier
assignment compared point-unit page positions against CSS-pixel scroll
positions, incorrectly assigning Hot/Warm/Cold tiers.

Fix 4 — document_view.rs: remove overflow-y:auto from DocumentView's outer
div. It was creating a nested scroll container inside editor_canvas.rs's
existing overflow-y:auto container. The parent container owns scroll
behaviour; DocumentView is now a non-scrolling block.

Fix 5 — editor_canvas.rs: document the hardcoded viewport_height_px:800.0
with a TODO comment. Affects cache tier zones only, not visual correctness.

https://claude.ai/code/session_017aWU3s4V3bdXh9vjBonxvk
@kevincarlson kevincarlson merged commit da422ba into main May 19, 2026
1 check passed
@kevincarlson kevincarlson self-assigned this May 19, 2026
@kevincarlson kevincarlson deleted the claude/audit-canvas-extraction-FbOWp branch May 19, 2026 02:47
@AppThere AppThere locked as resolved and limited conversation to collaborators May 19, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants