Files
DarkRoom/ui/dr-ui/Cargo.toml
T
dtourolleandClaude Opus 5 cf8f5b632f Show the develop frame itself, instead of a photocopy of it
The oldest open item in the project (ARCH §6.1, spike S1, AC-8). Every frame
in develop was read off the GPU into a `SharedPixelBuffer` and handed back to
Slint to upload again: ~7 ms at 4K against a 0.28 ms compute pass, 96% of the
frame spent carrying pixels to the CPU and back so they could be drawn where
they already were.

Slint 1.17 will adopt a `wgpu::Texture` directly, and the whole of what that
needs is arrangement rather than code.

**One device, made before the window.** A texture belongs to the device that
allocated it, so the compute passes and the compositor cannot each open their
own. `GpuContext::new_shared` opens one and hands back the instance and
adapter alongside it; `dr_ui::shared_gpu` gives all four to
`BackendSelector::require_wgpu_29(WGPUConfiguration::Manual { .. })`. That
call has to come before the first window, because creating one selects a
backend for you — which is why the GPU is now opened at the top of `run`
rather than two hundred lines down beside the other controllers.

dr-gpu still names no UI type. It hands out raw wgpu and does not ask who is
compositing (ARCH §6.5a).

**Vulkan only on the shared path**, where headless keeps its GL fallback.
wgpu's GL backend reaches its display through EGL at instance creation, and
before a window exists there is no display handle to give it — so a GL
instance cannot later produce the window surface Slint needs from it. A
machine with no Vulkan gets no shared device and browses without develop,
which is the same degradation as no adapter at all.

**`renderer-femtovg` becomes `renderer-femtovg-wgpu`.** The old one is FemtoVG
over OpenGL and cannot be handed a wgpu texture at all. It is not kept
alongside as a fallback: FemtoVG-over-GL has no branch for an imported
texture, falls through to "render this image into a buffer", gets nothing, and
draws nothing — a blank canvas with no error, which is worse than the failure
it would be papering over. The consequence is stated plainly in the manifest:
the desktop app now needs a working wgpu adapter to open a window.

**Two output textures, not one, and this is the part that is not obvious.**
Slint repaints when the image property *changes*, and it decides that with
`PartialEq` — which for two images over the same `wgpu::Texture` says
"unchanged". A pass that reused a single target would have rendered every
slider move correctly on the GPU and shown none of them: right, and invisible.
`AdjustPass` alternates between two targets, so consecutive frames are
genuinely different values. It also settles the read-while-write question that
one queue was already answering.

`RENDER_ATTACHMENT` is added to both render targets. Neither pass uses it;
Slint rejects an imported texture without it, on the reasoning that a
compositor handed a texture may need to draw into it.

**`AdjustPass::read_output` is deleted rather than gated.** It and
`export_pixels` were the same transfer under two names, and the comments
explaining why they were separate are the point of the whole criterion:
reading pixels back to *display* them is the defect, reading them back to
*encode a file* is the only way a file is made. The display twin is now gone
outright, which is stronger than a feature flag — it cannot be turned back on.
`export_pixels` is untouched and still ungated. The `readback` feature comes
off dr-ui, darkroom-desktop and darkroom-android; it stays in dr-gpu, where it
still gates `RenderTarget::read_pixels` and the segmentation field readback.
`examples/develop` moves to `export_pixels`, which is honest — it writes a
PPM — and so no longer needs the feature.

Four tests, each named for what it protects and each of which fails without a
screen if the property it guards breaks:

- the adjust target satisfies every condition Slint's import checks, asserted
  in the crate that owns the descriptor, because a descriptor that drifts
  fails at runtime on a real display and nothing else would notice;
- consecutive renders are different textures, and the third is the first
  again, so the alternation is a rotation and not an allocation per frame;
- the develop canvas has no CPU pixel buffer and does have a wgpu texture —
  AC-8 itself, in the terms Slint uses;
- consecutive frames compare unequal as `slint::Image`, which is the property
  the repaint actually depends on.

The zoom test's readback moves into the test module. It has to: there is no
library function that copies a displayed frame to the CPU any more, and that
is the point — the round-trip now exists in the test binary and nowhere a
shipping build can reach.

**What is not proven.** No GUI was run. What is verified is that the texture
satisfies the import contract, that the import succeeds, that the canvas is a
texture rather than a buffer, and that consecutive frames are distinguishable.
What is unverified is everything that needs a display: that Slint's FemtoVG
wgpu renderer adopts the Manual configuration on a real surface, that the
picture appears the right way up and the right colour, and the frame timing
that motivated the whole exercise. Android is untouched by testing — the
android backend routes a WGPU29 request to Skia, whose wgpu surface does
handle imported textures, but that is read from the source, not observed.

56 dr-gpu tests and 255 dr-ui tests pass, clippy clean under `-D warnings`,
fmt clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 09:54:00 +02:00

98 lines
4.3 KiB
TOML

[package]
name = "dr-ui"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
[dependencies]
dr-types.workspace = true
# No `readback`. S1 wired Slint's texture import, so the develop view hands
# the compositor the texture itself and there is no display round-trip left to
# gate (ARCH §6.1, AC-8). The export path reads pixels back through
# `export_pixels`, which is ungated and always was.
dr-gpu.workspace = true
dr-decode.workspace = true
serde_json.workspace = true
tokio.workspace = true
reqwest.workspace = true
dr-plat.workspace = true
dr-sync.workspace = true
dr-sync-nextcloud.workspace = true
dr-export.workspace = true
dr-pipeline.workspace = true
dr-catalog.workspace = true
dr-thumbs.workspace = true
# The library module writes scan results straight into the catalog, so it
# needs the same SQLite types dr-catalog exposes.
rusqlite.workspace = true
# The renderer is shared, but the backend is not: winit on desktop,
# android-activity on Android, and enabling both makes the backend selector
# pick at random. So the backend features live on the target-specific
# dependencies below rather than here.
#
# `renderer-femtovg-wgpu` rather than `renderer-femtovg`: the latter is
# FemtoVG over OpenGL, and a compositor drawing through GL cannot be handed a
# `wgpu::Texture`. Importing one requires Slint itself to be rendering with
# wgpu, and this is the FemtoVG backend that does (ARCH §6.1, spike S1).
#
# `unstable-wgpu-29` is the other half: the renderer feature makes Slint draw
# with wgpu, and this one exposes the API to say so — `BackendSelector::
# require_wgpu_29` and `Image::try_from(wgpu::Texture)`. Unstable is Slint's
# word for it; the surface is small and the alternative is the 7 ms round-trip.
#
# `renderer-femtovg` is *not* kept alongside as a fallback, though it would
# still compile. Slint's winit backend prefers the wgpu FemtoVG renderer
# whenever both are built, so the GL one would only ever be reached by someone
# setting `SLINT_BACKEND=winit-femtovg` — and on that path an imported texture
# is not drawn at all. FemtoVG-over-GL has no branch for a `wgpu::Texture`, so
# it falls through to "render this image to a buffer", gets nothing back, and
# draws nothing. A blank canvas with no error is a far worse failure than the
# one below, so the fallback is removed rather than left as a trap.
#
# Consequence worth stating plainly: the desktop app now needs a working wgpu
# adapter to open a window at all. Slint refuses a CPU adapter for this
# renderer unless `SLINT_WGPU_CPU` is set in the environment.
slint = { workspace = true, features = [
"compat-1-2",
"renderer-femtovg-wgpu",
"unstable-wgpu-29",
] }
wgpu.workspace = true
anyhow.workspace = true
# `SettingsError` distinguishes an io failure from a malformed file, which the
# settings page reports differently; anyhow would flatten both to a string.
thiserror.workspace = true
log.workspace = true
pollster.workspace = true
# Runtime YAML only for `live-style`; release builds read the tokens the
# Slint compiler folded in at build time and never touch style.yaml.
serde_norway = { workspace = true, optional = true }
# Backend per platform. Slint already declares its android-activity backend
# under `cfg(target_os = "android")`, so this only has to name the feature;
# cargo resolves it away entirely on desktop.
[target.'cfg(not(target_os = "android"))'.dependencies]
slint = { workspace = true, features = ["backend-winit"] }
[target.'cfg(target_os = "android")'.dependencies]
slint = { workspace = true, features = ["backend-android-activity-06"] }
# Opening the sign-in URL needs an ACTION_VIEW Intent — Android has no
# xdg-open. Version-matched to Slint's Android backend so both halves of the
# process agree on the JavaVM types; ndk-context supplies the VM and activity
# that android-activity's glue already stashed.
jni = "0.22"
ndk-context = "0.1"
[build-dependencies]
slint-build.workspace = true
# build.rs generates theme.slint from style.yaml (S2).
serde_norway.workspace = true
[features]
default = []
# Debug convenience: re-read style.yaml at startup so a palette can be tuned
# without rebuilding. Costs the constant-folding of every token, so it stays
# off by default and has no business in a release build.
live-style = ["dep:serde_norway"]