Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions docs/codebase-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ Daemon mode therefore provides a persistent background service that reacts to us
- Communicate with `capture::CaptureManager` for screenshot actions.
- Exit when `InputState.should_exit` is set (Escape, tray close, etc.).

`WaylandState` coordinates the runtime owners handlers need. `FocusState` owns activation, focus, and startup acquisition; `ProtocolGlobals` owns bound globals and toolkit handler state; `PointerRuntime` owns pointer position, board-pan and chrome gestures, cursor, pointer-lock, and single-contact touch protocol lifecycles; `ToolbarChrome` owns toolbar placement, inline interaction, and fade state; `ToolbarDrag` owns built-in and GTK drag lifecycles; `RegionCaptureRuntime` owns region selection generations, active/review/window-snap state, and the window-query and cut-preview workers; `AcquisitionRuntime` owns the capacity-one screen-acquisition and zoom-waiter registries plus eyedropper source correlation; `FrozenState` owns its availability and one-shot startup gate; `SurfaceState` owns output/fullscreen/layer placement and frozen-fullscreen transitions; `OverlaySuppressionState` owns suppression reason, keyboard policy, capture barrier, and clickthrough state; `RenderRuntime` owns the canvas layer cache, render-profile baseline, and per-effect damage history; `InputHudRuntime` owns system-reader lifecycle and reconciliation; `SpotlightRuntime` owns render memory, warning latches, and wheel timing; `ClipboardRuntime` owns single-flight clipboard workers and queue policy; `PreferenceStores` groups durable preference stores and workers; `UiAnimationClock` owns animation scheduling; and `FontCatalogPrewarm` owns the one-shot font scan. The root retains cross-owner coordination. `handlers::route::SurfaceRouter` is the single classifier for pointer, touch, and stylus surfaces and supplies overlay screen coordinates before modality-specific dispatch.
`WaylandState` coordinates the runtime owners handlers need. `FocusState` owns activation, focus, and startup acquisition; `ProtocolGlobals` owns bound globals and toolkit handler state; `PointerRuntime` owns pointer position, board-pan and chrome gestures, cursor, pointer-lock, and single-contact touch protocol lifecycles; `ToolbarChrome` owns toolbar placement, inline interaction, and fade state; `ToolbarDrag` owns built-in and GTK drag lifecycles; `RegionCaptureRuntime` owns region selection generations, active/review/window-snap state, and the window-query and cut-preview workers; `AcquisitionRuntime` owns the capacity-one screen-acquisition and zoom-waiter registries plus eyedropper source correlation; `FrozenState` owns its availability and one-shot startup gate; `SurfaceState` owns output/fullscreen/layer placement and frozen-fullscreen transitions; `OverlaySuppressionState` owns suppression reason, keyboard policy, capture barrier, and clickthrough state; `RenderRuntime` owns the canvas layer cache, reusable image and blur caches, render-profile baseline, and per-effect damage history; `InputHudRuntime` owns system-reader lifecycle and reconciliation; `SpotlightRuntime` owns render memory, warning latches, and wheel timing; `ClipboardRuntime` owns single-flight clipboard workers and queue policy; `PreferenceStores` groups durable preference stores and workers; `UiAnimationClock` owns animation scheduling; and `FontCatalogPrewarm` owns the one-shot font scan. The root retains cross-owner coordination. `handlers::route::SurfaceRouter` is the single classifier for pointer, touch, and stylus surfaces and supplies overlay screen coordinates before modality-specific dispatch.

Within `PointerRuntime`, pending chrome targets and device-owned release suppression have separate lifecycles. Clearing a toast, HUD, or zoom-chip press preserves mouse and touch suppression; cancelling a touch clears only its own release latch.

Expand Down Expand Up @@ -146,8 +146,14 @@ Freeze capture waits for the overlay-suppression frame, then selects `wlr-screen
builds an owned `FramePlan`, paints with Cairo, and submits the buffer. A busy buffer pool
defers the frame before preparation. `state/render/{prepare,plan,paint,submit}.rs` keep
these boundaries explicit; the planner derives canvas policy and screen/world/buffer damage.
- `CanvasRenderCtx` borrows frame parameters and the local Cairo target. The canvas layer
cache, reusable profile baseline, and effect damage history remain in `RenderRuntime`.
- `CanvasRenderCtx` borrows frame parameters and the local Cairo target. `RenderRuntime`
owns the canvas layer cache, drawing resources, reusable profile baseline, and effect
damage history. Drawing resources stay outside the owned `FramePlan`.
- `draw::RenderCtx` borrows a Cairo target and explicit `RenderCaches` for image and blur
rendering. The overlay shares its cache owner across direct drawing, layer baking,
provisional previews, and board thumbnails. Each export job creates its own owner;
PDF pages and magnified-page raster passes share that job's image cache. Export blur
remains uncached because page backdrops can share a numeric source identifier.
- Draw order: board background → finalized shapes → spotlight effects → provisional shape
→ text cursor preview → UI. Color-profile passes surround UI painting according to their
selected targets. Main-surface submission precedes toolbar rendering and marking capture
Expand All @@ -157,6 +163,12 @@ Freeze capture waits for the overlay-suppression frame, then selects `wlr-screen
The result is a predictable pipeline: Wayland → handlers → `InputState` →
`BoardManager`/active `BoardPages`/`DrawingState` → `WaylandState::render`.

The public `draw::render_shape*` and `draw::render_blur_rect` convenience functions create
local drawing resources, as do `InputState::render_provisional_shape` and
`ui::render_board_picker`. Repeated internal rendering uses an explicitly borrowed cache
owner. Public PNG/PDF export functions keep their snapshot-based interfaces and create
Cairo resources inside the rendering job, after any worker-thread handoff.

---

## 5. Capture Pipeline
Expand Down
2 changes: 1 addition & 1 deletion src/backend/wayland/state/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
- This subtree supports live overlay runtime state: buffers, damage, boards, capture routing, clipboard paste, color picker, onboarding, PDF export, render helpers, toolbar plumbing, zoom, and core accessors.
- Runtime owners extracted from `WaylandState` live beside it: `focus.rs` (activation, focus, and startup acquisition), `protocol_globals.rs` (bound globals and toolkit handler state), `pointer_runtime.rs` (pointer position, board-pan and chrome gestures, cursor, pointer-lock, and touch lifecycles), `region_capture/runtime.rs` (region generations, active/review/window-snap state, and query/preview workers), `acquisition.rs` (screen acquisition, zoom waiters, and eyedropper source correlation), `input_hud.rs` (system-reader lifecycle and reconciliation), `spotlight_runtime.rs` (render memory, warning latches, and wheel timing), `clipboard_runtime.rs` (single-flight workers and queue policy), `preference_stores.rs` (persistence stores and workers), `ui_animation.rs` (animation scheduling), `font_catalog.rs` (font-catalog prewarm), `text_input.rs` (text-input-v3 lifecycle and commit serials), `tablet_runtime.rs` (tablet-input-v2 objects and stylus contact), `key_repeat.rs` (manual key-repeat timing), and `helper_launch.rs` (About/configurator launches requested by input).
- `core/overlay.rs` owns suppression policy and capture-barrier state; `../surface.rs` owns output/fullscreen/layer placement and frozen-fullscreen transitions.
- `render/` separates mutable preparation, pure `FramePlan` decisions, Cairo painting, and buffer submission. `CanvasRenderCtx` borrows frame parameters; `RenderRuntime` owns cache, profile baseline, and per-effect damage history. `toolbar/` owns `ToolbarChrome` (placement, inline interaction, and fade state), `ToolbarDrag` (built-in and GTK drag lifecycles), and runtime toolbar effects; `clipboard/` owns session paste helpers.
- `render/` separates mutable preparation, pure `FramePlan` decisions, Cairo painting, and buffer submission. `CanvasRenderCtx` borrows frame parameters; `RenderRuntime` owns the canvas layer cache, explicit drawing caches, profile baseline, and per-effect damage history. Direct canvas rendering, layer baking, previews, and board thumbnails borrow that drawing owner in short scopes. `toolbar/` owns `ToolbarChrome` (placement, inline interaction, and fade state), `ToolbarDrag` (built-in and GTK drag lifecycles), and runtime toolbar effects; `clipboard/` owns session paste helpers.

## Invariants
- Preserve snapshot boundaries for export and session actions.
Expand Down
113 changes: 83 additions & 30 deletions src/backend/wayland/state/canvas_layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,14 @@ impl CanvasLayerCache {
/// Renders one committed shape with the standard eraser/blur replay handling.
/// Shared between the direct canvas render path and the layer-cache bake.
pub(in crate::backend::wayland) fn render_committed_shape(
ctx: &cairo::Context,
render: &mut crate::draw::RenderCtx<'_, '_>,
drawn_shape: &crate::draw::DrawnShape,
replay_ctx: &crate::draw::EraserReplayContext<'_>,
text_halo_enabled: bool,
) {
match &drawn_shape.shape {
crate::draw::Shape::EraserStroke { points, brush } => {
crate::draw::render_eraser_stroke(ctx, points, brush, replay_ctx);
crate::draw::render_eraser_stroke(render.cairo, points, brush, replay_ctx);
}
crate::draw::Shape::BlurRect {
x,
Expand All @@ -109,8 +109,7 @@ pub(in crate::backend::wayland) fn render_committed_shape(
strength,
style,
} => {
crate::draw::render_blur_rect(
ctx,
render.render_blur_rect(
crate::draw::BlurRectParams {
x: *x,
y: *y,
Expand All @@ -124,7 +123,7 @@ pub(in crate::backend::wayland) fn render_committed_shape(
);
}
other => {
crate::draw::render_shape_with_halo(ctx, other, text_halo_enabled);
render.render_shape_with_halo(other, text_halo_enabled);
}
}
}
Expand Down Expand Up @@ -154,16 +153,10 @@ impl WaylandState {
height: u32,
scale: i32,
) -> bool {
let scale = scale.max(1);
let (origin_x, origin_y) = self.canvas_view_origin();
let view_x = origin_x.floor() as i32;
let view_y = origin_y.floor() as i32;
let logical_w = width.min(i32::MAX as u32) as i32;
let logical_h = height.min(i32::MAX as u32) as i32;
if logical_w <= 0 || logical_h <= 0 {
let origin = self.canvas_view_origin();
if width == 0 || height == 0 {
return false;
}

let background = match self.input_state.boards.active_background() {
crate::input::BoardBackground::Solid(color) => Some(*color),
crate::input::BoardBackground::Transparent => None,
Expand All @@ -175,10 +168,65 @@ impl WaylandState {
);
let generation = self.input_state.canvas_content_generation();
let frame = self.input_state.boards.active_frame();
let shapes_len = frame.shapes.len();
let last_shape_id = frame.shapes.last().map(|shape| shape.id);
let (cache, draw_caches) = self.render.canvas_draw_parts_mut();
cache.ensure(
draw_caches,
&frame.shapes,
CanvasLayerInputs {
width,
height,
scale,
origin,
background,
text_halo_enabled,
board_key,
generation,
},
)
}
}

let cache = self.render.canvas_layer_cache();
#[derive(Clone, Copy)]
pub(super) struct CanvasLayerInputs {
pub(super) width: u32,
pub(super) height: u32,
pub(super) scale: i32,
pub(super) origin: (f64, f64),
pub(super) background: Option<Color>,
pub(super) text_halo_enabled: bool,
pub(super) board_key: (usize, usize),
pub(super) generation: u64,
}

impl CanvasLayerCache {
pub(super) fn ensure(
&mut self,
draw_caches: &mut crate::draw::RenderCaches,
shapes: &[crate::draw::DrawnShape],
inputs: CanvasLayerInputs,
) -> bool {
let CanvasLayerInputs {
width,
height,
scale,
origin,
background,
text_halo_enabled,
board_key,
generation,
} = inputs;
let scale = scale.max(1);
let (origin_x, origin_y) = origin;
let view_x = origin_x.floor() as i32;
let view_y = origin_y.floor() as i32;
let logical_w = width.min(i32::MAX as u32) as i32;
let logical_h = height.min(i32::MAX as u32) as i32;
if logical_w <= 0 || logical_h <= 0 {
return false;
}
let shapes_len = shapes.len();
let last_shape_id = shapes.last().map(|shape| shape.id);
let cache = self;
let params_match = cache.valid
&& cache.surface.is_some()
&& cache.scale == scale
Expand All @@ -204,37 +252,35 @@ impl WaylandState {
let phys_w = bake_w.saturating_mul(scale);
let phys_h = bake_h.saturating_mul(scale);
if phys_w <= 0 || phys_h <= 0 || phys_w > CAIRO_MAX_DIM || phys_h > CAIRO_MAX_DIM {
self.render.canvas_layer_cache_mut().clear();
cache.clear();
return false;
}
if phys_w as usize * phys_h as usize * 4 > MAX_CACHE_BYTES {
self.render.canvas_layer_cache_mut().clear();
cache.clear();
return false;
}

let reuse_surface = self
.render
.canvas_layer_cache_mut()
let reuse_surface = cache
.surface
.as_ref()
.is_some_and(|surface| surface.width() == phys_w && surface.height() == phys_h);
if !reuse_surface {
match cairo::ImageSurface::create(cairo::Format::ARgb32, phys_w, phys_h) {
Ok(surface) => self.render.canvas_layer_cache_mut().surface = Some(surface),
Ok(surface) => cache.surface = Some(surface),
Err(err) => {
debug!("canvas layer cache: surface allocation failed: {err}");
self.render.canvas_layer_cache_mut().clear();
cache.clear();
return false;
}
}
}

{
let Some(surface) = self.render.canvas_layer_cache_mut().surface.as_ref() else {
let Some(surface) = cache.surface.as_ref() else {
return false;
};
let Ok(bake_ctx) = cairo::Context::new(surface) else {
self.render.canvas_layer_cache_mut().clear();
cache.clear();
return false;
};

Expand Down Expand Up @@ -267,20 +313,27 @@ impl WaylandState {
width: bake_w,
height: bake_h,
};
let frame = self.input_state.boards.active_frame();
for drawn_shape in &frame.shapes {
let mut render = crate::draw::RenderCtx {
cairo: &bake_ctx,
caches: draw_caches,
};
for drawn_shape in shapes {
if let Some(bbox) = drawn_shape.bounding_box()
&& rects_intersect(bbox, bake_bounds)
{
render_committed_shape(&bake_ctx, drawn_shape, &replay_ctx, text_halo_enabled);
render_committed_shape(
&mut render,
drawn_shape,
&replay_ctx,
text_halo_enabled,
);
}
}
}
if let Some(surface) = self.render.canvas_layer_cache_mut().surface.as_ref() {
if let Some(surface) = cache.surface.as_ref() {
surface.flush();
}

let cache = self.render.canvas_layer_cache_mut();
cache.world_x = world_x;
cache.world_y = world_y;
cache.width = bake_w;
Expand Down
Loading
Loading