Record TD-1 as paid off, checked by eye on the tablet
The pre-rotation patches landed in the previous four commits. This records how the debt was paid, by a fourth route its own list missed: patching wgpu-hal and Slint's Skia surface locally rather than waiting for either upstream. It also updates architecture.md §1 and §6.1, which still said Android draws with OpenGL behind a readback. The verification is stated as what it was: the user found the release-signed build clean on the tablet in portrait. No dumpsys composition or bufferTransform readings were taken, because adb would not hold the device that morning, and no frame times were measured.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user