render, types, ci: resolution-time dp with per-canvas density, bounded GLES image cache, clippy gate, backend capability matrix
Length::dp no longer collapses to absolute pixels at construction: the design value travels in a new LengthBase::Dp variant and the density multiplication happens when the length is resolved. Previously dp( n ) baked in whatever density() returned at view-build time, so correctness across output changes depended on the view being rebuilt after set_density and in that order; now a density change is picked up by the very next paint with no reconstruction. Length::resolve keeps its signature (process density), and the new Length::resolve_with_density takes an explicit factor. dp becomes const in the bargain. Density also becomes overridable per canvas, the first step towards surface-local responsive state. SoftwareCanvas and GlesCanvas carry a density: Option<f32> analogous to the layout_viewport introduced for sub-canvas fluid resolution: None means "use the process global", Canvas::set_density pins a local factor, and sub-canvases inherit it. All canvas-routed resolution honours it — geom_px / font_px for stock-widget design pixels, and the new Canvas::resolve_geom / resolve_font for explicit Length values, which every widget now uses in place of the raw l.resolve( canvas.viewport_layout(), EM ) pattern (row, column, wrap_grid, spacer, container, separator, button, text, rich_text, text_edit, list_item, vslider, image, and the container draw path). Overlay sizing keeps resolving against the main surface with the global density, which is what it describes. New tests cover explicit-density resolution, resolution-time application, the local-over-global override and sub-canvas inheritance. The GLES image texture cache is now bounded. It was content-keyed but unbounded and never evicted, so a stream of distinct buffers — a photo carousel, video thumbnails — grew GPU memory for the lifetime of the canvas. The cache now tracks an estimated byte total (RGBA8, w × h × 4) against a 32 MiB budget and evicts least-recently-drawn textures on insert; the most recent entry is never evicted, so a single texture larger than the whole budget still draws and simply owns the cache until replaced. Drop-time cleanup is unchanged: drain deletes whatever the map holds. CI gains a Clippy step (workspace, all targets, test-support, -D warnings) sharing the build cache of the test job, with make clippy mirroring the invocation locally and CONTRIBUTING listing it. Run make clippy locally before pushing the first time — the gate has not seen the tree yet and pre-existing lints will fail CI until addressed. docs/backends.md formalises the software/GLES capability matrix that was previously scattered across per-method rustdoc: parity set (fills, strokes, text, images, paths, path clips), graceful degradations on software (flat-fill gradients, no shadows, no backdrop blur, hard bottom edge), GPU-only features (external textures), the shared Oklab-fallback limitation, and the cross-backend blit panic. Linked from README, onboarding and architecture's known-gaps list, which now states the parity gaps explicitly. The dp/density prose in architecture.md, lib.rs and the Length rustdoc is updated for resolution-time semantics and the per-canvas override.
This commit is contained in:
33
docs/backends.md
Normal file
33
docs/backends.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Backend capability matrix
|
||||
|
||||
ltk renders through one of two interchangeable backends behind the same `Canvas` API: **software** (CPU rasterisation with tiny-skia + fontdue into a `wl_shm` buffer) and **GLES** (GPU rasterisation via EGL + OpenGL ES 2/3). `ltk::run()` selects GLES when an EGL context can be created and falls back to software; an embedder driving `core::UiSurface` can force either. `Canvas::is_software()` lets a caller branch on the active backend at draw time.
|
||||
|
||||
The two backends are kept at visual parity for the common primitives — solid fills, strokes, lines, text, images, vector paths. This page is the canonical statement of where they differ, so the information does not have to be reassembled from per-method rustdoc. When a difference listed here is closed, update this table in the same patch.
|
||||
|
||||
## Matrix
|
||||
|
||||
| Capability | Software | GLES | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| Solid fills, strokes, lines (`fill_rect`, `stroke_rect`, `draw_line`) | Yes | Yes | Visual parity. |
|
||||
| Text (`draw_text`, `draw_text_with_font`) | Yes | Yes | Both snap pen positions to integer pixels; glyph caches differ (per-canvas hashmap with eviction vs. shared atlas texture) but output is at parity. |
|
||||
| Images (`draw_image_data`) | Yes | Yes | Both snap destinations to integer pixels. GLES caches uploaded textures content-keyed, bounded to 32 MiB with LRU eviction; software draws directly from the buffer. |
|
||||
| External GL texture (`draw_external_texture`, `External::texture`) | No-op | Yes | Software has no GL state to sample from. Use `External::cpu` for a backend-independent immediate-mode draw hook. |
|
||||
| Vector paths (`fill_path`, `stroke_path`) | Yes | Yes | Both rasterise through the same tiny-skia code, so shapes are at parity. GLES uploads the rasterised path as a texture per draw — costly for large animated vectors. |
|
||||
| Rect clipping (`set_clip_rects`) | Exact multi-rect mask | Bounding-box scissor | On GLES several dirty rects clip to their union; software masks each rect exactly. Branch on `is_software()` when exactness matters. |
|
||||
| Path clipping (`set_clip_path`) | Yes (coverage mask) | Yes (offscreen layer + anti-aliased coverage composite) | Visual parity; different mechanisms. |
|
||||
| Gradients (`fill_paint_rect` with `Linear` / `Radial`) | Flat fill from the first stop | Dedicated shaders | Software gradient rendering is a known gap; tiny-skia could render them natively but is not wired up yet. |
|
||||
| Gradient interpolation spaces | `srgb`, `linear-rgb` | `srgb`, `linear-rgb` | `oklab` is declared in the schema but currently falls back to linear-light **on both backends** (shared LUT builder). |
|
||||
| Outer drop shadows (`fill_shadow_outer`) | No-op | Analytic soft-shadow shader | Software shadow rendering is a known gap. |
|
||||
| Inset shadows (`fill_shadow_inset`) | No-op | Yes, with `normal` / `plus-lighter` / `multiply` / `screen` / `overlay` blends | `overlay` routes through an FBO snapshot on GLES. |
|
||||
| Backdrop blur (Glass surfaces with a `backdrop` block) | Not rendered | Yes (aux-FBO Gaussian pipeline) | Themes ship `-flat` surface variants without `backdrop`; nothing selects them per backend automatically — pass one explicitly when targeting software. |
|
||||
| Bottom-edge fade (`blit_fade_bottom`, `viewport.fade_bottom`) | Hard edge | Feathered | Software ignores `fade_bottom_px`. |
|
||||
| Cross-backend blit (`blit` between a software and a GLES canvas) | — | — | Unimplemented (panics). Never occurs in practice: sub-canvases always share their parent's backend. |
|
||||
| Pixel readback (`read_rgba_pixels`) | Yes (un-premultiplies) | Yes | Both return tightly packed straight-alpha RGBA8. |
|
||||
| Presentation | `write_to_wayland_buf` into `wl_shm` | `eglSwapBuffers` (with damage when available) | `write_to_wayland_buf` is a no-op on GLES; `present` is the GLES path. |
|
||||
| Animation frame-rate cap | ~30 Hz default (overridable via `App::cap_software_animation`) | Uncapped (compositor frame callbacks) | The cap protects the CPU path from sustained full-rate rasterisation. |
|
||||
|
||||
## Practical guidance
|
||||
|
||||
For a theme or app that must look identical on both backends, stay within the parity set: solid fills, strokes, text, images, paths, path clips. The visible divergences are gradients, shadows, backdrop blur and the bottom fade — all degrade gracefully on software (flat fill, no shadow, no blur, hard edge) rather than failing, so a software rendering of a GLES-designed theme is flatter but functional.
|
||||
|
||||
The pixel-level test suite runs on the software backend; the GLES paths that need a live GL context (layer composites, shaders) are exercised by the examples. There are currently no golden tests comparing the two backends' output — parity for the common primitives is maintained by review and by the shared rasterisation code where it exists (paths).
|
||||
Reference in New Issue
Block a user