diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md index 0de2c83..9fec9ab 100644 --- a/docs/dev/architecture.md +++ b/docs/dev/architecture.md @@ -11,9 +11,10 @@ and records the decisions and constraints behind the design. ## 1. Overview DarkRoom is a Rust application with a Slint interface. On Linux it renders through wgpu to Vulkan. -On Android it renders through Skia to OpenGL — not by preference but because wgpu's Vulkan -swapchain cannot pre-rotate, which tears a portrait window on a landscape-mounted panel -([technical-debt.md TD-1](technical-debt.md)). The compute passes are wgpu on both. The design is organised around four ideas, each of which the rest of this +On Android it renders through Skia, also on wgpu's Vulkan swapchain, which a patched wgpu-hal and +Skia renderer pre-rotate for a landscape-mounted panel ([technical-debt.md TD-1](technical-debt.md), +[third_party/](../../third_party/README.md)). The compute passes are wgpu on both, on the same +device the compositor draws with. The design is organised around four ideas, each of which the rest of this document elaborates: 1. **Pixels stay on the GPU.** From decode to display, image data never round-trips through the @@ -1125,12 +1126,11 @@ At 4K the shader finishes in 0.28 ms and then 7.15 ms is spent moving pixels thr 26× overhead that scales with area, which is why an uncapped window resize falls off a cliff. The constraint is not a stylistic preference; it is the dominant cost in the frame. -**One exception, on Android only, and it is debt rather than a revision.** The develop view there -reads the frame back rather than handing over a texture, because zero-copy requires Slint to draw -with wgpu and wgpu's Android swapchain tears a portrait window. The reasoning, the measurements -that forced it and what would remove it are in [technical-debt.md TD-1](technical-debt.md). The -constraint above still governs every other path, including the desktop develop view and the export -pipeline, and the Android exception is expected to be temporary. +**No exceptions since 0.15.0.** Android read the develop frame back until then, because wgpu's +Vulkan swapchain could not pre-rotate and a portrait window tore on the tablet. Two local patches +removed the need ([technical-debt.md TD-1](technical-debt.md)), so the frame reaches the compositor +as a texture on both platforms. The constraint governs every display path. The export pipeline +reads pixels back by design, because a file is its output. ### 6.2 Tiling from day one diff --git a/docs/dev/technical-debt.md b/docs/dev/technical-debt.md index 7a00b0b..97864fc 100644 --- a/docs/dev/technical-debt.md +++ b/docs/dev/technical-debt.md @@ -14,7 +14,7 @@ has quietly stopped being necessary. --- -## TD-1 — The Android develop view reads pixels back through the CPU +## TD-1 — The Android develop view reads pixels back through the CPU ✅ PAID OFF **Breaks:** [architecture.md §12 / 6.1](architecture.md) — GPU results never round-trip through the CPU — and AC-8, on Android only. Desktop is unaffected and keeps the zero-copy path. @@ -88,6 +88,41 @@ Any one of these removes it: `unstable-wgpu-29` to non-Android, the `#[cfg(target_os = "android")]` arm of `DevelopSession::render` is gone, and the tablet is clean in portrait. +### Paid off + +By none of the three routes above. It came from a fourth that the list missed: patching both +halves locally. "wgpu cannot do [the rotation] on Skia's behalf" was true and beside the point, +because Skia can rotate its own canvas. Slint's Skia renderer already does it for rotated panels on +linuxkms, just not on its wgpu surface. So two small patches, carried in `third_party/` +([README](../../third_party/README.md)), do it for us: + +- **wgpu-hal 29.0.4.** `vulkan::Surface::set_pre_transform` lets a caller choose the swapchain's + `preTransform`, and `current_transform` reads the surface's. It is opt-in, and the default stays + `IDENTITY`, so desktop is unchanged. +- **i-slint-renderer-skia 1.17.1.** On Android the wgpu surface sizes its swapchain in the panel's + orientation, promises the display's transform, and concatenates the matching rotation onto the + canvas before Slint draws. The imported develop texture goes through the same matrix as + everything else. The surface re-reads the transform every frame, because a half turn does not + resize the window. The item renderer's pixel snapping now accepts right-angle rotations, + or portrait would have lost it everywhere. + +With those in place, `unstable-wgpu-29` is common to both platforms, `shared_gpu` hands the one +device to Slint on Android too, and both readbacks are gone (the frame through `export_pixels` and +the focus overlay through `read_overlay`). The develop frame and its overlay reach the compositor +as textures, as they do on desktop. + +**Verified by eye, not by instrument.** On 2026-09-25 the user checked the release-signed build on +the tablet and found it clean. That is the portrait tear this entry was opened over. The +`dumpsys SurfaceFlinger` readings that would complete the table above (composition and +`bufferTransform` per orientation, expected `DEVICE` in all three) were not taken, because adb +would not hold the device that morning. Nor was the develop frame time measured before or after. +The readback's cost was never measured either, so the saving is reasoned, not measured. + +**The cost moved rather than vanished:** two upstream crates are pinned by path. A Slint or wgpu +bump has to carry the patches forward (the README says how), and cargo only *warns* when a patch no +longer matches, then silently builds the unpatched crate. Both patches should go upstream: the +Skia half is the Android counterpart of a feature Slint already has, and the wgpu half is #3345. + --- ## TD-2 — Thumbnails are fetched one at a time