The grid tore while scrolling on the tablet, in portrait only, and was
flawless in landscape. It was not vsync and it was not the grid.
Measured on the device, same build, only the tablet rotated:
landscape bufferTransform=ROT_180 composition=DEVICE (2) clean
portrait bufferTransform=ROT_270 composition=CLIENT (1) torn
The panel is mounted landscape — 1920x3000 at installOrientation 3 — so a
portrait window needs a 90 degree rotation before scanout. wgpu-hal hardcodes
the swapchain's `preTransform` to `IDENTITY` and says so in a comment beside
the line:
// On Android 10+, libvulkan's `vkQueuePresentKHR` returns
// `VK_SUBOPTIMAL_KHR` if not doing pre-rotation ... This is always the
// case when the device orientation is anything other than the identity
// one, as we unconditionally use `VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR`.
That is gfx-rs/wgpu#3345, and it cannot be fixed by setting the field:
`preTransform` is a *promise* that the content is already rotated, so keeping
it needs the renderer to rotate what it draws, which wgpu cannot do on Skia's
behalf.
We do not have to be on that swapchain. `AndroidWindowAdapter` chooses
`SkiaRenderer::default_wgpu_29` only because this crate enables
`unstable-wgpu-29`; without it `SkiaRenderer::default` resolves — through
i-slint-renderer-skia's build script, which selects OpenGL on anything that is
not Apple, Windows or wasm — to Skia over OpenGL, where the driver owns the
rotation and there is no transform to get wrong. So both renderer features
move to the desktop-only dependency, and desktop is untouched.
# The cost, stated rather than hidden
Skia over OpenGL cannot sample a `wgpu::Texture`, so the develop view's frame
comes back through memory: `AdjustPass::export_pixels`, already ungated and
already used by the export path, into a `SharedPixelBuffer`. That is the
round-trip ARCH §6.1 and AC-8 exist to forbid, and it is the right trade only
because of what the alternative actually is — not a faster develop view, but a
grid that tears in the orientation a tablet is mostly held in.
Two things keep it small. The device is still opened on Android, so demosaic
and the adjust pass are untouched on the GPU; only the last hop changes. And
`render` fits the pass to the canvas before it runs, so the readback is at
viewport resolution, a fraction of the ~7 ms at 4K the original measurement
was taken against.
Four other explanations died on the way here, each by measurement rather than
argument: the present mode (a patch confirmed in the installed binary reached
`AutoVsync`, and the rows still duplicated), our shared wgpu device (Slint
opened its own, unchanged), Skia's partial rendering (off for GPU surfaces),
and client composition itself (unavoidable in portrait on this panel, so it
cannot be what distinguishes a torn frame from a clean one).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
114 lines
5.3 KiB
TOML
114 lines
5.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.
|
|
#
|
|
# `segment-readback` *is* on, and it is not a contradiction of the above. It
|
|
# gates the region-graph transfer that local masking is built on: once per
|
|
# image, on a worker, off the frame path. The display round-trip AC-8 forbids
|
|
# stays behind its own switch, which remains off. See `dr-gpu/src/segment.rs`.
|
|
dr-gpu = { workspace = true, features = ["segment-readback"] }
|
|
# The semantic arm and its weights, for local adjustments (FR-DEV-3, D14).
|
|
dr-segment = { workspace = true, features = ["semantic", "embedded-model"] }
|
|
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-ingest.workspace = true
|
|
dr-film.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.
|
|
# TEST BUILD: the wgpu renderer features have moved to the desktop-only
|
|
# dependency below. On Android they made `AndroidWindowAdapter` choose
|
|
# `SkiaRenderer::default_wgpu_29`, and so put the app on wgpu's Vulkan
|
|
# swapchain — which hardcodes `preTransform = IDENTITY` (gfx-rs/wgpu#3345).
|
|
# Without them the Android backend uses `SkiaRenderer::default`, which on
|
|
# Android resolves to Skia over OpenGL, where the driver owns the display
|
|
# rotation and there is no transform to get wrong.
|
|
slint = { workspace = true, features = ["compat-1-2"] }
|
|
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",
|
|
"renderer-femtovg-wgpu",
|
|
"unstable-wgpu-29",
|
|
] }
|
|
|
|
[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"]
|